Programming/Java

Java Optional Class 기본적인 이해

Jan92 2021. 12. 6. 23:09

Optional class

java.util.Optional<T>

(Java8부터 도입)

 

 

Optional is primarily intended for use as a method return type where there is a clear need to represent "no result" and where using null is likely to cause errors. A variable whose type is Optional should never itself be null. it should always point to an Optional instance.

(Java SE 9 & JDK 9 / Optional API Note 참조)

 

옵셔널은 주로 "결과 없음"을 나타낼 필요가 분명하고 null을 사용하면 오류가 발생할 가능성이 있는 메서드의 리턴 타입으로 사용하기 위한 것입니다. 옵셔널 타입의 변수는 그 자체가 null이 되면 안 되며, 항상 Optional 인스턴스를 가리켜야 합니다.

 

=> 이처럼 옵셔널 클래스의 가장 큰 용도는 NullPointException 예외로부터 자유로워지기 위해 만들어진 클래스입니다.

 

 

'널 포인트'라는 개념을 만든 토니 호어(Tony Hoare)는 null point를 10억 불짜리 실수라고 이야기합니다.

 

 

Optional을 사용할 때 꼭 알아야 할 점은 Optional은 값을 Wrapping 하고, 다시 풀고, null일 경우에는 대체하는 함수를 호출하는 등의 오버헤드가 있기 때문에 성능이 저하될 수 있다는 점입니다. 그렇기 때문에 메서드의 반환값이 절대 null이 아니라면 Optional을 사용하지 않는 것이 성능저하가 적습니다. 

즉, Optional은 메소드의 결과가 null이 될 수 있으며, 클라이언트가 이 상황을 처리해야 할 때 사용하는 것이 좋습니다.

 

 


 

 

Member member = findByIdx();
System.out.println("Member name = " + member.getName());
// member가 null이라면 NullPointException 발생합니다.

Member member = findByIdx();
// NullPointException 발생을 막기 위해 null 검사를 해야합니다.
if (member != null) {
    System.out.println("Member name = " + member.getName());
}

 

Optional<T> 클래스는 null 값일 수도 있는 어떤 변수를 감싸주는 '래퍼 클래스(Wrapper class)'입니다. Optional 클래스는 제네릭(Generic)으로 값의 타입을 지정해줘야 하며, 여러 가지 메서드를 통해 value 값에 접근하기 때문에 바로 NullPointException 예외가 발생하지 않습니다. null 일수도 있는 값을 다루기 위한 다양한 메소드들을 제공합니다.

 

 

 

Optional 객체를 생성하는 3가지 방법

 

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

System.out.println(optional);               // Optional.empty
Systme.out.println(optional.isPresent());   // false

Optional.empty();

비어있는 Optional 객체를 생성합니다. 내부적으로는 Optional 클래스가 가진 싱글톤 인스턴스를 반환하게 되며, "비어있다"는 의미에서 null과 비슷하지만, Optional.empty()는 참조하더라도 NullPointException 예외가 발생하지 않는다는 점에서 다릅니다.

 

 

Optional.of();

Optional.of();

null이 아닌 명시된 값을 가지는 Optional 객체를 반환합니다.

만약 of() 메소드를 통해 생성된 Optional 객체에 null이 저장되면 NullPointException 예외가 발생합니다. 따라서 참조 변수의 값이 null이 될 가능성이 있다면 아래에 나오는 ofNullable() 메소드를 사용하여 Optional 객체를 생성하는 것이 좋습니다.

 

 

Optional.ofNullable();

Optional.ofNullable();

ofNullable() 메서드는 명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환하며, 명시된 값이 null이면 비어있는 Optional 객체를 반환합니다.

null 값을 저장할 수 있는 Optional 객체를 생성합니다. 따라서 인자로 넘겨지는 값이 null일지도 모르는 상황에서 사용할 수 있습니다.

 

 

 

(간략한 예시)

// 안 좋은 사용
return Optional.of(member.getName());
// member의 name이 null일 경우 NullPointException 예외가 발생합니다.

// 좋은 사용
return Optional.ofNullable(member.getName());

 

 

 


 

 

value를 가져오기 위한 Optional.get()

 

Optional<String> memberName = Optional.ofNullable("Jan");

if (memberName.isPresent()) {
    System.out.println(memberName.get());     // Jan
}

