null
은 ‘값이 부족하다(lack of value)’는 것을 나타낸다.
null
이라는 것은 값이 설정되지 않았거나 제거되었다는 것null
을 리턴한다는 것은 여러 의미를 가질 수 있다.
String.toIntOrNull()
- String
을 Int
로 적절히 변환할 수 없는 경우 null
을 리턴Iterable<T>.firstOrNull(()→ Boolean)
- 주어진 조건에 맞는 요소가 없을 때 null
을 리턴null
은 명확한 의미를 가져야 이를 처리하는 사람이 적절히 핸들링할 수 있다.throw
null
을 안전하게 처리하는 방법 중 널리 알려진 방법으로 다음 두 가지가 있다.
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
문제가 똑같이 발생해버린다.nul
l일 수 없다는 것을 확신해서 지금 !!
을 사용하더라도, 앞으로 미래에도 계속 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)
}