null은 ‘값이 부족하다(lack of value)’는 것을 나타낸다.
null이라는 것은 값이 설정되지 않았거나 제거되었다는 것null을 리턴한다는 것은 여러 의미를 가질 수 있다.
String.toIntOrNull() - String을 Int로 적절히 변환할 수 없는 경우 null을 리턴Iterable<T>.firstOrNull(()→ Boolean) - 주어진 조건에 맞는 요소가 없을 때 null을 리턴null은 명확한 의미를 가져야 이를 처리하는 사람이 적절히 핸들링할 수 있다.thrownull을 안전하게 처리하는 방법 중 널리 알려진 방법으로 다음 두 가지가 있다.
printer?.print()if (printer ≠ null) printer.print()val printerName1 = printer?.name ?: "Unnamed"
val printerName2 = printer?.name ?: return
val printerName3 = printer?.name ?: throw Error("Printer must be name")
null일 땐 출력하지 않기 등)require, check, assert 등이 이러한 공격적 프로그래밍을 위한 도구printer?.print() 같은 세이프 콜로 처리하면 이를 개발자에게 알리지 않고 코드가 그대로 진행된다.throw, !!, requireNotNull, checkNotNull 등을 활용할 수 있다.!!)을 사용하면 간단히 null을 처리할 수 있지만 자바에서의 null 문제가 똑같이 발생해버린다.null일 수 없다는 것을 확신해서 지금 !!을 사용하더라도, 앞으로 미래에도 계속 null이 아니라고 보장할 수는 없다.!! 연산자가 의미 있는 경우는 굉장히 드물다.
!! 사용은 피해야 한다고 말한다.
!! 연산자 사용 시 오류를 발생하도록 설정하고 있다.null 처리는 추가 비용이기에 필요한 경우가 아니면 nullability 자체를 피하는 것이 좋다.getOrNull)lateinit과 notNull 델리게이트를 사용null 대신 빈 컬렉션을 리턴@BeforeEach 내에서 초기화되는 프로퍼티들null이 아닌 타입으로 변환하는 것은 바람직하지 않다.lateinit 한정자를 통해 프로퍼티가 이후에 설정될 것임을 명시할 수 있다.class UserControllerTest {
private lateinit var dao: UserDao
private lateinit var controller: UserController
@BeforeEach
fun init() {
dao = mockk()
controller = UserController(dao)
}
}
lateinit 프로퍼티를 사용하려하면 예외가 발생하지만 근본적으로 nullalbe과 비교해서 차이가 있다.
!! 연산자로 언팩하지 않아도 된다.null을 사용하고 싶을 때, nullable로 만들 수도 있다.lateinit은 반드시 초기화될 거라 예샹되는 상황에 사용해야 한다.
Int, Long, Double, Boolean 등 기본 타입의 프로퍼티에는lateinit을 사용할 수 없다.
lateinit 보단 약간 느리지만 Delegates.notNull을 사용한다.class DoctorActivity: Activity() {
private val doctorId: Int by Delegates.notNull()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
doctorId = intent.extras.getInt(DOCTOR_ID_ARG)
}
}
onCreate 때 초기화하는 프로퍼티는 지연 초기화 형태로 다음과 같이 프로퍼티 위임을 사용할 수도 있다.class DoctorActivity: Activity() {
private val doctorId: Int by arg(DOCTOR_ID_ARG)
}