Kotlin in action 2/e: 널이 될 수 있는 값(2): not null assertion(!!), let(), lateinit()

2026. 2. 16. 14:44·Kotlin

!!를 통해 널 아님 단언하기

!!를 사용하면 컴파일러에게 해당 값이 절대 null이 아니라고 믿게 할 수 있다. 심지어 진짜 null값에도 !!를 사용하여 null이 아니라고 믿게 할 수 있기 때문에 NPE가 발생할 수 있다.

fun ignoreNulls(str: String?) {
    val strNotNull: String = str!!
    println(strNotNull.length)
}

fun main() {
    ignoreNulls(null)
}

 

str은 nullable 이지만 !!를 통해 컴파일러에게 널이 아님을 전달하였기에 세이프 콜이나 널 검사 없이 바로 String의 프로퍼티 length에 접근할 수 있다. 

 

널 아님 단언은 런타임에 NPE가 발생할 수 있기에 거의 사용하지 않는 편이 좋다고 생각한다. 책에서는 다음과 같은 상황에서 널 아님 단언을 사용해볼만 하다고 소개하고 있다.

class SelectableTextList(
    val contents: List<String>,
    var selectedIndex: Int? = null,
)

class CopyRowAction(val list: SelectableTextList) {
    fun isActionEnabled(): Boolean = 
        list.selectedIndex != null
    fun executeCopyRow() {
        val index = list.selectedIndex!!
        val value = list.contests[index]
	}
}

 

위 클래스가 있다고 하고, isActionEnabled()를 호출한 뒤, null이 아님을 확인하고 executeCopyRow() 호출한다고 할 때, 컴파일러는 isActionEnabled() 함수에서 not null을 검사했다는 사실을 기억하지 못하기 때문에 널 아님 단언을 해줄만 하다고 소개하고 있다.

 

let(), 자신의 수신 객체를 인자로 전달받은 람다에 넘기기

let() 함수는 자신의 수신 객체를 인자로 전달받은 람다에 넘긴다. 이를 세이프 콜과 함께 사용하면 다음과 같은 기능으로 확장시킬 수 있다. 

 

수신객체가 널일 때: 아무일도 발생하지 않는다.

 

수신객체가 널이 아닐 때: let 함수에 전달된 람다를 실행한다. 

fun sendEmailTo(email: String) {
    println("Sending email to $email")
}

fun main() {
    var email: String? = "yole@example.com"
    email?.let { sendEmailTo(it) }
    email = null
    email?.let { sendEmailTo(it) }
}

 

위 예제처럼 사용해볼 수 있을 것이다. 위 예제에서 첫 번째 let은 실행되지만,  두 번째 let은 절대 실행되지 않을 것이다. 

 

지연 초기화 프로퍼티를 이용한 nullable 타입 피하기

class MyService {
    fun performAction(): String = "Action Done!"
}

class MyTest {
    private var myService: MyService? = null

    @BeforeAll fun setUp() {
        myService = MyService()
    }

    @Test fun testAction() {
        assertEquals("Action Done!", myService!!.performAction())
    }
}

 

위 코드에서는 myService의 초기값을 제공할 것이 딱히 없기 때문에 null을 이용해 초기화를 진행하고 있다. 

때문에 굳이 nullable 타입으로 계획하지 않았음에도 nullable 타입이 되버린다. 

 

혹은 MyService 타입의 초기화 값을 따로 만들면 nullable 하게 만들지 않아도 될 수 있다. 

하지만 코틀린에서는 이러한 상황을 위해 지연 초기화를 명시할 수 있다.

class MyTest {
    private lateinit var myService: MyService

    @BeforeAll fun setUp() {
        myService = MyService()
    }

    @Test fun testAction() {
        assertEquals("Action Done!", myService.performAction())
    }
}

 

위와 같이 lateinit을 붙이면, 프로퍼티를 나중에 초기화 할 수 있기에 nullable 타입에서 벗어난 것을 확인할 수 있다. 당연하게도 lateinit을 명시했으므로, 초기화 하기 전까진 해당 프로퍼티에 접근할 수 없다. 

 

타입 파라미터는 기본적으로 nullable이다. 

자바 타입 시스템은 널 가능성을 지원하지 않는다. 하지만 자바에서도 @Nullable, @NotNull 등으로 널 가능성을 어노테이션으로 표시할 수 있는데 코틀린에서는 이러한 nullable 정보를 인식할 수 있다. 

