1. null 검사와 호출 합치기
안전한 호출 연산자 ( ?. )
프로퍼티에 대한 안전한 호출 연산자 사용
class Employee(val name: String, val manager: Employee?)
fun managerName(employee: Employee): String? = employee.manager?.name
fun main() {
val ceo = Employee("Da Boss" null)
val developer = Employee("Bob Smith", ceo)
println(managerName(developer))
println(managerName(ceo))
}
안전한 호출 연쇄 사용
class Address(val streetAddress: String, val zipCode: Int,
val city: String, val Country: String)
class Company(val name: String, val address: Address?)
class Person(val name: String, val company: Company?)
fun Person.countryName(): String {
val country = this.company?.address?.country
return if (country != null) country else "Unknown"
}
fun main() {
val person = Person("Dmitry", null)
println(person.countryName())
}
2. null에 대한 기본값 제공 (엘비스 연산자)
- 코틀린 → 엘비스 연산자( ?: )를 통해 null에 대한 기본값을 간단하게 지정할 수 있음
사용 방법
fun greet(name: String?) {
val recipient: String = name ?: "unnamed"
println("Hello, ${recipient}!")
}
- 2개의 값을 받음
- null이 아닐 경우의 결과값
- null일 경우의 결과값
안전한 호출 연산자와 엘비스 연산자 동시사용
fun strLenSafe(s: String?): Int = s?.Length ?: 0
fun main() {
println(strLenSafe("abc")
println(strLenSafe(null))
}
- 객체가 null인 경우에 대비해 값을 지정할 수 있음
- 안전한 호출 연산자가 null을 반환할 때 기본값을 지정할 수 있음
엘비스 연산자 오른쪽에 throw, return등 사용가능
class Address(val streetAddress: String, val zipCode: Int,
val city: String, val country: String)
class Company(val name: String, val address: Address?)
class Person(val name: String, val company: Company?)
fun printShippingLabel(person: Person) {
val address = person.company?.address ?: throw IllegalArgumentException("No address")
with (address) {
println(streetAddress)
println("$zipCode $city, $country")
}
}
fun main() {
val address = Address("Elsestr. 47", 80687, "Munich", "Germany")
val jetbrains = Company("JetBrains", address)
val person = Person("Dmitry", jetbrains)
printShippingLabel(person)
printShippingLabel(Person("Alexey", null))
}
- printShippingLabel 함수 → 모든 정보가 있으면 주소를 출력, 주소가 없으면 throw문 실행
3. 안전한 타입 캐스트 ( as )
- 어떤 값을 지정한 타입으로 변환
- 대상 타입으로 변환할 수 없으면 null 반환
class Person(val firstName: String, val lastName: String){
override fun equals(o: Any?): Boolean {
val otherPerson = o as? Person ?: return false
return otherPerson.firstName == firstName && otherPerson.lastName == lastName
}
override fun hashCode(): Int =
firstName.hashCode() * 37 + lastName.hashCode()
}
fun main() {
val p1 = Person("Dmitry", "Jemerov")
val p2 = Person("Dmitry", "Jemerov")
println(p1 == null)
println(p1 == p2)
println(p1.equals(42))
}
- 엘비스 연산자와 함께 사용하여 쉽게 타입을 검사하고 캐스트 할 수 있음
4. null 아님 단언 (!!)
- 느낌표를 이중으로 사용 ( !! )
- null이 아닌 null이 될 수 있는 값을 null이 될 수 없는 값으로 강제 변환
null이 될 수 있는 인자를 null이 될 수 없는 타입으로 변환하기
fun ignoreNulls(str: String?) {
val strNotNull: String = str!!
println(strNotNull.length)
}
fun main() {
ignoreNulls(null)
}
- null에 사용할 시 NPE가 발생함
- 단언문을 선언한 곳에서 오류가 발생함
- ignoreNulls 함수는 null이 될 수 있는 값을 인자로 받기 때문에 오류가 발생하지 않음
- 코틀린 설계자들은 컴파일러가 검증할 수 없는 단언 대신 다른 좋은 방법을 사용하도록 설계함
단언문이 좋은 해결책인 경우
- 어떤 함수에서 null에 대한 검증을 했더라고 다른 함수에선 이를 인식할 수 없음
- 다른 함수에서 이미 검증했음에도 다른 함수에서도 검증하는 건 비효율적임
- null이 아닌 값을 전달받는 것이 확실한 상황에서는 단언문이 좋은 선택지가 될 수 있음
액션 클레스에서 단언문 사용하기
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.contents[index]
}
}
- 단언문을 사용하지 않으려면
val value = list.selectedValue ?: return 등을 사용해 null이 아닌 값을 획득해야 함
- 단언문을 사용해서 발생한 예외의 스택 트레이스는 파일의 몇 번째 줄에서 오류가 발생했는지는 알려주지만 어떤 식에서 발생했는지는 안알려주기 때문에 단언문을 한줄에 여러개 사용하는 것은 지양해야 함
person.company!!.address!!.country 같은 형식 지양
5. let 함수
null이 아닌 값만 인자로 받는 함수에 null이 될 수 있는 값을 넘기려면?
- 코틀린 컴파일러는 이를 허용하지 않음
- 하지만 표준 라이브러리에 이를 도와주는 함수 let이 있음
- 안전한 호출과 함께 사용하면 null 검사와 그 결과를 다른 함수에 넘기는 작업을 간단하게 처리할 수 있음
null이 될 수 있는 값을 null이 아닌 값을 받는 함수에 넘기기
null 검사를 통해 인자를 넘기기
fun sendMailTo(email: String) {
println("Sending email to $email")
}
fun main() {
val email: String? = "foo@bar.com"
sendMailTo(email)
}
- 이 경우 null 검사를 하지 않았기 때문에 에러가 발생함
if (email != unll) sendEmailTo(email) 를 사용하면 오류가 발생하지 않음
let 함수로 null 검사 생략
fun sendMailTo(email: String) {
println("Sending email to $email")
}
fun main() {
var email: String? = "foo@bar.com"
email?.let { sendMailTo(it) }
email = null
email?.let { sendMailTo(it) }
}
- let은 자신의 수신 객체를 인자로 전달 받은 람다에 넘김
- 수신 객체가 null이 아닌 경우에만 람다를 호출함
- 여러 값이 null인지 검사해야 하는 경우 let을 사용하기 보단 if문으로 null 검사를 수행하는 것이 가독성이 더 좋음