Optional
JAVA/Reference

Optional

개발을 하면서 다들 NullPointException을 겪어 보셨을 겁니다.

참조 및 예외로 값이 없는 상황에서 가장 구현이 쉬운 'null'을 넣은 탓입니다. (지금은 땅을 치고 후회 중입니다.)

 

그래서 이러한 상황을 모면하기 위해 개발자들은 'null' 검증을 통해서 코드를 처리하곤 했었습니다.

String case1(){
	if(ref != null){
		null이 아닐 때 처리하는 코드
	}
	return "unknown"	// null일 시 리턴 값
}

String case2(){
	if(ref == null){
    		return "unknown"	// null일 시 리턴 값
	}
	null이 아닐 때 처리하는 코드
}

하지만 참조되는 것이 많을수록 중첩 if문을 사용해야 하고 이는 유지 보수성이 떨어지는 결과를 낳게 됩니다.

 

그래서 JAVA8부터는 이를 막을 방법들을 고안해냈습니다.

바로 Optional 클래스를 통해서 말이죠

 

Optional 클래스

public final class Optional <T> extends Object
A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present). This is a value-based class; use of identity-sensitive operations (including reference equality (==), identity hash code, or synchronization) on instances of Optional may have unpredictable results and should be avoided.

null이 아닌 값을 포함하거나 포함하지 않을 수 있는 컨테이너 개체입니다. (선택형 값을 캡슐화하는 클래스)

    public static void main(String[] args) {
        Student student1 = new Student("name1", 20);
        Student student2 = null;
        Optional<Student> optionalStudent1 = Optional.of(student1);
        Optional<Student> optionalStudent2 = Optional.ofNullable(student2);

        optionalStudent1.map(Student::getName); // 이상 없음
        optionalStudent2.map(Student::getName); // 이상 없음

        student1.getName();        // 이상 없음
        student2.getName();        // nullPointException 발생
    }

 

1. Optional 생성하기

◎ Optional.empty( ) : 빈 Optional 생성

public static <T> Optional <T> empty()
Returns an empty Optional instance. No value is present for this Optional.
API Note : 
Though it may be tempting to do so, avoid testing if an object is empty by comparing with == against instances returned by Option.empty(). There is no guarantee that it is a singleton. Instead, use isPresent().
Type-Parameters : T - Type of the non-existent value
Returns : an empty Optional

 

어려울 것 없습니다. 정적 팩토리 메서드 Optional.empty( )를 통해 빈 Optional 객체를 생성합니다.

그림2. 빈 Optional

객체가 진짜 비어있는지를 테스트하고 싶을 때가 있을 겁니다.

다른 인스턴스들과 마찬가지로 싱글톤 보장이 되지 않기 때문에 '=='을 통해서 테스트하시면 안 됩니다!!

대신 'isPresent( )'를 사용해서 테스트를 할 수 있습니다.

 

○ isPresent( )

public boolean isPresent()
Return true if there is a value present, otherwise false.
Returns : true if there is a value present, otherwise false

값이 있으면 true를 반환하고 그렇지 않으면 false를 반환합니다.

 

○ ifPresent( )

public void ifPresent(Consumer <? super T> consumer)
If a value is present, invoke the specified consumer with the value, otherwise do nothing.
Parameters:consumer - block to be executed if a value is present
Throws:NullPointerException - if value is present and consumer is null

값이 있으면 consumer을 호출하고 그렇지 않으면, 아무 작업도 수행하지 않습니다.

값이 null인 것은 상관없지만 consumer가 null이면 NullPointerException이 발생합니다.

 

 

◎ Optional.of( ) : null이 아닌 값으로 Optional 만들기

public static <T> Optional <T> of(T value)
Returns an Optional with the specified present non-null value.
Type Parameters : T - the class of the valueParameters:value - the value to be present, which must be non-null
Returns : an Optional with the value presentThrows:NullPointerException - if value is null

 

정적 팩토리 메서드 Optional.of를 통해 null값이 아닌 지정된 Optional을 생성합니다.

Optional<T> var = Optional.of(T value)

 

 

◎ Optional.ofNullable( ) : null이면 빈 Optional, 그렇지 않으면 지정된 Optional 만들기

public static <T> Optional<T> ofNullable(T value)
Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.
Type Parameters : T - the class of the value
Parameters : value - the possibly-null value to describe
Returns : an Optional with a present value if the specified value is non-null, otherwise an empty Optional

 

null이 아닌 경우 지정된 값을 설명하는 Optional을 반환하고, 그렇지 않으면 빈 Optional을 반환합니다.

 

파라미터로는 null의 가능성이 있는 값을 입력하시면 됩니다.

그렇게 되면 지정된 값이 null이 아닌 경우 현재 값이 있는 Optional, 그렇지 않으면 비어 있는 Optional이 반환될 것입니다.

 

 

 

2. 데이터 얻기

◎ get( )

public T get()
If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException.
Returns : the non-null value held by this Optional
Throws : NoSuchElementException - if there is no value presentSee Also:isPresent()

Optional에 있는 값을 꺼내는 메서드입니다. 간단하지만 가장 안전하지 않습니다.

