본문 바로가기
Kotlin

코틀린 Nullable vs 자바 Optional

by HwanChang 2021. 5. 13.

코틀린이라는 언어가 있다는 것만 알고 있다가 처음으로 코틀린 언어의 특징을 알게 된건 제목에 써있는 자바의 Optional을 처음 알게 되었을 때이다.

자바의 Optional을 배우며 자바에서는 8버전에 와서야 Null 처리를 위한 API가 나왔지만 코틀린은 태생부터가 null safety 언어이다 라는 말을 여기저기서 자주 들었다.

사실 처음 들었을 때만 해도 코틀린 이라는 언어에 그다지 관심이 없었다.

당장 자바를 배우기도 급급한 상황에 코틀린은 또 뭐고 언제 배우나 하는 생각이 컸었다.

후에 자바 Optional을 제대로 알고나니 다시 코틀린이 궁금해져서 배워본 후 직접 느낀 코틀린, 그 중에서 코틀린의 가장 큰 특징이라고 할 수 있는 코틀린 Nullable자바Optional를 비교하는 글을 써보려 한다.

(* 이 글은 코틀린 사용자의 입장에서 자바와 비교하는 글이기 때문에 자바 Optional에 대해서는 이미 알고있다는 전제하에 글을 작성한다)

코틀린 Nullable?

자바에서는 Optional을 사용하여 객체가 null일 경우 예측하지 못한 곳에서 발생할 수 있는 NullPointException(NPE)을 통제한다.

Optional은 해당 객체가 null일 수도 있고 아닐 수도 있다는걸 의미하는데 코틀린에서도 이러한 API가 있을까?

사실 코틀린 자체는 자바와 완벽 호환이 되기 때문에 코틀린 코드에서도 Optional을 그대로 사용가능하다.

하지만 그렇게 코드를 작성하게되면 코틀린을 사용하는 메리트를 느끼지 못할 것이다.

코틀린이 자바와 가장 큰 다른점은 아래와 같이 변수 선언 시 해당 변수가 null이 가능한지 불가능한지를 설정을 한다는 점이다.

// val : 변경 불가능한 변수, 자바의 final 개념
// var : 변경 가능한 변수

// Non-Null (null 할당 불가능)
val title: String
var contents: String

// Nullable (null 할당 가능)
val title: String?
var contents: String?

null 할당 가능한 변수와 불가능한 변수의 차이가 보이는가?

기본적으로 코틀린 변수는 null을 할당할 수 없다.

하지만 null을 사용해야 할 경우는 변수의 타입 뒤에 ? 를 붙여서 null이 할당 될 수 있음을 표현해준다.


Nullable vs Optional

코틀린 Nullable에 대해 간단히 알아봤으니 본격적으로 사용할 때의 차이점을 알아보도록 하겠다.

코틀린 let() vs 자바 Optional map()

가장먼저 비교할 메소드는 자바 Optional을 쓰면 가장 많이 쓰게 되는 map() 메소드이다.

코틀린에는 자바의 map()과 비슷한 동작을 하는 let()이라는 메소드가 있다.

자바 Optional map()

// 자바 Optional의 map()을 사용하는 경우
BoardDto boardDto = boardRepository.findById(boardId)
        .map(board -> {
            return new BoardDto(board);
        })
        .orElse(new Board());

코틀린 let()

// 코틀린의 let()을 사용하는 경우 - 람다식 사용
var boardDto = boardRepository.findByIdOrNull(boardId)
    ?.let { board ->
        BoardDto(board)
    }

// 코틀린의 let()을 사용하는 경우 - it 사용 : it라는 키워드가 위 람다식에서 본 board와 동일한 값이다.
var boardDto = boardRepository.findByIdOrNull(boardId)
    ?.let {
        BoardDto(it)
    }

코드를 작성하는 형태는 비슷해서 별다른 설명이 없어도 이해가 어렵지는 않을 것이다.

하지만 let 앞에 붙어있는 ?. 는 처음 보는 거라 바로 이해가 힘들 수 있다.

위에 작성되어 있는 자바 코드와 비교해보면서 아래 설명을 읽어보면 이 또한 크게 어렵진 않다.

자바 Optional의 map() 에는 Optional이 감싸고 있는 값이 null이 아닐 경우에 실행되는 로직을 작성하는데 코틀린에서도 마찬가지로 null이 아닐경우에 실행하는 부분이 필요하다.

