☕ Java/이론

[Java] Optional 클래스

an2z 2022. 2. 7. 18:18

Optional 클래스

  • Optional<T>클래스는 T타입의 객체를 포장해주는 일종의 래퍼 클래스이다.
  • Optional 클래스는 if~else 구문을 대신할 수 있다.
  • 조건에 따른 코드의 흐름이 나눠지는 것을 막을 수 있어 코드를 더 깔끔하게 구성할 수 있다.
  • Optional 객체를 사용하면 예상치 못한 NullPointerException 예외를 단순하게 처리할 수 있다. 즉, 복잡한 조건문 없이도 null값으로 인해 발생하는 예외를 처리할 수 있게 된다.

 

💡 NullpointerException 예외 처리

  • 클래스를 정의할 때 인스턴스 변수는 null이 되지 않게 유효한 값을 가지고 있도록 초기화 해주는 것이 좋지만, null을 허용해야 하는 경우도 있다.
  • 이때 null 가능성에 대비하는 코드의 작성은 번거롭고 코드도 복잡하다. 이를 해결하기 위해 자바 8에서 Optional 클래스가 등장하게 되었다.
class Friend { // 친구 정보
    String name; // 친구 이름
    Company cmp; // 친구 회사 (null일수 있음)

    public Friend(String n, Company c) {
        name = n;
        cmp = c;
    }

    public String getName() { return name; }
    public Company getCmp() { return cmp; }
}

class Company { // 친구 정보에 속하는 회사 정보
    String cName; // 회사 이름
    CompInfo cInfo; // 회사 정보 (null일수 있음)

    public Company(String cn, CompInfo ci) {
        cName = cn;
        cInfo = ci;
    }

    public String getCName() { return cName; }
    public CompInfo getCInfo() { return cInfo; }
}

class CompInfo { // 회사 정보에 속하는 회사 연락처
    String phone; // 회사 번호 (null일수 있음)
    String adrs; // 회사 주소 (null일수 있음)

    public CompInfo(String ph, String ad) {
        phone = ph;
        adrs = ad;
    }

    public String getPhone() { return phone; }
    public String getAdrs() { return adrs; }
}

class Optional_NullPointerCase {
    public static void showCompAddr(Friend f) { // 친구가 다니는 회사 주소 출력
        String addr = null;

        if (f != null) { // 인자로 전달된 것이 null 일 수도 있으니
            Company com = f.getCmp();
            if (com != null) { // 회사 정보가 없을 수도 있으니
                CompInfo info = com.getCInfo();
                if (info != null) // 회사 연락처 정보가 없을 수도 있으니
                    addr = info.getAdrs();
            }
        }

        if (addr != null) // 주소 정보를 얻지 못할수도 있으니
            System.out.println(addr);
        else
            System.out.println("회사 주소 정보가 없습니다.");
    }

    public static void main(String[] args) {
        CompInfo ci = new CompInfo("321-444-577", "Republic of Korea");
        Company cp = new Company("자취 컴퍼니", ci);
        Friend frn = new Friend("wuga", cp);
        showCompAddr(frn); // 친구가 다니는 회사의 주소 출력
    }
}
Republic of Korea

 

💡 if ~ else문 대신하기

// 기존 코드
class CompInfo { // 회사 정보
    String phone; // null일 수 있음
    String adrs;  // null일 수 있음

    public CompInfo(String ph, String ad) {
        phone = ph;
        adrs = ad;
    }

    public String getPhone() { return phone; }
    public String getAdrs() { return adrs; }
}

class Optional_IfElse {
    public static void main(String[] args) {
        CompInfo ci = new CompInfo(null, "Republic of Korea");
        String phone;
        String addr;

        if (ci.phone != null)
            phone = ci.getPhone();
        else
            phone = "전화번호 정보가 없습니다.";

        if (ci.adrs != null)
            addr = ci.getAdrs();
        else
            addr = "주소 정보가 없습니다.";

        System.out.println(phone); // 전화번호 정보가 없습니다.
        System.out.println(addr); // Republic of Korea
    }
}
// 개선 후 코드
class CompInfo { // 회사 정보
    String phone; // null일 수 있음
    String adrs;  // null일 수 있음

