티스토리 뷰

Programming/Java

Java Optional 클래스

junojuno 2022. 1. 11. 21:03

람다식과 Optional, Stream은 정말 한 세트같다는 느낌이 든다. 그래서 현재 람다식 공부를 깊게 하지 않은 나에게, 람다식을 필수적으로 공부해서 잘 사용할 수 있게 만들어야 겠다는 생각이 든다.

 

먼저 Optional 클래스가 Java 8 버전부터 등장했는데 왜 Optional이라는 클래스가 추가 되었으며, 이 클래스가 하는 역할이 무엇이라서 어떤 경우에 쓸수 있는지를 알아보고자 한다.

 

1. Optional 클래스란

Optional이라는 의미에서도 알수 있다싶이, "존재 할수도 있지만 안 할 수도 있는 객체". 즉 " Null이 될수도 있는 객체"를 감싸고 있는 Wrapper 클래스를 말한다.

Optional 클래스

Optional 클래스를 살펴 본다면, Optional<T>는 제네릭 클래스로 T 타입의 객체를 감싸는 래퍼 클래스이다.

따라서 Optional 타입의 객체는 모든 타입의 참조변수를 담을 수 있다.

 

2. Optional 클래스를 왜 사용할까

Optional 클래스를 다루는 이유는 null 값을 다루기 위해서이다. 자바8 이전에는 null인 객체를 다룰때, 자바에서 NullPointerException이 발생하기 때문에, 반환하는 결과가 null인지 매번 if문으로 체크했어야 했었다.

 

문제 1. 런타임에NPE 예외 발생할 수 있다.
문제 2. NPE를 방어하기 위해서 null 체크 때문에 코드 가독성과 유지 보수성이 떨어진다

위의 문제를 해결하기 위해서 함수형 언어에서 해법을 찾아 적용한것이 바로 Optional 클래스라고 한다.

/* 주문 */
public class Order {
	private Long id;
	private Date date;
	private Member member;
	// getters & setters
}

/* 회원 */
public class Member {
	private Long id;
	private String name;
	private Address address;
	// getters & setters
}

/* 주소 */
public class Address {
	private String street;
	private String city;
	private String zipcode;
	// getters & setters
}

위와 같은 코드가 나와있을때, '어떤 주문을 한 회원이 어느 도시에 살고 있는지'를 알아 내기 위해서 다음과 같은 메소드가 있다고 할때,

/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
	return order.getMember().getAddress().getCity();
}

NullPointerException의 위험 포인트가 정말 많은것을 알수 있다.

order가 null일 경우, order.getMember()로 얻은 Member가 null일경우, 게터로 얻은 Address가 null일경우, 그리고 반환된 String city값이 null일 경우. 이럴 경우 null처리를 if문을 통해서 모두 다음과 같이 처리해 주었어야 했다.

public String getCityOfMemberFromOrder(Order order) {
	if (order != null) {
		Member member = order.getMember();
		if (member != null) {
			Address address = member.getAddress();
			if (address != null) {
				String city = address.getCity();
				if (city != null) {
					return city;
				}
			}
		}
	}
	return "Seoul"; // default
}

이런 코드를 Optional을 이용하면 다음과같이 작성이 가능하다.

/* 주문을 한 회원이 살고 있는 도시를 반환한다 */
public String getCityOfMemberFromOrder(Order order) {
	return Optional.ofNullable(order)
			.map(Order::getMember)
			.map(Member::getAddress)
			.map(Address::getCity)
			.orElse("Seoul");
}

훨씬 더 직관적이며 간결한 코드가 되었다. 이를 통해 다음 과같이 Optional을 사용하면 얻을 수 있는 장점을 알 수 있다.

 

3. Optional을 사용함으로서 얻는 장점

  • NPE를 유발할 수 있는 null을 직접 다루지 않아도 된다
  • if문으로 null 체크를 하지 않고 Optional에 정의된 메소드를 통해서 간단히 처리할 수 있다.
  • 명시적으로 해당 변수가 null일 수도 있다는 가능성을 표현 가능하다.

그럼 Optional에 대한 장점을 알았고, 어떤 경우에 사용하는지를 알았으니 어떻게 사용할 수 있을까

 

4. Optional 생성법

Optional 객체를 생성할 때에는 of() 또는 ofNullable()을 사용한다.

String str = "HWANG JUNHO";
Optional<String> str1 = Optional.of(str);
Optional<String> str2 = Optional.of("Junho Hwang");

참조변수의 값이 null일 가능성이 있으면 of()대신 ofNullable()을 사용해야 한다. 왜냐하면 of()는 매개변수의 값이 null일경우 NullPointerException을 발생하기 때문이다.

