본문 바로가기
Java

[Java] Optional

by goblin- 2024. 9. 29.

자바에서 **Optional**은 null 처리를 안전하게 할 수 있도록 도와주는 유틸리티 클래스입니다. 자바 8에서 도입되었으며, null 값을 직접 다루는 대신 값이 있거나 없을 수 있는 컨테이너로, null 포인터 예외를 피하고 더 명확한 코드를 작성하는 데 도움을 줍니다.

 

1. Optional의 기본 개념

 

**Optional**은 값이 있을 수도 있고, 없을 수도 있는 상황에서 null을 사용하지 않고, 더 명확하게 비어 있음을 표현할 수 있는 클래스로 설계되었습니다.

**null 포인터 예외(NullPointerException, NPE)**는 자바에서 자주 발생하는 오류 중 하나입니다. Optional을 사용하면 null을 직접 사용하지 않고, 값이 없을 때의 처리를 안전하게 할 수 있습니다.

 

Optional의 정의

public final class Optional<T> {
    // Optional 클래스 내부는 제네릭 T 타입을 사용
}

**Optional**은 제네릭 클래스로, T 타입의 값을 감싸는 역할을 합니다.

Optional은 값이 있거나 없을 수 있는 상황을 처리하는 데 사용됩니다.

 

2. Optional을 사용하는 이유

 

1. 명확한 의사 표현:

null을 직접 사용하는 대신 Optional을 사용함으로써, 해당 값이 있을 수도 없을 수도 있음을 명확하게 표현할 수 있습니다. 메서드가 Optional을 반환한다면, 호출자는 그 값이 존재하지 않을 수 있음을 알 수 있습니다.

2. null 포인터 예외 방지:

null을 직접 다루는 경우 자칫하면 NullPointerException이 발생할 수 있습니다. Optional을 사용하면 명시적으로 값의 존재 여부를 처리할 수 있기 때문에 NPE를 방지할 수 있습니다.

3. 더 읽기 쉬운 코드:

Optional을 사용하면 if-else null 체크 대신, Optional이 제공하는 메서드를 통해 코드가 더 간결해지고 의도를 명확히 드러낼 수 있습니다.

 

3. Optional 사용법

 

3.1 Optional 객체 생성

 

Optional 객체는 주로 다음 세 가지 방식으로 생성할 수 있습니다.

 

1. Optional.of(): 값이 절대로 null이 아님을 보장할 때 사용합니다. 만약 null을 전달하면 NullPointerException이 발생합니다.

Optional<String> nonEmptyOpt = Optional.of("Hello");

 

2. Optional.ofNullable(): 값이 null일 수도 있고, 아닐 수도 있는 상황에서 사용합니다. 값이 null이면 비어 있는 Optional을 반환합니다.

Optional<String> nullableOpt = Optional.ofNullable(null);  // Optional.empty() 반환

 

3. Optional.empty(): 비어 있는 Optional을 명시적으로 생성할 때 사용합니다.

Optional<String> emptyOpt = Optional.empty();

 

 

3.2 Optional에서 값 가져오기

 

Optional을 사용하여 값에 접근하는 방식은 여러 가지가 있으며, 각 방식은 값이 존재하는지 여부에 따라 다르게 처리됩니다.

 

1. isPresent(): Optional에 값이 있는지 확인할 때 사용합니다.

Optional<String> opt = Optional.of("Hello");

if (opt.isPresent()) {
    System.out.println(opt.get());  // 출력: Hello
}

 

2. get(): Optional에 값이 있을 때만 값을 반환하며, 만약 값이 없으면 NoSuchElementException을 던집니다. 따라서, isPresent()로 확인한 후 사용하는 것이 안전합니다.

Optional<String> opt = Optional.of("Hello");
System.out.println(opt.get());  // 출력: Hello

 

3. orElse(): Optional에 값이 없을 경우 대체 값을 반환합니다.

Optional<String> opt = Optional.empty();
String result = opt.orElse("Default Value");
System.out.println(result);  // 출력: Default Value

 

4. orElseGet(): Optional에 값이 없을 때 대체 값을 생성하는 함수를 실행합니다. 이 함수는 대체 값이 비용이 클 경우 유용합니다.

Optional<String> opt = Optional.empty();
String result = opt.orElseGet(() -> "Generated Default Value");
System.out.println(result);  // 출력: Generated Default Value

 

 

5. orElseThrow(): Optional에 값이 없을 경우 예외를 던집니다.

Optional<String> opt = Optional.empty();
String result = opt.orElseThrow(() -> new IllegalArgumentException("값이 없습니다"));

 

