이 포스팅의 코드 및 정보들은 강의를 들으며 정리한 내용을 토대로 작성한 것입니다.
Java 8에 새로 추가된 인터페이스
비어있을 수도 있고, 뭔가(값)를 담고 있을 수도 있는 컨테이너
실습에 앞서 소스들을 먼저 알아보자.
public class Main {
public static void main(String[] args) {
List<SchoolClass> programmingClass = new ArrayList<>();
programmingClass.add(new SchoolClass(1, "JSP programming", true));
programmingClass.add(new SchoolClass(2, "C programming", true));
programmingClass.add(new SchoolClass(3, "Java programming", false));
programmingClass.add(new SchoolClass(4, "Android programming", false));
programmingClass.add(new SchoolClass(5, "JavaScript programming", false));
}
}
public class SchoolClass {
private Integer id;
private String subject;
private boolean closed;
public Progress progress;
public SchoolClass(Integer id, String subject, boolean closed) {
this.id = id;
this.subject = subject;
this.closed = closed;
}
/*
id, subject, closed에 대한 getter, setter메서드는
만들었지만 너무 길어서 생략
*/
}
import java.time.Duration;
public class Progress {
private Duration learnDuration; // 한 과목당 주차 수
private boolean finished; // 과목을 마쳤는지 안 마쳤는지
public Duration getLearnDuration() {
return learnDuration;
}
public void setLearnDuration(Duration learnDuration) {
this.learnDuration = learnDuration;
}
}
jsp_programming이라는 객체를 만들고, 그의 시간을 가져와서 출력하려고 하면 NullPointerException
이 발생한다.
jsp_programming.getProgress().getLearnDuration();
왜냐하면 progress가 null이기 때문이다.
getProgress()를 했을 때 progress가 기본값을 반환하는데, Reference 타입은 기본값이 null이다.
null에다가 operation으로 메서드를 호출하려고 하면 null을 참조하면 안 된다. 는 것이 NullPointerException이다.
Reference 타입을 받을 때 값이 있는지 없는지 체크하기 위해 if문을 사용할 수도 있다.
SchoolClass에 있는 Progress의 getter 메서드에다가 if조건문으로 progress가 null일 때 RuntimeException이자 CheckedException을 던지도록 만들어줄 수도 있다.
진짜로 필요한 경우에만 예외를 써야지, 로직을 처리할 때 에러를 쓰는 건 좋지 못하다.
이것을 Optional을 활용해서 명시적으로 표현할 수 있으며, 아래 코드처럼 (웬만하면) 리턴 타입에 Optional을 붙여 사용한다.
class SchoolClass {
...
public Optional<Progress> getProgress() {
// 반환하려는 progress 자체가 null일 수도 있기에 ofNullable() 사용
return Optional.ofNullable(progress);
// Optional.of()는 뒤에 오는 progress가 무조건 null이 아닐 때 사용
}
...
}
물론 메서드 매개변수 타입으로 쓸 수 있으며, 문법적으로 오류는 없지만
이렇게 사용하게 되면 이때에도 체크를 해야한다.
하지만, ifPresent()를 사용해서 값이 존재하는지 체크하더라도 메서드를 호출할 때 null을 호출할 수 있기 때문에 위험하다.
이런 식으로 호출하는 쪽에서 의도하던 안 하던 null을 넣을 수 있다.
결국 setter() 쪽에서 null체크를 한 번 더 해야하기 때문에, 메서드 파라미터에 Optional을 쓰는 의미가 없어진다.
이렇게 하면 IDE에서는 Optional을 썼으니 체크도 Optional로 하라고 하지만
Optional의 ifPresent()를 사용하면 NullPointerException이 발생한다.
Map이라는 인터페이스가 가지고 있는 key는 null이 아니다
라는 특징을 깨게 된다.
만약 Optional을 사용해서 Map타입의 객체를 체크한다면 어불성설이 된다.
progress가 있을 수도 있고 없을 수도 있다? 필드를 선언할 때 Optional을 붙이면 애초에 도메인 클래스 설계가 잘못됐다고 볼 수 있다.
차라리 상위/하위 클래스로 쪼개거나, delegation을 사용하는 것이 낫다.
Optional.of(10);
이렇게 Optional에 primitive type을 넣을 수는 있지만, 안에서는 boxing/unboxing이 된다. boxing/unboxing이 많이 벌어지면 성능에 좋지 않다.
OptionalInt.of(10);
Optional보다는 OptionalInt 등의 primitive type용을 쓰는 것이 권장사항이라고 한다.
Optional 타입의 progress객체를 반환하는 getter메서드의 반환값을 null로 하고서
이를 호출하려고 하면 null을 불러오는 게 되기 때문에 NullPointerException이 발생하게 된다.
정 반환할 게 없다면 Optional.empty()
를 반환하면 된다.
Collection, Map, Stream, Array, Optional은 Optional로 감싸지 말라는 것이다.
얘네 자체로 비어있는지의 여부를 판단할 수 있는 컨테이너 성격의 인스턴스들이기 때문이다.
굳이 두 번 감쌀 필요가 없다.