Optional<Object> obj1 = Optional.of(null); 	// NPE 발생
Optional<Object> obj2 = Optional.ofNullable(null); // ERROR 발생 X

Optional<T> 타입의 참조변수를 기본값으로 초기화 할때에는 empty()를 사용하는 것이 바람직하다. null로 초기화 하는것이 가능은 하지만 null을 직접적으로 다루므로 위험하기 때문이다.

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

5. Optional 객체의 값 가져오기

Optional 객체에 저장된 값을 가져올 때에는 get()을 사용한다.

null일 경우 NPE가 발생하기 때문에 이를 대비해서 orElse()로 대체할 수 있다. 

orElse의 변형으로 orElseGet()이나 orElseThrow()가 있다.

Optional 클래스의 get() 메소드
Optional 클래스의 orElse() 메소드

  • .get() : Optional객체에 저장되어 있는 value를 가져온다. 비어있는 Optional 개체에 대해서 NoSuchElementException을 던진다.
  • .orElse(T other) : 비어 있는 Optional 객체면 other 객체를 반환한다.
  • .orElseGet(Supplier other) : 비어 있는 Optional 객체에 대해서 null을 대체할 값을 반환하는 람다식을 지정할 수 있다.
  • .orElseThrow(Supplier exceptionSupplier) : null일경우 지정한 예외를 던진다.
 String str = "HWANG JUNHO";
Optional<String> str1 = Optional.of(str);
System.out.println(str1.get()); 	// HWANG JUNHO

Optional<String> optional1 = Optional.empty();	
System.out.println(optional1.orElse("No Value"));	// No Value
System.out.println(optional1.orElseGet(() -> new String("No Value")));	// No Value
System.out.println(optional1.orElseThrow(NoSuchElementException::new));	// NoSuchElementException 발생

 

6. 그외 Optional 클래스에서 사용할 수 있는 메소드

  • isPresent()
  • ifPresent()
  • Stream과 같은 map(), filter(), flatmap()

(1) isPresent()와 ifPresent()

Optional 클래스 안의 isPresent()
Optional 클래스 안의 ifPresent()

isPresent()는 Optional 객체의 값이 null이 아니면  true, null이면 false를 반환한다.

ifPresent()는 값이 null이 아니면 주어진 람다식을 실행하고, null이면 아무 것도 하지 않는다. 이 ifPresent()는 Stream클래스에 정의된 메소드 중에서 Optional을 리턴하는 것들은 findAny()나 findFirst()와 같은 최종연산과 어울린다. 

 

예) ... 첫번째 값을 출력해라

(...).findFirst().ifPresent(System.out::println)

 

예)

// 1. 
if (str != null){
    System.out.println(str);
}
// 2.
if (Optional.ofNullable(str).isPresent()){
    System.out.println(str);
}
// 3.
Optional.ofNullable(str).ifPresent(System.out::println);

위 코드를 1 -> 2 -> 3과 같이 변환 가능하다.

 

7. OptionalInt, OptionalLong, OptionalDouble

IntStream과 같은 기본형 스트림에는 Optional 또한 기본형으로 하는 OptionalInt, OptionalLong, OptionalDouble을 반환한다.

반환타입이 Optional<T>가 아니라는 것을 제외하고는 Stream에 정의된 것과 비슷하다.

기본형 Optional에 저장된 값을 꺼낼대에는 사용하는 메소드 이름이 또 조금 다르다.

OptionalInt 안의 getAsInt()메소드

IntStream에 정의된 findAny()나 findFirst()같은 경우 리턴 타입이 OptionalInt이다.

OptionalInt, OptionalLong, OptionalDouble 같은 경우 get() 메소드가 위의 getAsInt와 같이 getAs타입형()이다.

 

* OptionalInt와 Optional의 차이

OptionalInt optInt1  = OptionalInt.of(0);   // 0을 저장 isPresent : true
OptionalInt optInt2  = OptionalInt.empty(); // 빈 객체를 생성 isPresent : false

System.out.println(optInt1.isPresent());   // true
System.out.println(optInt2.isPresent());   // false
System.out.println(optInt1.getAsInt());  // 0
System.out.println(optInt1.equals(optInt2)); // false
Optional<String> optional = Optional.ofNullable(null);
Optional<String> optional1 = Optional.empty();

 System.out.println(optional.isPresent());	//false
System.out.println(optional.isPresent());	//false
System.out.println(optional.equals(optional1));	// true

 

Reference: 

 - 자바의 정석(도우출판)

 - https://www.daleseo.com/java8-optional-effective/

 - https://www.daleseo.com/java8-optional-after/

 - https://www.daleseo.com/java8-optional-after/

(매우 영감을 받은 블로그 : https://www.daleseo.com/)

 

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday