Kotlin Koans - Introduction / Nullable types

이준영·2020년 12월 9일
1

Kotlin Koans

목록 보기
7/9
post-thumbnail

문제

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    TODO()
}

class Client(val personalInfo: PersonalInfo?)
class PersonalInfo(val email: String?)
interface Mailer {
    fun sendMessage(email: String, message: String)
}

Learn about null safety and safe calls in Kotlin and rewrite the following Java code so that it only has one if expression:

public void sendMessageToClient(
    @Nullable Client client,
    @Nullable String message,
    @NotNull Mailer mailer
) {
    if (client == null || message == null) return;

    PersonalInfo personalInfo = client.getPersonalInfo();
    if (personalInfo == null) return;

    String email = personalInfo.getEmail();
    if (email == null) return;

    mailer.sendMessage(email, message);
}

풀이

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    val email = client?.personalInfo?.email
    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

class Client(val personalInfo: PersonalInfo?)
class PersonalInfo(val email: String?)
interface Mailer {
    fun sendMessage(email: String, message: String)
}

이번 문제는 if 표현식을 한 번만 사용하여 주어진 Java 코드를 Kotlin 코드로 변경하라는 문제였습니다.

Kotlin의 nullable property를 접근하는 방법 중에서 Safe Calls를 사용하여 이 문제를 풀 수 있었습니다.

Java 코드를 보면 email을 가져오기 위한 null 체크가 진행됩니다. 그리고 마지막의 mailer.sendMessage() 함수를 호출하기 위한 email과 message의 null 체크가 진행되는 것을 확인할 수 있습니다. 즉, 최종적으로 email과 message 변수가 null 이 아님을 보장하도록 코드를 작성하면 해결할 수 있었습니다.

먼저 email을 가져올 때 까지는 safe call를 사용하여 가져왔습니다.

val email = client?.personalInfo?.email

그 후 mailer.sendMessage() 함수의 인자인 email과 message가 모두 null 이 아닐 경우에 해당 함수를 실행하면 됩니다.

이번 문제에서 저는 마지막 if 표현식 부분을

if (email != null && message != null) mailer.sendMessage(email, message)

위와 같이 식이 본문인 형식으로 작성했는데, 문제에서는 블록이 본문인 형식으로 if 를 사용하였습니다.

이 부분은 저의 생각이긴 하지만, Kotlin의 if"문"이 아니라 값을 반환할 수 있는 "식"이라는 것에 초점을 맞춰서 생각해봤습니다.

식이 본문인 if 표현식은 반환 타입을 지정하지 않고, return 키워드를 사용하지 않고 값을 반환할 수 있다는 장점이 있습니다. 반면에 블록이 본문인 if 표현식은 반환 타입을 지정해야 하고 return 키워드를 사용하여 값을 반환해야 합니다.

이 문제에서 if 표현식은 반환해야 할 값이 존재하지 않습니다. 그래서 명시적으로 반환하지 않음을 보여주려고 블록이 본문인 형식으로 작성했다고 생각했습니다.

혹시 더 자세한 내용에 대해 알고 계시다면 댓글로 남겨주시면 정말 감사하겠습니다 🙇🏻‍♂️

학습 내용

Null Safety

Safe Calls

null 에 대해서 안전하게 property에 접근하는 방법 중 하나는 Safe Call 연산자인 ?. 를 사용하는 것입니다.

val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // Unnecessary safe call

위의 코드는 bnull 이 아니면 b.length 를 반환하고, 그렇지 않으면 null 을 반환합니다. 이 식의 타입은 Int? 입니다.

Safe calls는 체인(chain)에서 유용합니다. 예를 들어, 만약 직원인 Bob이 부서에 배정될 수 있다면(또는 그렇지 않은 경우), 부서장으로 다른 직원을 임명할 수 있다면, Bob이 부서장의 이름을 얻기 위해(있을 경우) 다음과 같은 코드를 작성합니다:

bob?.department?.head?.name

이 체인에서 property가 null 인 것이 하나라도 있다면 이 체인은 null 을 반환합니다.

null 이 아닌 값(not-null value)에 대해서만 특정 연산을 수행하려면 아래와 같이 안전한 호출 연산자인 let 과 함께 safe call을 사용하면 됩니다:

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { println(it) } // prints Kotlin and ignores null
}

또한 safe call은 할당(assignment)부의 왼쪽에도 위치할 수도 있습니다. 그런 다음, 만약 safe calls 체인(chain)의 리시버(receivers) 중 하나가 null 이면 할당은 취소되며, 오른쪽의 식은 고려되지 않습니다:

// If either `person` or `person.department` is null, the function is not called:
person?.department?.head = managersPool.getManager()

정리하며

Kotlin은 null 을 참조하는 코드의 위험성(Java에서의 NullPointerException 발생 등)을 없애는 것에 목표를 두고 있습니다. 그런 만큼 다양한 방법으로 null 참조를 안전하게 해결할 수 있었습니다.

이번에는 그 중에서 safe call을 사용하여 안전하게 null 을 사용하는 방법을 배웠습니다.

추후 Kotlin Reference의 Null Safety를 정리하면서 좀 더 상세히 알아보도록 하겠습니다!

참고 자료

Kotlin Reference - More Language Constructs / Null Safety

[Kotlin] 코틀린의 널 안전성(Null Safety) - devlog of ShinJe Kim

profile
growing up 🐥

0개의 댓글