3.3 Optional 값 처리

 

Optional은 값이 있을 때 그 값을 처리할 수 있는 여러 메서드를 제공합니다.

 

1. ifPresent(): Optional에 값이 있을 때만 동작을 수행합니다.

Optional<String> opt = Optional.of("Hello");
opt.ifPresent(value -> System.out.println("값이 있습니다: " + value));  // 출력: 값이 있습니다: Hello

 

2. map(): Optional 내부의 값을 변환하는 데 사용됩니다. 값이 없으면 Optional.empty()를 반환합니다.

Optional<String> opt = Optional.of("Hello");
Optional<String> upperOpt = opt.map(String::toUpperCase);  // 값이 있을 때만 변환
System.out.println(upperOpt.get());  // 출력: HELLO

 

3. flatMap(): Optional 내부의 값이 또 다른 Optional일 때 중첩된 Optional을 평탄화(하나의 Optional로 합침)합니다.

Optional<String> opt = Optional.of("Hello");
Optional<Optional<String>> nestedOpt = Optional.of(Optional.of("World"));

// flatMap으로 중첩된 Optional을 풀어냄
Optional<String> flattenedOpt = nestedOpt.flatMap(o -> o);
System.out.println(flattenedOpt.get());  // 출력: World

 

4. Optional을 사용하는 예시

 

Optional은 주로 메서드의 반환 타입으로 사용되어, 값이 없을 가능성을 명확하게 처리할 수 있습니다.

 

4.1 데이터베이스 조회 예시

import java.util.Optional;

class UserRepository {
    // Optional을 반환하여 값이 없을 가능성을 명확히 표현
    public Optional<User> findByUsername(String username) {
        // 데이터베이스 조회 로직 (예시)
        if (username.equals("john")) {
            return Optional.of(new User("john", "John Doe"));
        } else {
            return Optional.empty();
        }
    }
}

class Main {
    public static void main(String[] args) {
        UserRepository userRepository = new UserRepository();
        Optional<User> userOpt = userRepository.findByUsername("john");
        
        // ifPresent를 사용하여 값이 있을 때만 동작 수행
        userOpt.ifPresent(user -> System.out.println("사용자 이름: " + user.getName()));

        // orElse를 사용하여 값이 없을 때 기본값 반환
        User defaultUser = userRepository.findByUsername("unknown").orElse(new User("unknown", "Anonymous"));
        System.out.println("기본 사용자 이름: " + defaultUser.getName());
    }
}

class User {
    private String username;
    private String name;

    public User(String username, String name) {
        this.username = username;
        this.name = name;
    }

    public String getUsername() {
        return username;
    }

    public String getName() {
        return name;
    }
}

findByUsername() 메서드는 **Optional**를 반환하여, 호출하는 쪽에서 사용자가 없을 경우를 명시적으로 처리할 수 있습니다.

ifPresent()orElse()를 사용하여 값이 있을 때의 처리없을 때의 기본 처리를 각각 구현할 수 있습니다.

 

5. Optional을 사용할 때의 주의사항

 

1. Optional 필드는 지양:

Optional은 주로 메서드 반환 타입으로 사용되며, 클래스 필드로 사용하는 것은 권장되지 않습니다. 필드에 Optional을 사용하면 메모리 낭비를 초래할 수 있습니다.

2. Optional을 남용하지 말 것:

Optional은 값이 있을 수도 없을 수도 있는 상황에서만 사용해야 합니다. 모든 경우에 Optional을 사용하면 불필요한 복잡성을 초래할 수 있습니다.

3. get()의 사용을 피하라:

get() 메서드는 Optional의 값을 무조건 반환하며, 값이 없을 경우 예외가 발생할 수 있습니다. 대신 안전한 접근 방식(예: orElse, orElseThrow, ifPresent)을 사용해야 합니다.

 

6. 결론

 

Optional은 자바에서 null을 안전하게 처리하기 위한 유용한 도구입니다. null을 직접 다루지 않고, Optional의 메서드를 사용하여 값을 안전하게 처리할 수 있습니다. 이를 통해 null 포인터 예외를 방지하고, 더 명확한 코드를 작성할 수 있습니다.

 

값이 있을 수도 없을 수도 있는 상황을 명시적으로 표현하여, 코드의 가독성안정성을 높입니다.

Optional은 특히 함수형 프로그래밍 스타일과 잘 어울리며, 값을 안전하게 변환하고 처리하는 다양한 메서드를 제공합니다.