Kotlin null을 다루는 방법

코틀린을 배우기 위해서 인프런에서 강의를 구매하고 코틀린과 친해지고 기본기를 다지기 위해서 공부하는 중이다. 글 내용은 null을 다루는 방법이고 최태현님의 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide) 강의에 소금을 조금 친 내용이다

Person class

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    @Nullable
    public String getName() {
        return name;
    }
}

Java에서의 null

public class JavaMain {
    public static void main(String[] args) {
        // startsWithA(null); NullPointerException 발생
    }

    public static boolean startsWithA(String str) {
        return str.startsWith("A");
    }

    // 예외를 던지는 메서드 & return null을 허용하지 않는 메서드
    public static boolean startsWithException(String str) {
        if (str == null) {
            throw new IllegalArgumentException("null이 들어왔습니다");
        }
        return str.startsWith("A");
    }


    // return null을 허용하는 메서드
    public static Boolean startsWithReturnNull(String str) {
        if (str == null) {
            return null;
        }
        return str.startsWith("A");
    }


    public static boolean startsWithBoolean(String str) {
        if (str == null) {
            return false;
        }
        return str.startsWith("A");
    }
}

null checking

  • Kotlin에서는 null을 허용하는 타입과 허용하지 않는 타입으로 구분되기 때문에, null을 체크하는 방법이 다르다
  • null을 허용하는 타입은 ?을 붙여서 선언하고, null을 체크하는 방법은 if문을 사용한다
  • null을 허용하지 않는 타입은 ?를 붙이지 않고 선언하고, null을 체크하는 방법은 !!를 사용한다

safe call

fun main() {
    val str: String? = "ABC"
    println(str.length) // 컴파일 에러 발생
    println(str?.length) // 3

    val str2: String? = null
    println(str2?.length) // null
}
  • ?가 safe call이며 ?.에서 ?는 null 체크를 의미한다
  • sase call은 ?.을 사용하여 null을 체크하고, null이 아닐 경우에만 메서드를 호출하는 방법이다

Elvis operator

fun main() {
    val str3: String? = null
    println(str3?.length ?: 0) // 0
}
  • 앞의 연산자가 null일 경우 뒤의 값을 반환하는 연산자이다
  • Java에서는 삼항 연산자(?:)를 사용하지만, Kotlin에서는 Elvis operator(?:)를 사용한다
  • 90도 회전하면 엘비스 프레슬리를 닮았다고 해서 Elvis operator라고 불린다 (서양유머???? 이모티콘을 많이 사용해서 그런가보다)
Kotlin
val str: String? = "ABC"
str?.length ?: 0
// str이 null이면 0을 반환, null이 아니면 length를 반환

Java
String str = "ABC";
int length = (str != null) ? str.length() : 0;
// str이 null이면 0을 반환, null이 아니면 length를 반환
  • Elvis operator은 Java에서 early return을 사용하는 것과 유사하다
Kotlin
fun calculate(number: Long?): Long {
    number ?: return 0

    // 다음 로직
}

Java
public long calculate(Long number) {
    if (number == null) return 0;
}

함수로 정의해서 알아보기

다양한 null 처리 방법 비교를 위한 변수 정의

val testStr: String? = "Apple"
val nullStr: String? = null

return null을 허용하지 않는 메서드 Boolean

fun main() {
    try {
        startsWithException(nullStr)
    } catch (e: IllegalArgumentException) {
        println("에러: ${e.message}")
        // 에러: null이 들어왔습니다
    }
}

fun startsWithException(str: String?): Boolean {
    if (str == null) {
        throw IllegalArgumentException("null이 들어왔습니다")
    }

    return str.startsWith("A")
}

return null을 허용하는 메서드 Boolean?

fun main() {
    println("${startsWithReturnNull(testStr)}") // true

    println("${startsWithReturnNull(nullStr)}") // null
}

fun startsWithReturnNull(str: String?): Boolean? {
    if (str == null) {
        return null
    }

    return str.startsWith("A")
}

return Boolean을 반환하는 메서드

fun main() {
    println("${startsWithBoolean(testStr)}") // true

    println("${startsWithBoolean(nullStr)}") // false
}

fun startsWithBoolean(str: String?): Boolean {
    if (str == null) {
        return false;
    }

    return str.startsWith("A")
}

