ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Optional
    JAVA/Optional 2023. 6. 22. 14:01
    728x90

    개요

    자바를 하면서 우리는 수없이 많은 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절의 많은 부분들을 대체할수 있게 만들어놨다.

     

Designed by Tistory.