    public CompInfo(String ph, String ad) {
        phone = ph;
        adrs = ad;
    }

    public String getPhone() { return phone; }
    public String getAdrs() { return adrs; }
}

class Optional_IfElse_MapOrElse {
    public static void main(String[] args) {
        Optional<CompInfo> ci = Optional.of(new CompInfo(null, "Republic of Korea"));

        String phone = ci.map(c -> c.getPhone()).orElse("전화번호 정보가 없습니다.");
        String addr = ci.map(c -> c.getAdrs()).orElse("주소 정보가 없습니다.");

        System.out.println(phone); // 전화번호 정보가 없습니다.
        System.out.println(addr); // Republic of Korea
    }
}

 

 

Optional 클래스의 사용 방법

Optional 객체 생성

  • of() 메소드나 ofNullable() 메소드를 사용해 Optional 객체를 생성할 수 있다.
메소드 설명
of() • null이 아닌 명시된 값을 가지는 Optional 객체를 반환
• of 메소드를 통해 생성된 Optional 객체에 null이 저장되면 NullPointerException 예외 발생
ofNullable() • 명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환
• 명시된 값이 null이면 비어있는 Optional 객체를 반환
• 따라서 만약 참조 변수의 값이 null이 될 가능성이 있다면 ofNullable 메소드를 사용해 Optional 객체를
   생성하는 것이 좋다.
empty() • 비어있는 Optional 객체를 반환

* java.utill 패키지에 묶여 있음

Optional 객체 접근

  • get() 메소드를 호출하기 전에 isPresent() 메소드를 사용해 Optional 객체에 저장된 값이 null인지 아닌지 먼저 확인 후 호출하는 것이 좋다.
메소드 설명
get() • Optional 객체에 저장된 값을 반환
• Optional 객체에 저장된 값이 null이면, NoSuchElementException 예외 발생
boolean isPresent() • Optional 객체에 저장된 값이 존재하면 true, 값이 존재하지 않으면 flase 반환
void ifPresent(Consumer<? super T> consumer) • Optional 객체에 저장된 값이 존재하면, 그 값을 인자로 전달하며
   accept 메소드를 호출
• 값이 존재하지 않으면 아무일도 일어나지 않음
class Optional_makeInstance {
    public static void main(String[] args) {
        // String 인스턴스를 저장한 Optional 인스턴스 생성
        Optional<String> os1 = Optional.of(new String("Red"));
        Optional<String> os2 = Optional.of(new String("blue"));

        // Optional 인스턴스에 접근
        if(os1.isPresent())
            System.out.println(os1.get());		
        if(os2.isPresent())
            System.out.println(os2.get());

        os1.ifPresent(s -> System.out.println(s)); //람다식 버전
        os2.ifPresent(System.out::println); // 메소드 참조 버전
    }
}
Red
blue
Red
blue

위 예제를 보면 ifPresent() 메소드 사용을 통해 if문을 생략할 수 있음을 알 수 있다.

 

 

Optional 클래스의 메소드

앞서 Optional 클래스를 사용하여 if~else 구문을 사용하지 않을 수 있다 하였다.

이에 필요한 Optional 클래스의 메소드를 알아보자.

 

map()

public <U> Optional<U> map(Function<? super T, ? extends U> mapper)
  • Function 함수형 인터페이스의 apply(T t) 추상메소드를 구현하는 람다식을 map 호출시 인자로 전달해야 한다.
  • map 메소드는 람다식을 적용해 만들어진 결과물을 Optional 인스턴스에 담아서 반환한다.
class Optional_map {
    public static void main(String[] args) {
        Optional<String> os1 = Optional.of("Optional String");
        Optional<String> os2 = os1.map(s -> s.toUpperCase());
        System.out.println(os2.get());

        Optional<String> os3 = os1.map(s -> s.replace(' ', '_'))
                                  .map(s -> s.toLowerCase());
        System.out.println(os3.get());
    }
}
OPTIONAL STRING
optional_string

 

flatMap( )

  • map은 Optional로 감싸서 반환한다면, flatMap은 감싸지 않고 내용물 자체를 반환한다.
  • 필요시 직접 Optional로 감싸줘야 한다.
  • flatMap() 메소드는 Optional 인스턴스를 클래스의 멤버로 두는 경우 유용하게 사용할 수 있다.
