Optional 과 nullable

CmplxN·2020년 9월 27일
1

Optional

java로 프로그래밍하다보면 심심찮게 NullPointerException을 겪게 된다. 꼼꼼하게 설계하고, null관련 처리를 하면 된다곤 하지만, 현실은 녹록치 않다. 다른 사람이 만든 클래스를 사용하는데 문서화가 덜 되었다던가, 너무 복잡하다던가, 파악하기 귀찮다던가한 이유로 NPE를 뿜어내는 코드를 작성하게 된다.

이런 문제를 해결하기 위해 Java 8에서 Optional 이라는 클래스가 추가되었다.
Optional은 쉽게(?) 말하자면 "데이터가 없다"는 null 아닌 data를 만들고, 이것을 handling 하는 기능이 있는 클래스라고 할 수 있다.

아래는 Optional을 만드는 예시 java 코드다.

Optional<String> s1 = Optional.empty(); // 원래는 null인 경우
Optional<String> s2 = Optional.of("Hello"); // NPE if input is null
Optional<String> s3 = Optional.ofNullable("World"); // null input이면 empty 반환

또한 아래와 같이 Optional class를 변환 및 필터링할 수 있다.

s1.map(it -> it + " op"); // empty
s3.map(it -> it + " op"); // "World op"
s2.flatMap(it -> other); // other
s2.filter(it -> it.startsWith("H")); // Hello
s3.filter(it -> it.startsWith("H")); // empty

그리고 아래와 같이 Optional class를 unwrapping 해서 원래 type의 data를 얻을 수 있다.

s1.orElse("empty"); // empty
s1.orElseThrow(RuntimeException::new) // RuntimeException
s2.orElse(null); // Hello
s3.orElseGet(() -> "empty"); // World

마지막으로 아래와 같이 값이 존재하는지 조건문을 쓸 수 있다.

s1.ifPresent(it -> {
    System.out.println("not Empty");
}); // nothing happens
s2.ifPresent(it -> {
    System.out.println("not Empty " + it);
}); // not Empty Hello

s1.isPresent() // false
s2.isPresent() // true

Optional 클래스는 자바 버전에 따라 기능이 추가되기도 하였고, 이것 외에도 기능이 많다. 그러므로 Optional 클래스의 상세 스펙이나 자세한 정보는 관련 공식 문서나 블로그 포스트들을 찾아보면 된다.

nullable

kotlin에는 Optional 클래스가 따로 없다. 왜냐하면 굳이 필요가 없기 때문이다(!!). kotlin은 Optional 대신 변수(프로퍼티)의 타입을 nullable하게 지정해서 null 관련 연산자를 이용한다.

  • ?. : 앞이 null이 아니면 뒤의 함수 또는 프로퍼티에 접근한다. (safe-call)
  • ?: : 앞이 null이면 뒤의 값으로 대체한다. (elvis)
  • !! : non-null assertion으로 java에서 아무것도 처리하지 않는 것과 마찬가지다. 귀찮다고 남용하면 NPE ㅎㅇㅎㅇ
    여기에 scope function(주로 let)을 사용하면 Optional의 기능을 그대로 쓸 수 있다.

아래는 kotlin의 nullable로 위에 있던 java code를 작성한 것이다.

val s1: String? = null
val s2: String? = "Hello" ?: throw NullPointerException()
// 또는 val s2: String = "Hello"
val s3: String? = "World"

위의 Optional 변환 코드를 kotlin nullable을 사용하면 아래와 같을 것이다.

s1?.let {it + "op" }
s3?.let {it + "op" }
s2?.let { other }
s2?.takeIf { it.startsWith("H") }
s3?.takeIf { it.startsWith("H") }

위의 Optional unwrapping 코드를 kotlin nullable을 사용하면 아래와 같을 것이다.

s1?: "empty"
s1?: throw NoSuchElementException()
s2
s3?: run { "empty" }

마지막으로 Optional의 존재 여부 조건문은 아래와 같이 작성할 수 있을 것이다.

s1?.run { println("not Empty") }
s2?.run { println("not Empty $it") }

s1 == null
s2 == null

그래서 뭘 써야하는가?

Optional 대신 kotlin의 nullable을 사용했을 때의 단점은 아래 정도로 생각해 볼 수 있다.

  • 기존에 Java의 Optional 클래스를 사용하고 있었는데, 사정이 있어서 뒤엎을 수 없다.
  • RxJava2를 쓰려는데 RxJava2는 null을 지원하지 않는다. 즉 null은 그대로 pass할 수 없지만, Optional의 empty는 사용할 수 있다.

Optional 대신 kotlin의 nullable을 사용했을 때의 장점은 아래 정도로 생각해 볼 수 있다.

  • 어짜피 kotlin으로 프로그래밍할텐데 java스러운 kotlin이 아닌, kotlin스러운 kotlin을 작성할 수 있다.
  • Optional도 결국 wrapper class인데, kotlin nullable은 wrapper가 아니라 언어 내장 기능이다. 그러므로 성능적인 장점이 있다. (primitive type은 결국 wrapper class로 변환되긴 하지만도)
  • 잘못 사용했을 시 kotlin nullable은 compile error prone이나, Optional은 runtime error prone이다. error가 날거면 compile error가 훨씬 좋다. (물론 compile error를 냅다 모르겠다 생각 없이 !!로 처리하면 답이 없다.)

개인적으로는 위에서 말한 제약 조건이 없다면, kotlin의 nullable을 쓸 것 같다. (RxJava2가 너무 걸리는걸)

profile
Android Developer

0개의 댓글