-
OptionalJAVA/Optional 2023. 6. 22. 14:01728x90
개요
자바를 하면서 우리는 수없이 많은 if문을 사용한다.
그중 일부는 들어온 변수가 null인지 아닌지를 확인하여 프로그램의 흐름에 영향을 주기 위해서나,
프로그램의 비정상적인 작동을 막기 위해서 사용한다.
간단하게 코드로 확인해보자
// User 클래스 파일 public class User { private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } } //... // Address 클래스 파일 public class Address { private String street; public Address(){} public Address(String street) { this.street = street; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } @Override public String toString() { return String.format("[Address: street=%s]", this.street); } }
User 클래스는 Address 객체를 필드로 가지고 있으며, getter와 setter로 변경가능하다.
위의 상황에서 우리가 null을 처리하는 경우는 대개 뭐가 있을 수 있을까?
상황
if (user != null) { Address address = user.getAddress(); if (address != null) { String street = address.getStreet(); if (street != null) { System.out.println(street.toUpperCase()); } } }
객체안의 객체를 조회하는 상황. 그 객체 안의 프로퍼티에 접근하는 상황. 두가지 상황 모두 널 여부를 확인해야한다.
그렇지 않으면, null값 뒤에 점을 찍게 되므로, 우리가 자주보는 NPE(NullPointerException)이 발생하게 된다.
우리는 다양한 상황에서 null값을 마주하며, 이를 간편하게 처리할 방법이 필요하다.
개념
Optional<T>
- null 체크 로직의 복잡함을 해결하기 위한 객체
- Java8 에서 등장
- null 값을 담을 수 있는 값을 감싸는 래퍼객체
String str = "hello"; Optional<String> o1, o2, o3; // String 타입을 담을 수 있는 그릇. // 1. Optional.of(value) 를 사용하여 생성 o1 = Optional.of(str); // Optional.of > static 메소드에 String타입 매개변수 => 그릇에 담는다 System.out.println(o1); System.out.println(o1.get()); // Optional 내부 객체리턴 // Optional.of(null); // value 가 null 이면 NPE System.out.println(); // 2. Optional.ofNullable(value) // value 가 null이면 empty Optional 객체 반환 o2 = Optional.ofNullable((str)); System.out.println(o2); o2 = Optional.ofNullable(null); // null을 담고 있는 Optional 객체 System.out.println(o2); // Optional.empty // System.out.println(o2.get()); // NoSuchElementException System.out.println(); // 3.Optional.empty() empty Optional 객체 반환 o3 = Optional.empty(); System.out.println(o3); // Optional.empty System.out.println(); // OptionalInt, OptionalDouble, OptionalLong System.out.println("OptionalInt, OptionalDouble, OptionalLong"); Optional<Integer> optInteger = Optional.of(10); // auto-boxing OptionalInt optInt = OptionalInt.of(10); // auto-boxing 안해도됨. 인트를 담도록 만들엇자나 OptionalLong optLong = OptionalLong.of(1234L); OptionalDouble optDouble = OptionalDouble.of(3.14); // int, double long만 제공한 이유? >> 적절하게 형변환 해서 담으라고~✨✨ System.out.println(optInteger.get()); // auto-unboxing 발생 System.out.println(optInt.getAsInt()); // auto-unboxing 발생 안함 >> 성능적으로 더 좋다 System.out.println(optDouble.getAsDouble()); System.out.println(optLong.getAsLong());
기본적으로 Optional 생성 메소드로는
- Optional.of(값)
- Optional.ofNullable(값)
- Optional.empty()
로 만들 수 있다. 또한 primitive 타입에 auto-boxing과 같은 불필요한 작업을 덜어주기 위해,
- OptionalInt
- OptionLong
- OptionDouble
등이 제공된다. 이들 3가지로 적절히 형변환을 해서 다양한 primitive 타입들을 다룰 수 있다.
활용
앞서 우리가 봤던, User 클래스와 Address 클래스를 가지고 여러 작업을 해보자.
public class Test { public static void main(String[] args) { Address addr1 = new Address("서울시"); Address addr2 = new Address(); // address 까지 있음 User user1 = new User(); user1.setAddress(addr1); // Address 객체는 있으나, String addr이 없는 user2 User user2 = new User(); user2.setAddress(addr2); // Address 객체 자체가 없는 user User user3 = new User(); // null; User user4 = null; Optional<User> o1, o2, o3, o4; o1 = Optional.ofNullable(user1); o2 = Optional.ofNullable(user2); o3 = Optional.ofNullable(user3); o4 = Optional.ofNullable(user4); System.out.println("o1 = " + o1); System.out.println("o2 = " + o2); System.out.println("o3 = " + o3); System.out.println("o4 = " + o4); } }
결과
o1 = Optional[common.User@4dd8dc3]
o2 = Optional[common.User@6d03e736]
o3 = Optional[common.User@568db2f2]
o4 = Optional.empty◎ null값이 들어있는지 아닌지 체크하는 메소드
System.out.println(o1.isPresent()); System.out.println(o1.isEmpty()); System.out.println(o4.isPresent()); System.out.println(o4.isEmpty());
결과
true >> Optional<T> T타입 값이 들어있음
false
false
true >> null 값이 들어있음우리가 if절로 존재하면 ~, 존재하지 않으면~ 이런 구문을 한 라인으로 해결할 수 있는 메서드도 제공한다.
여기서 필요한 argument는 consumer 함수형 인터페이스와, runnable 함수형 인터페이스가 필요하다.
즉, 존재하면, consumer(인자를 받아 void를 리턴하는 메소드)
존재하지 않다면, runnable(인자없고, void를 리턴하는 메소드)
를 실행해주는 메소드이다.
System.out.println("ifPresent : 존재한다면? consumer<T> 동작"); o1.ifPresent((s) -> System.out.println("주소 : " + s)); o4.ifPresent(s -> System.out.println("s = " + s)); o4.ifPresentOrElse (s -> System.out.println("s = " + s), () -> System.out.println("아무것도 없습니다~"));
결과
ifPresent : 존재한다면? consumer<T> 동작
(((값이 없었지만, error를 발생시키지 않는다)))
주소 : common.User@4dd8dc3
아무것도 없습니다~◎ null값이 있으면 다른 객체를 넣어주고, 값이 있다면 해당 값을 리턴하는 메서드
System.out.println("orElse(T)"); User result1 = o1.orElse(new User(new Address("없다!"))); User result2 = o4.orElse(new User(new Address("없다!"))); System.out.println("result1 = " + result1); System.out.println("result2 = " + result2);
결과
orElse(T)
result1 = user의 주소는 : 서울시입니다
result2 = user의 주소는 : 없다!입니다o4에는 실제로 null이 들어있었기 때문에, Optional<User> o4 = null; 이므로
새로운 User 타입을 정의할 수 있다.
때문에 결과도 내가 Optional객체 내부에 아무것도 없을 경우 새로운 객체를 만들어 넣어주었기 때문에,
null이 찍히지 않았다.
지금까지 본것처럼 함수형인터페이스와의 조합으로 다양한 상황에 맞는 null값에 대처하는 다양한 메서드들이 존재한다.
orElseGet(Supplier<T>) : supplier 함수형 인터페이스를 통해 없는 경우, 새로운 것을 제공받는 방법도 있다.
orElseThrow(Supplier<? extends Throwable>) : null 일경우 예외를 발생시키는 방법도 존재한다.
또한 Stream에서 주로 쓰이는 map과 filter연산도 가능하다.
JAVA는 Optional 객체를 사용해서 우리가 null을 다룰 때 흔히 사용하던 if절의 많은 부분들을 대체할수 있게 만들어놨다.