Optional로 래핑이 되어있는 것이 아니기 때문에, 여기서는 값이 없으면 NoSuchElementException을 출력합니다.

  • Optional내에 값이 있으면 값을 반환
  • Optional내에 값이 없으면 NoSuchElementException

그렇기 때문에 반드시 값이 있다고 가정할 수 있는 상황에서만 get( )을 사용하기를 권장합니다.

 

 

◎ orElse

public T orElse(T other)
Return the value if present, otherwise return other.
Parameters : other - the value to be returned if there is no value present, may be null
Returns : the value, if present, otherwise other

값이 존재하면 값을 반환하고, 값이 없으면 기본값(other)을 반환합니다.

 

 

◎ orElseGet

public T orElseGet(Supplier <? extends T> other)
Return the value if present, otherwise invoke other and return the result of that invocation.
Parameters : other - a Supplier whose result is returned if no value is present
Returns : the value if present otherwise the result of other.get()
Throws : NullPointerException - if value is not present and other is null

값이 존재하면 값을 get( )으로 반환하고, 값이 없으면 Supplier을 호출해 Supplier이 제공하는 값을 반환합니다.

get( )으로 반환하기 때문에 값이 Null이거나, Supplier가 Null이라면 NullPointerException을 반환합니다.

 

 

 orElseThrow

public <X extends Throwable> T orElseThrow(Supplier <? extends X> exceptionSupplier) throws X extends Throwable
Return the contained value, if present, otherwise throw an exception to be created by the provided supplier.
API Note : A method reference to the exception constructor with an empty argument list can be used as the supplier. For example, IllegalStateException::new
Type Parameters : X - Type of the exception to be thrown
Parameters : exceptionSupplier - The supplier which will return the exception to be thrown
Returns : the present value
Throws : X - if there is no value present
            NullPointerException - if no value is present and exceptionSupplier is nullX extends Throwable

값이 존재하면 값을 반환하고, 값이 없으면 Supplier을 호출해 Supplier이 생성한 예외를 발생시킵니다.

값이 없고 exceptionSupplier가 null인 경우 NullPointerException을 반환합니다.

 

 

◎ or

public Optional<T> or​(Supplier<? extends Optional<? extends T>> supplier)
If a value is present, returns an Optional describing the value, otherwise returns an Optional produced by the supplying function.
Parameters : supplier - the supplying function that produces an Optional to be returned Returns: returns an Optional describing the value of this Optional, if a value is present, otherwise an Optional produced by the supplying function.
Throws : NullPointerException - if the supplying function is null or produces a null result
Since : 9

값이 있으면 Optional을 반환하고, 그렇지 않으면 파라미터로 입력한 함수에서 생성한 Optional을 반환합니다.

위의 여러 메서드들과 다르게 or은 supplier가 Optional을 반환하는 것을 주목하셔야 합니다.

 

ifPresentOrElse

public void ifPresentOrElse​(Consumer<? super T> action, Runnable emptyAction)
If a value is present, performs the given action with the value, otherwise performs the given empty-based action.
Parameters :
action - the action to be performed, if a value is present
emptyAction - the empty-based action to be performed, if no value is present
Throws : NullPointerException - if a value is present and the given action is null, or no value is present and the given empty-based action is null.
Since : 9

Optional이 비었을때 파라미터로 받은 Runnable을 실행합니다.

데이터 검증 섹션에 들어가야 하지만 Runnable을 실행하기 때문에 데이터 얻기에 포함했습니다.

Runnable이 null인 경우 NullPointerException을 반홥합니다.

 

 

 

3. 데이터 가공

◎ filter( )

public Optional<T> filter(Predicate <? super T> predicate)
If a value is present, and the value matches the given predicate, return an Optional describing the value, otherwise return an empty Optional.
Parameters : predicate - a predicate to apply to the value, if present
Returns : an Optional describing the value of this Optional if a value is present and the value matches the given predicate, otherwise an empty Optional
Throws : NullPointerException - if the predicate is null

파라미터에 입력된 predicate에 따라 일치하면 Optional을 반환하고, 일치하지 않는다면 빈 Optional을 반환합니다.

Optional은 NullPointerException에 대해 면역이나, 파라미터인 predicate가 null이면, NullPointerException 예외가 발생할 수 있습니다.

 

 

◎ Optiona.map( ) : 스트림의 map( )처럼 추출한 값을 변환

public <U> Optional <U> map(Function <? super T,? extends U> mapper)
If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.

API Note : This method supports post-processing on optional values, without the need to explicitly check for a return status. For example, the following code traverses a stream of file names, selects one that has not yet been processed, and then opens that file, returning an Optional <FileInputStream>: 
Optional<FileInputStream> fis = names.stream()
	.filter(name -> !isProcessedYet(name)) 
        .findFirst() 
        .map(name -> new FileInputStream(name));​
Here, findFirst returns an Optional <String>, and then map returns an Optional <FileInputStream> for the desired file if one exists.

Type Parameters : U - The type of the result of the mapping function
Parameters : mapper - a mapping function to apply to the value, if present
Returns : an Optional describing the result of applying a mapping function to the value of this Optional, if a value is present, otherwise an empty Optional

