본문 바로가기
Programming/Java

[자바/Java] Optional 개념 및 사용법

by 코딩하는 랄로 2023. 10. 15.
728x90

Opional 이란

자바의 가장 큰 고질적인 문제라고 하면 null을 체크해줘야 하는 문제를 꼽을 수 있다. NPE(NullPointerException)을 피하기 위해서는 꼭 null 여부를 체크해줘야 하기 때문에 코드가 길어지고 복잡해진다는 문제점이 발생하는 것이다.

 

이러한 문제를 보완하고자 Java8에서는 Optional<T> 클래스를 통해 NPE를 방지할 수 있도록 도와준다. Optional<T>는 null이 올 수 있는 값을 감싸는 Wrapper 클래스로, 참조하더라도 NPE가 발생하지 않도록 도와준다.

 

Optional 클래스는 아래와 같은 value에 값을 저장하기 때문에 값이 null이더라도 바로 NPE가 발생하지 않으며, 클래스이기 때문에 각종 메소드를 제공해준다.

 

class Optional<T> {
    T value;
    ...
}

 

 

 

Optional 객체 생성

Optional.empty()

Optional은 Wrapper 클래스이기 때문에 값이 없을 수도 있는데, 이때는 Optional.empty()로 생성할 수 있다.

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

 

Optional 클래스는 내부에서 static 변수로 EMPTY 객체를 미리 생성해서 가지고 있어, 여러개의 빈 객체를 생성하여도 1개의 Empty 객체를 공유함으로써 메모리를 절약한다.

public final class Optional<T> {

    private static final Optional<?> EMPTY = new Optional<>();
    private final T value;
    
    private Optional() {
        this.value = null;
    }
    
}

 

 

Optional.of(value)

해당 생성 메소드는 value의 값이 절대 null이 아닐 경우 사용하는 메소드이다. 그렇기 때문에 null 값이 넘어가게 되면 NPE가 발생한다.

Optional<String> optional = Optional.of("Hello");

 

 

Optional.ofNullable(value)

value값이 null값이 될 수도 있고 아닐 수도 있는 경우에 Optional 객체를 생성할 수 있는 메소드이다. 

Optional<String> opt1 = Optional.ofNullable("hello");
Optional<String> opt2 = Optional.ofNullable(null);

 

 

기본 타입을 위한 Optional 클래스

Optional 클래스도 참조 타입을 대상으로 하는 클래스이기 때문에 기본 타입인 int, long, double을 사용하면 boxing, unboxing이 발생하여 추가적인 작업이 발생한다.

 

이것을 막고자, OptionalInt, OptionalLong, OptionalDouble 클래스 또한 지원하고 있다.

//Optional 사용
Optional<Integer> optInteger1 = Optional.of(10);  // boxing 발생O
Optional<Long> optLong1 = Optional.of(10L);  // boxing 발생O
Optional<Double> optDouble1 = Optional.of(10.12);  // boxing 발생O

//OptionalInt, OptionalLong, OptionalDouble
OptionalInt optInteger2 = OptionalInt.of(10);  // boxing 발생X
OptionalLong optLong2 = OptionalLong.of(10L);  // boxing 발생X
OptionalDouble optDouble2 = OptionalDouble.of(10.12);  // boxing 발생x

 

 

 

Optional의 주요 메소드

Optional은 클래스이기 때문에 여러 메소드를 제공하고 있고, 그 중 주요 메소드로는 아래와 같다.

 

  • boolean isPresent()
  • void ifPresent(Consumer<T>)
  • Optional<T> filter(Predicate<T>)
  • Optional<U> map(Function<T, U>)
  • T get()
  • T orElse(T)
  • T orElseGet(Supplier<T>)
  • T orElseThrow(Supplier<T>)

 

isPresent()

내부 객체가 null인지 아닌지 확인한다. null이면 false를 반환한다.

public class Main {

    public static void main(String[] args) {

        Optional<String> opt1 = Optional.isEmpty();
        Optional<String> opt2 = Optional.of("hello");

        System.out.println(opt1.isPresent()); //false
        System.out.println(opt2.isPresent()); //true
        
    }
}

 

 

ifPresent(Consumer<T>)

Consumer<T>는 void 추상 메서드를 갖고 있다. null 이 아닐 때만 실행된다.

public class Main {

    public static void main(String[] args) {

        Optional<String> opt1 = Optional.isEmpty();
        Optional<String> opt2 = Optional.of("hello");

        opt1.ifPresent(System.out::println); //실행 안됨
        opt2.ifPresent(System.out::println); //hello
        
    }
}

 

 

Optional<T> filter(Predicate<T>)

내부 객체가 해당 조건을 만족하는지 확인하고 Optional<T>를 반환하는 메소드이다. Predicate 메소드는 boolean을 리턴한다. 

public class Main {

    public static void main(String[] args) {

        Optional<String> opt = Optional.of("hello");

        Optional<String> opt2 = opt.filter(s -> s.equals("hello"));
        opt2.ifPresent(System.out::println); //hello
        
    }
}

 

 

Optional<U> map(Function<T, U>)

stream과 같다. 내부 객체를 반환하는 용도로 사용한다. Optional<U>를 반환한다.

public class Main {

    public static void main(String[] args) {

        Optional<String> opt1 = Optional.of("hello");

        Optional<String> opt2 = opt1.map(s -> s + " Optional");
        opt2.ifPresent(System.out::println); //hello Optional
        
    }
}

 

 

T get()

내부 객체를 반환한다. 다만 내부 객체가 null이면 NPE가 발생한다. 그렇기 때문에 null 이 아닌 확실한 경우에만 사용해야 한다.

public class Main {

    public static void main(String[] args) {

        Optional<String> opt1 = Optional.of("hello");
        Optional<String> opt2 = Optional.isEmpty();

        System.out.println(opt1.get()); //hello
        System.out.println(opt2.get()); //NPE 발생
        
    }
}

 

 

T orElse(T)

내부 객체를 반환한다. 내부 객체가 null이면 인자로 들어간 기본값을 반환한다.

public class Main {

    public static void main(String[] args) {

        Optional<String> opt1 = Optional.of("hello");
        Optional<String> opt2 = Optional.isEmpty();

        System.out.println(opt1.orElse("optional")); //hello
        System.out.println(opt2.orElse("optional")); //optional
        
    }
}

 

 

 

T orElseGet(Supplier<T>)

orElse와 동일한데 orElse가 기본값 레퍼런스를 인자로 받는다면 orElseGet은 내부 객체가 null일 때 기본값을 반환할 객체를 인자로 받는다.

public class Main {

    public static void main(String[] args) {

        Optional<String> opt1 = Optional.of("hello");
        Optional<String> opt2 = Optional.isEmpty();

        String opt3 = opt1.orElseGet(() -> new String("hello")); //opt1.orElse("hello")
        String opt4 = opt2.orElseGet(() -> new String("hello"));
        
        System.out.println(opt3); //hello
        System.out.println(opt4); //hello
        
    }
}

 

 

T orElseThrow(Supplier<U>)

내부 객체가 null이면 인자로 전달받은 예외를 발생시킨다.

public class Main {

    public static void main(String[] args) {

        Optional<String> opt1 = Optional.of("hello");
        Optional<String> opt2 = Optional.isEmpty();
        
        System.out.println(opt1.orElseThrow()); //hello
        System.out.println(opt2.orElseThrow()); //throw new NullpointerException();
        
    }
}
728x90