# Java Optional - 개발을 하다보면 가장 많이 발생하는 예외 중 하나가 바로 NPE(Null Pointer Exception)이다. 자바에서는 Optional이라는 클래스를 제공함으로써 이 NPE를 회피하는 효과적인 방법을 제시한다. ## Optional이란? - Optional은 Java 8에서 도입된 클래스로, Null Pointer Exception을 방지하고, Null을 보다 명확하게 처리하도록 돕는 기능을 제공한다. - Optional은 java.util 패키지에 포함되어 있는 클래스로, 이 클래스의 주 목적은 Null이 될 수 있는 객체를 감싸는 데에 있다. - 값이 Null일 가능성이 있는 객체를 직접 다루는 대신, Optional 객체를 통해 간접적으로 다루게 한다. 이렇게 함으로써 Optional은 개발자가 의도치 않은 Null Pointer Exception에 대한 방어적인 코딩을 할 수 있게 돕는다. ## 백문이불여일견 ### Optional 사용 전 ```java public String getCarInsuranceName(Person person) { // return person.getCar().getInsurance().getName(); if (person != null) { Car car = person.getCar(); if (car != null) { Insurance insurance = car.getInsurance(); if (insurance != null) { return insurance.getName(); } } } return "Unknown"; } ``` ### Optional 사용 후 ```java public String getCarInsuranceName(Person person) { return Optional.ofNullable(person) .map(Person::getCar) .map(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); } ``` ## Optional의 장점 ### 1. 명확한 의도의 표현 - Optional은 메서드가 결과를 반환하지 않을 수 있다는 것을 명확하게 표현한다. 즉, Optional을 사용하면 Null을 반환하는 것이 아니라, 비어있는 Optional을 반환하는 것이 가능해진다. ### 2. Null Pointer Exception 방지 - Optional은 Null 값을 직접 다루는 것이 아니라 감싸서 다루므로, NullPointerException을 방지할 수 있다. ## Optional 사용 방법 ### Optional 생성하기 Optional 객체를 생성하는 방법은 간단하다. ```java Optional<String> optional = Optional.of("Hello World"); ``` > 위 코드는 "Hello World"라는 문자열을 가진 Optional 객체를 생성한다. - 만약 Null 값을 가진 Optional 객체를 생성해야하는 경우에는 `Optional.empty()` 메서드를 사용한다. ```java Optional<String> optional = Optional.empty(); ``` > 위 코드는 빈 Optional 객체를 생성하는 코드이다. - 만약 Null이 될 수 있는 값을 Optional 객체로 감싸려면 `Optional.ofNullable()` 메서드를 사용한다. ```java String str = null; Optional<String> optional = Optional.ofNullable(str); ``` 위 코드는 Null 값을 가진 Optional 객체를 생성한다. ### Optional 값 접근하기 Optional 객체의 값을 얻으려면 `get()` 메서드를 사용한다. ```java Optional<String> optional = Optional.of("Hello World"); String value = optional.get(); // "Hello World" ``` 하지만 만약 Optional 객체가 비어 있으면 `get()` 메서드는 NoSuchElementException을 던진다. 따라서 `get()` 메서드를 호출하기 전에 `isPresent()` 메서드를 사용하여 Optional 객체가 값이 있는지 확인해야 한다. ```java Optional<String> optional = Optional.of("Hello World"); if (optional.isPresent()) { String value = optional.get(); // "Hello World" } ``` 그런데 이 방법은 여전히 Null 확인 로직을 직접 다루게 한다. 그 대신 Optional에서 제공하는 `orElse()` 메서드를 사용하여 Optional 객체가 비어 있을 때의 기본 값을 설정할 수 있다. ```java Optional<String> optional = Optional.empty(); String value = optional.orElse("Default Value"); // "Default Value" ``` ## Optional의 더 많은 기능들 ### ifPresent() `ifPresent()` 메서드는 값이 존재하는 경우에만 주어진 작업을 실행하는 메서드이다. 이 메서드는 매개변수로 Consumer 함수형 인터페이스를 받는다. ```java Optional<String> optional = Optional.of("Hello World"); optional.ifPresent(value -> System.out.println(value)); // "Hello World" ``` ### orElseGet() `orElseGet()` 메서드는 Optional 객체가 비어있을 때 실행할 Supplier 함수형 인터페이스를 인자로 받는다. 이 메서드는 `orElse()` 메서드와 비슷하지만, 기본값을 동적으로 생성할 수 있다는 점에서 차이가 있다. ```java Optional<String> optional = Optional.empty(); String value = optional.orElseGet(() -> "Default Value"); // "Default Value" ``` #### `orElse()`와 `orElseGet()`의 차이 `orElse()`와 `orElseGet()`은 모두 Java의 Optional 클래스에 포함된 메서드로, Optional 객체가 비어있는 경우에 대한 대체 값을 제공한다. 하지만 두 메서드는 사용 방식에 약간의 차이가 있다. `orElse()` 메서드는 인수로 받은 값을 반환한다. 이 인수는 Optional 객체가 비어있는지 여부와 상관없이 항상 계산된다. 따라서 이 인수는 CPU 또는 메모리 자원을 많이 사용하는 "무거운" 연산이 아닐 때 사용하는 것이 좋다. ```java Optional<String> optional = Optional.empty(); String value = optional.orElse(expensiveOperation()); ``` 위의 코드에서 `expensiveOperation()`은 Optional 객체가 비어있는지 여부에 관계없이 항상 실행된다. 반면에, `orElseGet()` 메서드는 인수로 받은 Supplier를 호출하여 그 결과를 반환한다. 이 Supplier는 Optional 객체가 비어 있을 때만 실행되므로, CPU 또는 메모리 자원을 많이 사용하는 "무거운" 연산을 실행할 때 사용하는 것이 좋다. ```java Optional<String> optional = Optional.empty(); String value = optional.orElseGet(() -> expensiveOperation()); ``` 위의 코드에서 `expensiveOperation()`은 Optional 객체가 비어 있을 때만 실행된다. 따라서 `orElse()`와 `orElseGet()`의 주된 차이점은 비어있는 Optional 객체에 대한 대체 값을 언제 계산하느냐에 있다. `orElse()`는 항상 계산하고, `orElseGet()`은 필요할 때만 계산한다. ### orElseThrow() `orElseThrow()` 메서드는 Optional 객체가 비어있을 때 예외를 발생시키는 메서드이다. 이 메서드는 `orElseGet()` 메서드와 비슷하지만, 비어있는 경우 Supplier 함수형 인터페이스에 의해 제공되는 예외를 던진다. ```java Optional<String> optional = Optional.empty(); String value = optional.orElseThrow(() -> new IllegalArgumentException("Optional is empty")); ``` ### filter(), map(), flatMap() Optional 클래스는 `filter()`, `map()`, `flatMap()` 등의 메서드를 제공하여 Optional 객체를 더 유연하게 다룰 수 있다. 이들 메서드는 Stream 인터페이스의 동일한 메서드와 비슷한 동작을 한다. `filter()` 메서드는 Predicate 함수형 인터페이스를 인자로 받아 Optional 객체의 값을 테스트하고, 테스트를 통과하는 값만을 포함하는 새로운 Optional 객체를 반환한다. ```java Optional<String> optional = Optional.of("Hello World"); Optional<String> filtered = optional.filter(value -> value.length() > 10); // Optional["Hello World"] ``` `map()` 메서드는 Function 함수형 인터페이스를 인자로 받아 Optional 객체의 값을 변환하고, 그 결과를 포함하는 새로운 Optional 객체를 반환한다. ```java Optional<String> optional = Optional.of("Hello World"); Optional<Integer> mapped = optional.map(String::length); // Optional[11] ``` `flatMap()` 메서드는 Function 함수형 인터페이스를 인자로 받아 Optional 객체의 값을 변환하고, 그 결과를 포함하는 새로운 Optional 객체를 반환한다. 이 메서드는 `map()` 메서드와 비슷하지만, 변환 함수의 결과가 Optional인 경우에 유용하다. ```java Optional<String> optional = Optional.of("Hello World"); Optional<Optional<String>> mapped = optional.map(value -> Optional.of(value.toLowerCase())); // Optional[Optional["hello world"]] Optional<String> flatMapped = optional.flatMap(value -> Optional.of(value.toLowerCase())); // Optional["hello world"] ``` ## Optional 사용 시 주의사항 Java의 `Optional` 클래스는 null 값을 안전하게 처리하는 데 도움이 되는 강력한 도구이지만, 모든 상황에 적합한 것은 아니다. 마지막으로 이와같은 `Optional` 사용에 대한 주요 고려 사항을 알아보자. - **Optional이 null이 될 수 있는 경우** - 아이러니하게도, `Optional` 객체 자체가 null이 될 수 있다. 이러한 상황은 피해야 한다. `Optional`을 사용하는 주요 목적이 null 참조를 피하는 것이기 때문이다. - **필드로서의 Optional** - 일반적으로 클래스 필드로서 `Optional`을 사용하는 것은 권장되지 않는다. `Optional`은 반환 타입으로 사용되거나 메소드 인자로 사용될 수 있지만, 필드에 `Optional`을 사용하면 메모리 사용량이 증가하고, 직렬화 문제가 발생할 수 있다. - **컬렉션과 배열에 대한 Optional** - 컬렉션, 배열 또는 스트림에서 `Optional`을 사용하는 것은 일반적으로 불필요하다. 이러한 타입들은 이미 "empty" 상태를 가질 수 있으므로, `Optional`을 사용하여 이를 표현하는 것은 복잡성을 불필요하게 증가시킨다. - **비용과 성능** - `Optional` 객체의 생성과 처리는 일정량의 오버헤드를 가진다. 대부분의 경우, 이 오버헤드는 무시할 만큼 작지만, 성능이 매우 중요한 상황에서는 고려해야 할 요소다. - **Optional 체이닝** - `Optional` 메소드를 체이닝하는 것은 코드를 간결하게 만들 수 있지만, 복잡한 체이닝은 가독성을 해칠 수 있다. - **null 반환** - `Optional`을 사용하는 주요 목적 중 하나는 null을 반환하는 것을 피하는 것이다. 그러나 `Optional` 메소드 중 하나인 `orElse(null)`은 결국 null을 반환하게 된다. 이러한 사용은 `Optional`의 목적에 반하는 것으로 볼 수 있다.