뿐만 아니라 안드로이드, 젯브레인즈 도구들이 지원하는 어노테이션 등 코틀린이 이해할 수 있는 nullable 어노테이션들을 인식할 수 있다고 한다. 

이에 대해서는 추후 좀 더 조사하여 정리하려고 한다. 

 

코틀린이 널 관련 정보를 알 수 없는 타입: 플랫폼 타입

코틀린이 널 관련 정보를 알 수 없는 경우도 존재한다. 이를 플랫폼 타입이라고 하는데, 이러한 타입은 nullable, non-nullable 둘 중 아무 타입이나 선택해서 처리해도 된다.

 

이는 다르게 생각하면, 컴파일러에게 널타입 안정성을 맡길 수 없으므로, 이 책임이 다시 개발자에게 돌아오며 런타임에 NPE가 발생할 수도 있다 라는 뜻이기도 하다. 

 

public class Person {
    private final String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name
    }
}

 

위와 같은 자바 코드에서 코틀린 컴파일러는 널 가능성에 대해 전혀 모른다

 

자바 메서드를 오버라이드 할 때 nullable 결정

위와 비슷한 맥락이지만, 코틀린에서 자바 메서드를 오버라이드 할 때, 그 메서드의 파라미터와 반환 타입을 nullable, non-nullable로 선언할지 결정해야한다. 

interface StringProcessor {
    void process(String value);
}

 

위과 같은 자바 인터페이스가 있을 때, 코틀린 컴파일러는 두 구현을 다 받아들인다. 

class StringPrinter : StringProcessor {
    override fun process(value: String) {
        println(value)
        }
    }

class NullableStringPrinter: StringProcessor {
    override fun process(value: String?) {
        if (value != null) {
            println(value) 
        }
    }
}

 

'Kotlin' 카테고리의 다른 글

Kotlin in action 2/e: 고차함수: 람다를 파라미터와 반환값으로 사용(2) 인라인 함수, 코틀린 표준 라이브러리의 인라이닝  (0) 2026.02.19
Kotlin in action 2/e:고차함수: 람다를 파라미터와 반환값으로 사용(1) 고차함수, 스마트 스테핑, 함수 타입의 파라미터, 함수를 반환하는 함수  (1) 2026.02.18
Kotlin in action 2/e: 널이 될 수 있는 값(1): nullability, safe call(?.) elvis operator(?:), safe cast(as?)  (0) 2026.02.16
Kotlin in action 2/e: 람다를 사용한 프로그래밍(2) 함수형 인터페이스, 수신 객체 지정 람다  (0) 2026.02.15
Kotlin in action 2/e: 람다를 사용한 프로그래밍(1) 코틀린의 람다  (0) 2026.02.14
'Kotlin' 카테고리의 다른 글
  • Kotlin in action 2/e: 고차함수: 람다를 파라미터와 반환값으로 사용(2) 인라인 함수, 코틀린 표준 라이브러리의 인라이닝
  • Kotlin in action 2/e:고차함수: 람다를 파라미터와 반환값으로 사용(1) 고차함수, 스마트 스테핑, 함수 타입의 파라미터, 함수를 반환하는 함수
  • Kotlin in action 2/e: 널이 될 수 있는 값(1): nullability, safe call(?.) elvis operator(?:), safe cast(as?)
  • Kotlin in action 2/e: 람다를 사용한 프로그래밍(2) 함수형 인터페이스, 수신 객체 지정 람다
Kirbyyy
Kirbyyy
개인적인 일상과 회고를 기록하는 블로그입니다.
  • Kirbyyy
    커브볼의 생존일지
    Kirbyyy
  • 전체
    오늘
    어제
    • 분류 전체보기 (53)
      • 우아한테크코스 (8)
      • 프로덕트 빌드 (0)
      • Problem Solving (20)
      • C++ (0)
      • Kotlin (19)
      • Java (3)
      • CS (2)
        • AI (2)
      • 취미생활 (0)
        • 서평 (0)
        • 프라모델 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    분할 정복
    백준 파도반 수열
    백준 31575
    너비 우선 탐색
    백준 16174
    우테코 8기
    다이나믹 프로그래밍
    백준 33272
    백준 알고리즘
    C++
    백준 RGB 거리
    백준 연속 합
    백준
    백준 1356
    그리디 알고리즘
    백준 16173
    BFS
    백준 11123
    ProblemSolving
    Problem Solving
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Kirbyyy
Kotlin in action 2/e: 널이 될 수 있는 값(2): not null assertion(!!), let(), lateinit()
상단으로

티스토리툴바