Optional<String> os1 = Optional.of("Optional String");    
Optional<String> os2 = os1.flatMap(s -> Optional.of(s.toLowerCase())); // optional string
더보기
class CompInfo { // 회사 정보
    Optional<String> phone; // null일 수 있음
    Optional<String> adrs;  // null일 수 있음

    public CompInfo(Optional<String> ph, Optional<String> ad) {
        phone = ph;
        adrs = ad;
    }

    public Optional<String> getPhone() { return phone; }
    public Optional<String> getAdrs() { return adrs; }
}

class Optional_FlatMapElse {
    public static void main(String[] args) {
        Optional<CompInfo> ci = Optional.of(new CompInfo(Optional.ofNullable(null), Optional.of("Republic of Korea")));

        String phone = ci.flatMap(c -> c.getPhone()).orElse("전화번호 정보가 없습니다.");
        String addr = ci.flatMap(c -> c.getAdrs()).orElse("주소 정보가 없습니다.");

        System.out.println(phone);
        System.out.println(addr);
    }
}
전화번호 정보가 없습니다.
Republic of Korea

 

orElse()

  • Optional 객체에 저장된 내용물이 있으면 내용물을 반환, 반환할 내용물이 없으면 인자로 전달된 인스턴스를 대신 반환한다.
  • get()메소드와 동일하게 Optional 객체에 저장된 내용물을 반환하지만, 반환할 대상을 지정해줄 수 있다는 점에서 차이가 있다.
class Optional_orElse {
    public static void main(String[] args) {
        Optional<String> os1 = Optional.empty(); // 비어있는 Optional 객체 생성
        Optional<String> os2 = Optional.of("Hi Optional.");

        String s1 = os1.map(s->s.toString())
                       .orElse("Empty");
        String s2 = os2.map(s->s.toString())
                       .orElse("Empty");

        System.out.println(s1);
        System.out.println(s2);
    }
}
Empty
Hi Optional.

 

 

기본 타입의 Optional 클래스

자바에서는 기본 자료형을 위한 별도의 Optional 클래스를 제공하고 있다.

이러한 클래스는 반환 타입이 Optional<T> 타입이 아닌 해당 기본 타입이라는 점에서 차이가 있다.

  • OptionalInt 클래스
  • OptionalLong 클래스
  • OptionalDouble 클래스

 

Optional과 Optional__와의 차이점

  • 기본자료형을 대상으로한 Optional__을 사용해 객체를 생성하면 오토 박싱, 오토 언박싱을 생략한 코드를 작성할 수 있다.
  • Optional__클래스들은 Optional 클래스보다 기능이 제한적이다.
    • Optional__클래스들에는 map과 flatMap 메소드가 정의되어 있지 않다.
더보기
// Optional<T>
class OptionalInt_Base1 {
    public static void main(String[] args) {
        Optional<Integer> oi1 = Optional.of(3); // 오토 박싱
        Optional<Integer> oi2 = Optional.empty();

        System.out.print("[step 1] : ");
        oi1.ifPresent(i -> System.out.print(i + " "));
        oi2.ifPresent(i -> System.out.print(i));
        System.out.println();

        System.out.print("[step 2] : ");
        System.out.print(oi1.orElse(100) + " ");
        System.out.print(oi2.orElse(100) + " ");
        System.out.println();
    }
}
[step 1] : 3 
[step 2] : 3 100
// OptionalInt 적용
class OptionalInt_Base2 {
    public static void main(String[] args) {
        OptionalInt oi1 = OptionalInt.of(3);
        OptionalInt oi2 = OptionalInt.empty();

        System.out.print("[step 1] : ");
        oi1.ifPresent(i -> System.out.print(i + " "));
        oi2.ifPresent(i -> System.out.print(i));
        System.out.println();

        System.out.print("[step 2] : ");
        System.out.print(oi1.orElse(100) + " ");
        System.out.print(oi2.orElse(100) + " ");
        System.out.println();
    }
}
[step 1] : 3 
[step 2] : 3 100

 


Reference

http://www.tcpschool.com/java/java_stream_optional