Optional 정리

Bruce Han·2023년 2월 11일
0

Java8-정리

목록 보기
9/20
post-thumbnail
post-custom-banner

이 포스팅의 코드 및 정보들은 강의를 들으며 정리한 내용을 토대로 작성한 것입니다.

Optional

Optional이란

Java 8에 새로 추가된 인터페이스
비어있을 수도 있고, 뭔가(값)를 담고 있을 수도 있는 컨테이너

실습에 앞서 소스들을 먼저 알아보자.

Main Class

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));
    }
}

SchoolClass

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메서드는
        만들었지만 너무 길어서 생략
    */
}

Progress Class

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;
    }
}

자주 접하게 되는 NullPointerException

optional1

jsp_programming이라는 객체를 만들고, 그의 시간을 가져와서 출력하려고 하면 NullPointerException이 발생한다.

 jsp_programming.getProgress().getLearnDuration();

왜냐하면 progress가 null이기 때문이다.

getProgress()를 했을 때 progress가 기본값을 반환하는데, Reference 타입은 기본값이 null이다.
null에다가 operation으로 메서드를 호출하려고 하면 null을 참조하면 안 된다. 는 것이 NullPointerException이다.

optional2

메서드에서 작업 중 특별한 상황에서 값을 제대로 리턴할 수 없는 경우

Reference 타입을 받을 때 값이 있는지 없는지 체크하기 위해 if문을 사용할 수도 있다.

  1. 하지만, 이런 if 조건문을 통한 체킹은 개발자의 실수 등으로 에러를 일으킬 수 있다.
  2. 또한, null을 리턴하는 것 자체가 문제이다.

optional3

SchoolClass에 있는 Progress의 getter 메서드에다가 if조건문으로 progress가 null일 때 RuntimeException이자 CheckedException을 던지도록 만들어줄 수도 있다.

  1. getProgress()를 사용하는 클라이언트쪽의 고통(?)이 덜하겠지만
  2. CheckedException을 던지기 시작하면 에러 처리를 강제하게 되는 부작용이 있다.
  3. Error가 발생되면 Java에서는 stack trace를 찍는다.
    • Error가 발생하기 전까지의 어떤 call stack을 거쳐서 에러가 발생하게 됐는지에 대한 정보를 담게 된다.
    • 이 과정에서 불필요하게 리소스가 낭비된다.

진짜로 필요한 경우에만 예외를 써야지, 로직을 처리할 때 에러를 쓰는 건 좋지 못하다.


이것을 Optional을 활용해서 명시적으로 표현할 수 있으며, 아래 코드처럼 (웬만하면) 리턴 타입에 Optional을 붙여 사용한다.

class SchoolClass {
	...
	public Optional<Progress> getProgress() {
    	// 반환하려는 progress 자체가 null일 수도 있기에 ofNullable() 사용 
    	return Optional.ofNullable(progress);
        // Optional.of()는 뒤에 오는 progress가 무조건 null이 아닐 때 사용
	}
    ...
}

Optional을 사용할 때 주의할 점

리턴값으로만 쓰기를 권장한다

메서드 매개변수 타입 (X)

물론 메서드 매개변수 타입으로 쓸 수 있으며, 문법적으로 오류는 없지만

optional4

이렇게 사용하게 되면 이때에도 체크를 해야한다.

optional5

하지만, ifPresent()를 사용해서 값이 존재하는지 체크하더라도 메서드를 호출할 때 null을 호출할 수 있기 때문에 위험하다.

optional6

이런 식으로 호출하는 쪽에서 의도하던 안 하던 null을 넣을 수 있다.
결국 setter() 쪽에서 null체크를 한 번 더 해야하기 때문에, 메서드 파라미터에 Optional을 쓰는 의미가 없어진다.

optional7

이렇게 하면 IDE에서는 Optional을 썼으니 체크도 Optional로 하라고 하지만

optional8

Optional의 ifPresent()를 사용하면 NullPointerException이 발생한다.

Map의 key 타입 (X)

Map이라는 인터페이스가 가지고 있는 key는 null이 아니다라는 특징을 깨게 된다.
만약 Optional을 사용해서 Map타입의 객체를 체크한다면 어불성설이 된다.

인스턴스 필드 타입 (X)

optional9

progress가 있을 수도 있고 없을 수도 있다? 필드를 선언할 때 Optional을 붙이면 애초에 도메인 클래스 설계가 잘못됐다고 볼 수 있다.
차라리 상위/하위 클래스로 쪼개거나, delegation을 사용하는 것이 낫다.

Primitive type용 Optional은 따로 있다.

Optional.of(10);

이렇게 Optional에 primitive type을 넣을 수는 있지만, 안에서는 boxing/unboxing이 된다. boxing/unboxing이 많이 벌어지면 성능에 좋지 않다.

OptionalInt.of(10);

Optional보다는 OptionalInt 등의 primitive type용을 쓰는 것이 권장사항이라고 한다.

Optional을 리턴하는 메서드에서 null을 리턴하지 말자

optional10

Optional 타입의 progress객체를 반환하는 getter메서드의 반환값을 null로 하고서

optional11

이를 호출하려고 하면 null을 불러오는 게 되기 때문에 NullPointerException이 발생하게 된다.

optional12

정 반환할 게 없다면 Optional.empty()를 반환하면 된다.

optional13

container 성격의 인스턴스들을 Optional로 다시 감싸지 말자

Collection, Map, Stream, Array, Optional은 Optional로 감싸지 말라는 것이다.
얘네 자체로 비어있는지의 여부를 판단할 수 있는 컨테이너 성격의 인스턴스들이기 때문이다.
굳이 두 번 감쌀 필요가 없다.

Reference

profile
만 가지 발차기를 한 번씩 연습하는 사람은 두렵지 않다. 내가 두려워 하는 사람은 한 가지 발차기를 만 번씩 연습하는 사람이다. - Bruce Lee
post-custom-banner

0개의 댓글