null일 때도 실행이 되면 결국 NPE를 피하기 위해 null 체크를 해줘야 하기 때문이다.

그래서 코틀린에는 ?. 라는 Safe-Call Operator가 존재한다.

?.null이 아닐경우 뒷 부분 수행 이라고 한 문장으로 풀어서 설명할 수 있다.

그리고 let() 메소드는 위 코드처럼 두 가지 방식으로 작성이 가능한데 let() 안에있는 람다식 부분의 파라미터는 it라는 키워드로 대체가 가능하다.

let() 메소드의 또 다른 특징으로 return 키워드를 사용하지 않아도 let() 안에 작성된 코드 중 가장 마지막 라인이 최종 리턴값이 된다.

따라서 위 코틀린 코드는 findByIdOrNull() 을 통해 조회된 값 혹은 null이 넘어올 수 있는데 null이 아닐 경우에만 BoardDto를 리턴하라는 의미로 이해할 수 있다.


코틀린 엘비스 연산자 vs 자바 Optional orElse()

엘비스 연산자란 ?: 모양으로 생긴 연산자를 말한다. (옆으로 세워서 보면 엘비스 프레슬리 머리모양을 닮아서 붙여진 이름이라고..)

이 엘비스 연산자는 null일 경우 뒷 부분 수행 이라고 생각하면 된다.

자바의 orElse()와 같은 역할인데 아래 코드를 보며 비교해보면 바로 이해가 될 것이다.

자바 Optional orElse()

// orElse() - null일 경우 Board 객체 반환
BoardDto boardDto = boardRepository.findById(boardId)
        .map(board -> {
            return new BoardDto(board);
        })
        .orElse(new Board());

// orElseGet() - null일 경우 메소드 수행
BoardDto boardDto = boardRepository.findById(boardId)
        .map(board -> {
            return new BoardDto(board);
        })
        .orElseGet(() -> getDefaultBoard());

// orElseThrow() - null일 경우 예외 발생
BoardDto boardDto = boardRepository.findById(boardId)
        .map(board -> {
            return new BoardDto(board);
        })
        .orElseThrow(() -> new NotFoundException())

코틀린 엘비스 연산자

// null일 경우 Board 객체 반환, orElse()와 동일
var boardDto = boardRepository.findByIdOrNull(boardId)
    ?.let {
        BoardDto(it)
    }
    ?: Board()

// null일 경우 메소드 수행, orElseGet()과 동일
var boardDto = boardRepository.findByIdOrNull(boardId)
    ?.let {
        BoardDto(it)
    }
    ?: getDefaultBoard()

// null일 경우 예외 발생, orElseThrow()와 동일
var boardDto = boardRepository.findByIdOrNull(boardId)
    ?.let {
        BoardDto(it)
    }
    ?: throw NotFoundException("$boardId Not Found")

코틀린 !! 연산자 vs 자바 Optional get()

자바에서는 Optional이 감싸고 있는 값이 확실히 null이 아니라면 get()을 통해 값을 반환받을 수 있다.

코틀린에서는 !! 연산자가 같은 기능을 한다.

자바 Optional get()

Optional<String> text = Optional.of("not null string");
boolean isLegalLength = text.get().length() > 5;

코틀린 !! 연산자

val text: String = "not null string"
var isLegalLength = text!!.length > 5

위와 같이 코틀린에서는 !! 을 사용하여 값이 null이 아님을 단언할 수 있다.


마치며...

자바 Optional과 비교하여 코틀린은 어떤식으로 null을 처리하는지 알아보았다.

코틀린을 직접 사용하며 느낀 Nullable의 가장 큰 장점은 런타임 시 예상하지 못한 NPE 발생을 줄일 수 있는 점인 것 같다.

Optional도 잘못 사용하면 런타임 시 에러가 발생할 수 있지만 코틀린에서는 null 관련 처리를 잘못하게 되면 컴파일 시 오류가 발생하여 즉시 수정이 가능하기 때문에 런타임 오류가 발생할 일이 줄어든다.

위 예시들은 코틀린이 가지고있는 다양한 연산자, 메소드 중 극히 일부에 불과하기 때문에 앞으로 정리해야 할 내용이 아직 산더미이다.

이번엔 가장 기본적인 부분을 정리해봤는데 이를 바탕으로 앞으로 차근차근 코틀린 관련 내용을 쌓아 나가보도록 하겠다.

'Kotlin' 카테고리의 다른 글

Kotlin에서 Optional을 사용한다?  (0) 2022.03.16

댓글