Throws : NullPointerException - if the mapping function is null

 

정보 중 null일 가능성이 있기 때문에, 객체의 정보를 추출할 때는 Optional을 사용할 때가 많습니다.

여기서 2차적으로 추출한 데이터를 가공할 때 스트림에서 했던 것처럼 'map( )'을 사용합니다. 

Optional.map의 경우 값이 있으면 제공된 매핑 함수를 적용하고, 결과가 null이면, 빈 Optional을 반환합니다.

 

Optional<Student> optStudent = Optional.ofNullable(student);
Optional<String> name = optStudent.map(Student::getName);

값이 있는 경우 이 매핑 함수를 적용한 결과를 설명하는 Optional을, 그렇지 않으면 빈 Optional을 반환합니다.

 

 

◎ Optiona.flatMap( ) : 계층화된 Optional 클래스들을 평면화 

public <U> Optional <U> flatMap(Function <? super T, Optional <U>> mapper)
If a value is present, apply the provided Optional-bearing mapping function to it, return that result, otherwise return an empty Optional. This method is similar to map(Function), but the provided mapper is one whose result is already an Optional, and if invoked, flatMap does not wrap it with an additional Optional.
Type Parameters : U - The type parameter to the Optional returned by
Parameters : mapper - a mapping function to apply to the value, if present the mapping functionReturns:the result of applying an Optional-bearing mapping function to the value of this Optional, if a value is present, otherwise an empty Optional
Throws : NullPointerException - if the mapping function is null or returns a null result

 

기존의 리턴 값은 두 가지 경우로 나뉩니다.

  • 값이 있으면 제공된 Optional을 함유한 매핑 기능을 적용하고 해당 결과를 반환
  • 값이 없으면 빈 Optional을 반환

다음 예를 보고 리턴되는 상황을 살펴봅시다.

public class Student{
    private String name;
    private int age;
    private Optional<Address> address;		// Optional 지정
}

public class Address{
    private String phoneNumber;
    private String homeAddress;
}

Address에 Optional <T>을 감싼 것을 주목하셔야 합니다.

Optional<Student> optStudent = Optional.ofNullable(student);
Optional<String> phoneNumber1 = optStudent.map(Student::getAddress)	// Optional<Optional<Address>>
				.map(Address::getPhoneNumber);		// 사용 불가.

여기서 첫 번째 map 연산은 Adress가 Optional <Address> 타입이기 때문에 반환 값이 Optioanl <Optional <Address>>가 됩니다. 그래서 일반적인 (Address:getPhoneNumber)은 불가능하게 됩니다.

 

Stream의 경우와 같이 이렇게 계층화된 객체들을 평 면화시키는 방법이 있습니다. 바로 flatMap( )입니다.

flatMap( )은 map(Function)과 같은 기능을 하나 결과가 이미 Optional이면 Optional을 추가로 래핑 하지 않습니다.

// flatMap이면 계층화된 Optional을 평면화 시켜준다.
Optional<String> phoneNumber3 = optStudent.flatMap(Student::getAddress)	// Optional<Address>
			.map(Addrsss:getPhoneNumber);

 

도메인 모델에서의 Optional

위에서는 Student 도메인에서 Optional <Address>을 사용했습니다.

하지만, Optional 설계자는 Optional이 선택형 반환 값을 지원하는 용도라고 정확하게 밝혔습니다. 이 외의 용도는 가정하지 않았단 말이죠!

 

그렇기 때문에 Optional 클래스는 Serializable 인터페이스를 구현하지 않습니다.

따라서 이렇게 도메인 모델에서 Optional을 사용하게 되면, 직렬화 문제가 생겨 이와 관련된 도구나 프레임워크를 사용하지 못합니다. 하지만, 클래스 필드 형식으로 Optional을 사용함으로써 얻을 수 있는 이득은 너무나 매력적입니다. 그렇기 때문에 적절한 Trade-off을 해야 합니다.

예를 들어 객체 그래프에서 일부 또는 전체가 null일 수 있는 상황의 경우에는 Optional 적극적으로 사용하는 것이 좋습니다.

 

만약 필드에 Optional 클래스를 만들고, 직렬화 모델이 필요하다면 새로운 정적 팩토리 메서드를 만들어야 합니다.

public class Student{
	private Address adrress;

	public Optional<Address> getAddressAsOptional(){
		return Optional.ofNullable(address);
        }
}

 

 

 

4. 기타

여기서 설명하는 메서드들은 다른 클래스와 같은 기능을 하기 때문에, 따로 설명하지 않겠습니다.

◎ equals

 

◎ hashCode

 

◎ toString

 

 

 

참고

Optional (Java Platform SE 8 ) (oracle.com)

 

Optional (Java Platform SE 8 )

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value. Additional methods that depend on the presence or absence of a contained value are provided, such as orEl

docs.oracle.com

 

'JAVA > Reference' 카테고리의 다른 글

LocalDate  (0) 2021.09.18
Spliterator 인터페이스  (0) 2021.08.23