startsWithException 메서드와 동일한 기능을 하는 메서드 – Safe call + Elvis 활용

func main() {
    println("${startsWithSafeCallElvisException(testStr)}") // true

    try {
        startsWithSafeCallElvisException(nullStr)
    } catch (e: IllegalArgumentException) {
        println("에러: ${e.message}")
        에러: null이 들어왔습니다
    }
}

fun startsWithSafeCallElvisException(str: String?): Boolean {
    return str?.startsWith("A")
        ?: throw IllegalArgumentException("null이 들어왔습니다")
}

fun startsWithException(str: String?): Boolean {
    if (str == null) {
        throw IllegalArgumentException("null이 들어왔습니다")
    }

    return str.startsWith("A")
}

startsWithReturnNull 메서드와 동일한 기능을 하는 메서드 – Safe Call + null 반환

fun main() {
    println("${startWithSafeCallNull(testStr)}") // true

    println("${startWithSafeCallNull(nullStr)}") // null
}

fun startWithSafeCallNull(str: String?): Boolean? {
    return str?.startsWith("A")
}

fun startsWithReturnNull(str: String?): Boolean? {
    if (str == null) {
        return null
    }

    return str.startsWith("A")
}

startsWithBoolean 메서드와 동일한 기능을 하는 메서드 – Safe Call + Elvis false

fun main() {
    println("${startWithSafeCallElvisBoolean(testStr)}") // true

    println("${startWithSafeCallElvisBoolean(nullStr)}") // false
}

fun startWithSafeCallElvisBoolean(str: String?): Boolean {
    return str?.startsWith("A") ?: false
}

fun startsWithBoolean(str: String?): Boolean {
    if (str == null) {
        return false;
    }

    return str.startsWith("A")
}

null 아님 단언 – Not-null assertion (!!)

fun main() {
println("${startWithNeverNull(testStr)}") // true

try {
startWithNeverNull(nullStr)
} catch (e: IllegalArgumentException) {
println("에러: ${e.message}")
// 에러: NullPointerException 발생
}
}

// 만약 null이 들어오면 RuntimeException이 발생하는 메서드
fun startWithNeverNull(str: String?): Boolean {
return str!!.startsWith("A")
}
  • nullable type이지만 아무리 생각해도 null이 될 수 없는 경우에는 !!를 사용하여 null이 아님은 단언할 수 있다
  • 생성일(createAt)을 코드 레벨이나 DB 레벨에서 100% 입력 하는 경우, null이 될 수 없다고 확실할 수 있다면 !!를 사용하여 null이 아님을 단언할 수 있다
  • 이럴 경우 Safe call을 매번 사용하는 것보다 코드가 더 간결해진다
  • null이 들어오면 NPE(Null Pointer Exception)가 발생할 수 있으므로 주의해야 한다

플랫폼 타입- Java에서 @Nullable 메서드

fun main() {
    val person = Person("혁")
    println("${person.name}") // Java @Nullable이므로 String? 타입

    println("${startsWithJavaNullable(person.name)}")
    // Java의 @Nullable 때문에 person.name이 String? 타입이므로 컴파일 에러 발생

    // null 체크 후 호출
    person.name?.let { name ->
        println("${startsWithJavaNullable(name)}")
        // startsWithJavaNullable(name)?.let = false
    }
}

fun startsWithJavaNullable(str: String): Boolean {
    return str.startsWith("A")
}

let은 무엇일까?

  • let은 nullable 값이 null이 아닐 때 만 코드 블록을 실행하는 scope 함수
    • person.name을 호출하면 String? 타입 반환
    • ?. (Safe call) → null이면 전체 체인 중단, 아니면 계속
    • let { name -> …} → null이 아닌 값을 name 매개변수로 전달하여 블록 실행한다
  • let의 역할
    • null 안정성: null이 아닐 때만 실행한다
    • Smart Cast: 블록 내에서 non-null 타입으로 자동 변환한다
    • 가독성: 조건부 실행을 간결하게 표현한다
    • 체이닝: 다른 함수들과 연결하여 사용 가능하다
  • let은 “null이 아니면 이 작업을 해줘”라는 의미로, nullable 값을 안전하고 간결하게 처리하는 Kotlin의 핵심 도구이다

출처 – 인프런 강의 중 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)