코틀린 언어 용어 사전
널 안전?. · ?: · !! · Elvis

널 안전

타입 수준에서 null 가능 여부를 구분한다. 기본이 not-null이라 NPE를 컴파일 시점에 막는다.

코틀린은 null 가능 여부를 타입에 넣었다. 기본은 not-null이다.

var a: String = "hi"
a = null            // 컴파일 에러

var b: String? = "hi"   // ? 를 붙여야 null 을 담을 수 있다
b = null            // OK

b.length            // 컴파일 에러 — null 일 수 있으니 그냥 못 쓴다

"실행하기 전에 막는다" 가 핵심이다. 자바에서 런타임에 터지던 NPE가 컴파일 오류가 된다.

세 가지 연산자

// 안전 호출 — null 이면 전체가 null
val len: Int? = b?.length
val city = user?.address?.city        // 중간에 하나라도 null 이면 통째로 null

// Elvis — null 일 때의 기본값
val len: Int = b?.length ?: 0
val name = user?.name ?: throw IllegalStateException("사용자 없음")
val id = user?.id ?: return            // 조기 반환에도 쓴다

// not-null 단언 — null 이면 NPE 를 던진다
val len: Int = b!!.length              // 코드 스멜. 최후의 수단

!!를 쓰게 되는 순간이 설계 신호다

"여기는 절대 null 이 아니다" 를 컴파일러에게 강요하는 것이다

  • 정말 아니라면 타입을 not-null 로 바꿀 수 있어야 한다
  • 못 바꾼다면 그 확신에 근거가 없다는 뜻이다

대안: 안전 호출 · Elvis · require/checkNotNull(의도를 메시지로 남긴다)

val user = checkNotNull(findUser(id)) { "사용자 $id 를 찾을 수 없다" }

스마트 캐스트

if (b != null) {
    println(b.length)      // 컴파일러가 기억한다. b!! 가 필요 없다
}

if (x is String) {
    println(x.uppercase()) // 타입 검사 후 자동 캐스트
}

var 프로퍼티에는 안 된다 — 검사 이후 다른 스레드가 바꿀 수 있기 때문이다. 지역 변수에 담아 쓰면 된다.

val v = someVarProperty ?: return      // 지역 val 로 고정한 뒤 쓴다

let — null 아닐 때만 실행

user?.let {
    println(it.name)       // user 가 null 이 아닐 때만 블록이 돈다
}

면접 함정

  • "코틀린은 NPE가 없다"!!·플랫폼 타입·초기화 전 lateinit 접근에서 난다.
  • "?. 를 쓰면 안전하다" → 결과가 null이 되므로 그다음을 처리해야 한다. 사슬 끝에서 무시하면 버그가 조용히 숨는다.

컬렉션과 null

val a: List<String>?    = null          // 리스트 자체가 null 일 수 있다
val b: List<String?>    = listOf(null)  // 원소가 null 일 수 있다
val c: List<String?>?   = null          // 둘 다

// 유용한 확장
list.filterNotNull()                    // null 원소 제거
list.mapNotNull { it.parseOrNull() }    // 변환하며 null 제거
nullableList.orEmpty()                  // null 이면 빈 리스트
nullableList?.isNotEmpty() == true      // null 이면 false (?: false 와 같다)

자주 쓰는 판별 함수

text.isNullOrBlank()      // null 이거나 공백만
list.isNullOrEmpty()
value ?: return           // 조기 반환
value ?: error("없음")     // 예외로 종료

컴파일러가 못 잡는 지점

  • ① 플랫폼 타입 (자바 경계)
  • ② !! 를 쓴 곳
  • ③ lateinit 초기화 전 접근
  • ④ 초기화 중 열린 메서드 호출
    • 생성자에서 open 메서드를 부르면 하위 클래스의 필드가 아직 null 이다
  • ⑤ 리플렉션 · 역직렬화
    • Gson 등이 not-null 필드에 null 을 넣을 수 있다 (생성자를 우회한다)

⑤가 특히 조용하다 — 타입은 String인데 실제로 null이 들어와 있어, 한참 뒤 엉뚱한 곳에서 터진다. kotlinx.serialization은 이 문제가 없다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 널 안전 — ?·!!·Elvis·플랫폼 타입·스마트 캐스트