💎 Kotlin

[Kotlin] 코틀린 타입 시스템 nullable type

itssweetrain 2023. 8. 5. 18:07

자바와 비교하면 코틀린의 타입 시스템은 코드의 가독성을 향상시키는 데 도움이 되는 특징을 가지고 있다.

그런 특징 중 널이 될 수 있는 타입(nullable type)과 읽기 전용 컬렉션이 있다.

 

널이 될 수 있는 타입과 널을 처리하는 구문 문법

  • 코틀린을 비롯한 최신 언어에서 null에 대한 접근 방법은 이 문제를 실행 시점에서 컴파일 시점으로 옮김
  • 널 가능성(nullability)은 NullPointerException 오류(NPE)를 피할 수 있게 돕기 위한 코틀린 타입 시스템의 특징
  • 널이 될 수 있는지 여부를 타입 시스템에 추가함으로써 컴파일러가 여러 가지 오류를 컴파일 시 미리 감지해서 실행 시점에 발생할 수 있는 예외 가능성을 줄여줌

널이 될 수 있는 타입

  • 코틀린 타입 시스템은 널이 될 수 있는 타입을 명시적으로 지원함
  • 널이 될 수 있는 타입은 프로그램 안의 프로퍼티나 변수에 null을 허용하게 만드는 방법이라는 뜻

어떤 변수가 널이 될 수 있다면 그 변수에 대해 메소드를 호출하면 NullPointerException이 발생할 수 있으므로 안전하지 않다. 코틀린은 그런 메소드 호출을 금지함으로써 많은 오류를 방지

널이 인자로 들어올 수 없다면 코틀린에서는 다음과 같이 함수를 정의

fun strLen(s: String) = s.length

strLen에 null이거나 널이 될 수 있는 인자를 넘기는 것은 금지되며, 혹시 그런 값을 넘기면 컴파일 시 오류가 발생

>>> strLen(null)
ERROR: Null can not be a value of a non-null type String

strLen 함수에서 파라미터 s의 타입은 String인데 코틀린에서는 이는 s가 항상 String의 인스턴스여야 한다는 뜻. 이 때 컴파일러는 널이 될 수 있는 값을 strLen에 인자로 넘기지 못하게 막음. 따라서 strLen 함수가 결코 실행 시점에 NullPointerException을 발생시키지 않으리라 장담할 수 있음.

이 함수가 널과 문자열을 인자로 받을 수 있게 하려면 타입 이름 뒤에 물음표(?)를 명시해야 함

fun strLenSafe(s: String?) = ...

:: 이렇게 널이 될 수 있는 타입과 널이 될 수 없는 타입을 구분하면 각 타입의 값에 대해 어떤 연산이 가능할지 명확히 이해할 수 있고 실행 시점에 예외를 발 생할 수 있는 연산을 판단할 수 있음. 따라서 그런 연산을 아예 금지시킬 수 있음

그렇다면 코틀린에서 널이 될 수 있는 타입을 어떻게 다루는가? 널이 될 수 있는 값을 안전하게 도와주는 연산자들이 있음

 

안전한 호출 연산자 ?.

  • ?.은 null 검사메소드 호출을 한 번의 연산으로 수행
  • 호출하려는 값이 null이 아니라면 ?.은 일반 메소드 호출처럼 작동하고, 호출하려는 값이 null이면 이 호출은 무시되고 null이 결과 값이 된다.
  • 널이 아닌 값에 대해서만 메소드를 호출
s?.toUpperCase() 
//위의 식은 아래의 식과 같음
if(s != null) s.toUpperCase() else null

 

엘비스 연산자 ?:

  • null 대신 사용할 디폴트 값을 지정할 때
fun strLenSafe(s: String?): Int = s?.length ?: 0
>>>strLenSafe("abc")
>>>3
>>>strLenSafe(null)
>>>0

병합연산자

js

test?.length ( null, false, 0 ) ?? 0

swift

boolValue? ( nil, false ) ?? 0

 

안전한 캐스트 as?

  • 어떤 값을 지정한 타입으로 캐스팅, 대상 타입으로 변환할 수 없으면 null을 반환

안전한 호출, 안전한 캐스트, 앨비스 연산자는 유용하기 때문에 코틀린 코드에 자주 나타남. 하지만 때로는 코틀린의 널 처리 지원을 활용하는 대신 직접 컴파일러에게 어떤 값이 널이 아니라는 사실을 알려주고 싶은 경우가 있음

 

널 아님 단언 !!

not-null assertion

  • 어떤 값이든 널이 될 수 없는 타입으로 강제 타입 캐스팅
  • 실제 널에 대해 !!를 적용하면 NPE 발생

이러한 리스크에도 널 아님 단언을 쓰는 경우가 더 좋은 경우 :

어떤 함수가 값이 널인지 검사한 다음에 다른 함수를 호출한다고 해도 컴파일러는 호출된 함수 안에서 안전하게 그 값을 사용할 수 있음을 인식할 수 없음. 하지만 이런 경우 호출된 함수가 언제나 다른 함수에서 널이 아닌 값을 전달받는다는 사실이 분명하다면 굳이 널 검사를 다시 수행하고 싶지 않을 것. 이럴 때 널 아님 단언문을 쓸 수 있다.

 

let

  • 널이 될 수 있는 값을 널이 아닌 값만 인자로 받는 함수에 넘기는 경우에 많이 쓰임
  • 자신의 수신 객체를 인자로 전달받은 람다에게 넘김