Optional 객체에 저장된 값에 접근할 수 있는 메서드입니다. 만약 Optional 객체에 저장된 값이 null이면, NoSuchElementException 예외가 발생합니다. 따라서 get() 메서드를 호출하기 전에 isPresent() 메서드를 사용하여 Optional 객체에 저장된 값이 null인지 여부를 먼저 확인한 후에 호출하는 것이 좋습니다.

 

* isPresent() 메서드는 boolean 타입으로, 저장된 값이 존재하면 true를 반환하고 값이 존재하지 않으면 false를 반환합니다.

 

 


 

 

orElse(), orElseGet(), orElseThrow()

 

위에서 본 get() 메서드를 활용하여 Optional 객체에서 저장된 값을 가지고 오기 위해서는 isPresent() 메서드를 통해 저장된 값이 있는지 확인 후 get() 메서드를 사용하여 값을 가지고 와야 했습니다. 하지만 orElse(), orElseGet(), orElseThrow() 메서드를 사용하면 저장된 값이 null일 때 null 대신에 대체할 값을 지정할 수도 있습니다.

 

 

orElse(...)

저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 값을 반환합니다.

orElse(...) 에서 ...는 Optional에 값이 있든, 없든 무조건 실행됩니다. Optional에 값이 없으면 orElse()의 인자로서 실행된 값이 반환되므로 실행하는 의미가 있지만, Optional에 값이 있으면 orElse()의 인자로서 실행되는 값은 무시되고, 버려지게 됩니다.

이처럼 ...가 새로운 객체를 생성하거나 새로운 연산을 수행하는 경우 무조건 그에 따른 비용이 추가되기 때문에 orElse() 대신 아래 orElseGet() 메서드를 사용하는 것이 좋습니다.

 

 

orElseGet()

저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 함수형 인자로 전달된 람다 표현식 또는 메서드 레퍼런스의 결과를 반환합니다.

Optional의 값이 null일 경우에만 호출되어 새로운 객체를 생성하거나 새로운 연산을 수행합니다. 따라서 발생하는 비용이 orElse() 메서드보다 저렴하며, 불필요한 문제가 발생하지 않습니다. 값이 미리 존재하지 않는 거의 대부분의 경우에 orElseGet()을 사용합니다.

 

 

orElseThrow()

저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 예외를 발생시킵니다.

 

 

 

(간략한 예시)

Optional<Member> optionalMember = Optional.empty();

optionalMember.orElse(new Member());      // 안 좋은 사용
optionalMember.orElseGet(Member::new);    // 좋은 사용

 

 


 

 

필드, 생성자, 메서드의 인자로 사용할 수 없습니다.

 

Optional은 필드에 사용될 목적으로 만들어지지 않았으며, 생성자나 메서드 인자로 사용하게 될 경우 호출할 때마다 Optional을 생성해서 인자로 전달해야 합니다. 하지만 호출되는 쪽, 즉 api나 라이브러리 메서드에서는 인자가 Optional이든, 아니든 null 체크를 하는 것이 언제나 안전하기 때문에 굳이 비용이 많이 드는 Optional을 인자로 사용하지 말고 호출되는 쪽에 null 체크 책임을 남겨두는 것이 좋습니다.

 

 

 


 

 

기본 타입을 위한 Optional

 

OptionalInt       // int getAsInt();
OptinalLong       // long getAsLong();
OptionalDouble    // double getAsDouble();

Java에서는 기본 타입을 위한 별도의 Optional 클래스를 제공하고 있습니다.

Optional에 담길 값이 int, long, double 이라면 Boxing / Unboxing이 발생하는 Optional<Integer>, Optional<Long>, Optional<Double>을 사용하지 말고 OptionalInt, OptionalLong, OptionalDouble을 사용하는 것이 좋습니다.

 

 

 

 

< 참고 자료 >

 

Java Optional 바르게 쓰기

Java Optional 바르게 쓰기Brian Goetz는 스택오버플로우에서 Optional을 만든 의도에 대해 다음과 같이 말했다. … it was not to be a general purpose Maybe type, as much as many people would have liked us to do so. Our intention w

homoefficio.github.io

 

 

 

[Java] Optional이란?

이번에는 Java8부터 지원하는 Optional 클래스에 대해 알아보도록 하겠습니다. 1. Optional이란? [ NPE(NullPointerException) ] 개발을 할 때 가장 많이 발생하는 예외 중 하나가 바로 NPE(NullPointerException)..

mangkyu.tistory.com

 

 

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com