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
에 대해서 안전하게 property에 접근하는 방법 중 하나는 Safe Call 연산자인 ?.
를 사용하는 것입니다.
val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // Unnecessary safe call
위의 코드는 b
가 null
이 아니면 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를 정리하면서 좀 더 상세히 알아보도록 하겠습니다!