Java

[Java8] Optional

나프초 2024. 8. 1. 21:29

Optioinal이란?

  • java8에 추가된 새로운 인터페이스
  • 비어있을 수도 있고 무엇을 담을 수도 있는 컨테이너 인스턴스의 타입

 

public class Birthday {	//생일 정보를 포함하는 클래스

	private String year;
    private String month;
    private String day;
    
    // ~~ getter, setter code ~~
    
}

public class MyName {	//이름, 성별, 생일 정보를 포함하는 클래스

	private Integer id;
    private String name;
    private boolean isMale;
    public Birthday birthday;	//사용자 정의 BirthDay 타입은 생성자에서 세팅해주지 않는다.
    
    public MyName(Integer id, String name, boolean isMale){
    	this.id = id;
    	this.name = name;
    	this.isMale = isMale;
    }
    
    // ~~ getter, setter code ~~
    
}
public class App {

	public static void main(String[] args) {
    	
        MyName mn = new MyName(1, "kevin", true);
        String birthMonth = mn.getBirthday().getMonth();
        
        System.out.println(birthMonth);	//이 때 NullPointerException이 발생한다.
    }
}

 

코드를 살펴보자.

 

MyName 클래스는 Birthday 클래스를 포함하고 있지만, 생성자에 Birthday를 파라미터로 받지 않는다.

MyName 클래스가 생성될 때 Birthday 객체는 Null 상태라는 뜻이다.

 

그 상태에서 Birthday 클래스의 Month 값을 가져오려고 하면 (= Null을 참조하여 어떤 메소드로 호출하려고 하면) null pointer exception이 발생한다.

 

// 클라이언트 단에서 null check
public class App {

	public static void main(String[] args) {
    	
        MyName mn = new MyName(1, "kevin", true);
        String birth = mn.getBirthday();
        
        if(birth != null){	//null 체크하는 로직 추가
        	System.out.println(birth.getMonth());
        }
    }
}

//클래스 getter 함수에서 null check
public class MyName {
	private Integer id;
    private String name;
    private boolean isMale;
    public Birthday birthday;
    
    // ~~ 생성자 ~~
    
    public Birthday getBirthday() {
    	if(this.birthday == null) {
        	throw new IllegalStateException();	//null 체크하는 로직 추가
        }
        return birthday;
    }
    
    // ~~ 나머지 getter, setter code ~~
    
}

 

우리는 위 코드처럼 클라이언트 단 혹은 클래스 getter 함수에서 null 체크하는 로직을 추가하여 해결해왔다.

 

null을 리턴해주는 경우 비용 문제는 없지만 그 코드를 사용하는 클라이언트 코드에서 주의해야하고, null 체크를 누락할 경우 에러가 많이 발생할 것이다. 클래스 내에서 예외 처리를 할 경우 stacktrace를 찍기 때문에 이 자체로 리소스를 사용하게되어 비용이 많이 든다.

 

자바8부터는 Optional을 사용하여 더 명시적으로  표현할 수 있는 방법이 생겼다.

 

사용 방법

public class MyName {
	private Integer id;
    private String name;
    private boolean isMale;
    public Birthday birthday;
    
    // ~~ 생성자 ~~
    
    public Optional<Birthday> getBirthday() {
    	return Optional.ofNullable(birthday);	//Optional로 감싸서 리턴한다
    }
    
    // ~~ 나머지 getter, setter code ~~

}

public class App {

	public static void main(String[] args) {
    	
        MyName mn = new MyName(1, "kevin", true);
        Optional<Birthday> birth = mn.getBirthday();
        
        //Optional의 ifPresent함수 사용하여 null 체크한다
        birth.ifPresent(p -> System.out.println(p.getMonth()));
        
    }
}

 

위 코드와 같이 리턴할 때 Optional로 감싸서 리턴하면 된다.

(클라이언트 코드에게는 명시적으로 빈 값일 수 있따는 것을 알려주고, 빈 값인 경우에 대한 처리를 강제한다.)

 

Optional이라는 박스를 만든 뒤 그 안에 객체 인스턴스를 담아놓고, 박스는 비어있을 수 도 있고 값이 있을 수 도 있다.

 

Optional.ofNullable() 은 말 그대로 null일 가능성이 있을 때 사용하며, null이 들어올 경우 Optional이 비어있는 것과 동일하게 처리를 해준다.

Optional.of()  는 무조건 null이 아닌거라고 가정하는 함수이고, 이 때 null이 들어올 경우 null pointer exception이 발생한다.

 

주의할 점

  • Optional은 문법적으로 제한은 없으나, 리턴 타입에만 쓰는 것만이 권장사항이다.
    • 메소드  매개변수 타입으로 사용할 경우, null 체크를 또 다시 해야한다.
    • 맵의 키 타입으로 사용할 경우, 맵의 정의 자체를 깨트리는 코드가 된다. 맵의 key는 null일 수 없기 때문이다.
    • 인스턴스 필드 타입으로 사용할 경우, 설계의 문제이다.
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말 것!
    • 정말 리턴할게 없다면 비어있는 Optional Optional.empty()를 리턴하자
  • 프리미티브 타입용 Optional이 따로 있으니 주의하여 사용할 것 (OptionalInt, OptionalLong, ...)
    • Optional.of(10); 과 같은 코드 가능하긴 하지만, 박싱/언박싱이 발생하기 때문에 권장하지 않는다.
  • 컨테이너 성격의 인스턴스 Collection, Map, Stream, Array, Optional은 Optional로 감싸지 말 것'
    • 그 자체로 비어있는지 판단할 수 있는 컨테이너 성격의 인스턴스들은 굳이 Optional을 쓰지 않아도 된다.

이 포스팅은 더 자바, Java 8 강의를 수강하며 작성되었습니다.

 

더 자바, Java 8 강의 | 백기선 - 인프런

백기선 | 자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합

www.inflearn.com