null
참조의 위험을 제거하는 것을 우선 목표로 한다.null
을 체크했던 기존 언어와는 달리, 코틀린은 컴파일 환경에서 null
을 체크하기 때문에 훨씬 안전하다.🙋 컴파일? 런타임?
컴파일 타임은 작성한 소스 코드를 기계어로 번역하고, 사용 가능한 프로그램으로 만드는 과정을 의미한다.
런타임은 컴파일 타임을 거친 프로그램을 실행하고 있는 시간을 의미한다.
일반적으로 런타임 보다는 컴파일 타임에서 오류를 잡아내는 것이 좋은 디버깅이라고 이야기한다.
앱을 이용하다가 뜬금없이 종료되는 것보다는, 빌드 중 오류를 잡고 넘어가는 것이 더 안전하기 때문이다.
null
참조를 최우선적으로 막는다고 하더라도 어쩔 수 없이 NPE
가 발생하는 경우가 있다.Java
라이브러리를 사용하는 경우, Java
는 Non-nullable
타입이 없기 때문에 자바 라이브러리를 사용할 경우 어쩔수 없이 nullable
타입으로 변환된다.throw
키워드를 사용, 개발자가 임의로 NPE
를 발생시키는 경우!!
연산자를 이용하고 해당 변수 혹은 함수에 접근할 경우nullable
타입과, non-nullable
타입을 분리하고 여러 연산자와 스마트 캐스트를 통해 null
관련 문제를 효과적으로 다룰 수 있도록 설계되었다.null
을 허용하지 않는다는 것이다.nullable
하게 선언하고 싶다면, ?
기호를 붙여 nullable
한 프로퍼티라는 것을 명시해야 한다.// Non-nullable Property
val number: Int = 30
var str: String = "Hello"
// Nullable Property
val number2: Int? = null
var str2: String? = null
// 컴파일 에러 발생
val number3: Int = null
?
기호를 명시하지 않고 null
을 할당하려 한다면, 바로 컴파일 에러가 발생하게 된다.null
허용 여부에 따라 서로 다른 자료형임을 확실하게 이해해야 한다.if-else
조건문을 이용하여 분기처리 하는 것이다.val b: String? = "Kotlin"
if (b != null && b.length > 0) {
print("String of length ${b.length}")
} else {
print("Empty string")
}
b
가 null
이면 비어있다는 것을 알리는 단순한 코드이다.null
이 할당되어 있을 가능성이 있는 변수를 검사하고, 안전하게 호출하도록 도와주는 연산자이다.?.
기호를 붙여 사용한다.null
여부를 체크하여 null
이면 그대로 null
을 리턴하고, 아니면 연산을 실시한다.// safe call
fun main() {
var str: String? = null
// checking for null
val len = if (str != null) str.length else -1
val len2 = str?.length
println("str: $str, length: ${len}")
println("str: $str, length2: ${len2}")
}
/* RESULT
str: null, length: -1
str: null, length2: null */
len
의 경우 if-else
문을 사용하여 null
인지, 아닌지를 체크했지만, len2
의 경우 safe call
을 이용해 체크한 것을 확인할 수 있다.safe call
은, 복잡하게 얽혀져있는 값을 가져올 때도 유용하게 사용할 수 있다.val name = bob?.department?.head?.name
null
임이 체크되면, 앞의 요소와 뒤의 요소에 상관없이 곧바로 null
을 리턴한다.null
이 아닌 값에 대해서만 연산을 수행할 때는 let
키워드와 함께 사용할 수 있다.val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
item?.let { println(it) } // prints Kotlin and ignores null
}
safe call
과 함께 사용할 때 굉장히 유용한 연산자이다.nullable
한 변수 b
를 사용할 때, b
가 null
이 아니라면 어떤 값을 할당하고, null
이면 -1을 할당하길 원한다면 어떻게 해야할까?if-else
조건식을 활용해 할당하는 방법이 있다.null
을 체크할 수 있다.?:
기호를 붙여 사용한다.fun main() {
var str: String? = null
// checking for null
val len = if (str != null) str.length else -1
val len2 = str?.length ?: -1
println("str: $str, length: ${len}")
println("str: $str, length2: ${len2}")
}
/* RESULT
str: null, length: -1
str: null, length2: -1 */
?:
만 추가했을 뿐인데, null
여부에 따라 다른 값을 출력하는 것을 확인할 수 있다.str
에 null
이 할당되어 있지 않았다면 str
의 길이를 출력했을 것이다.throw
, return
와 같은 모든 표현식이 허용되기 때문에, 필요한 상황에는 이런 식으로도 사용할 수 있다.fun foo(node: Node): String? {
val parent = node.getParent() ?: return null
val name = node.getName() ?: throw IllegalArgumentException("name expected")
// ...
}
null
이 발생할 가능성이 없다고 확신할 때 사용할 수 있다.!!
기호를 붙여 사용한다.fun main() {
val age: Int? = 26
val age2: Int = age // 에러 발생
val age3: Int = age!! // 에러 발생 X
}
nullable
을 처리하는 가장 간단한 방법이지만, 좋은 해결 방법은 절대 아니다.null
이 아니라고 해서, 미래에도 null
이 아닐까? !!
은 최대한 사용하지 않는 것이 좋다.safe call
, 엘비스 연산자를 적극적으로 이용하도록 하자.ClassCastException
오류 발생을 방지하기 위해 사용한다.fun main() {
val str: String? = "ABC"
val strToInt: Int? = str as Int // Error
println(strToInt)
}
String
으로 선언된 str
을 Int
로 강제 형변환을 일으키려 하면, 곧바로 ClassCastException
오류가 발생한다.fun main() {
val str: String? = "ABC"
val strToInt: Int? = str as? Int
val strToInt2: Int? = str as? Int ?: 0
println(strToInt)
println(strToInt2)
}
/* RESULT
null
0 */
null
을 리턴하는 것을 확인할 수 있다.strToInt2
의 경우처럼 엘비스 연산자를 함께 활용하면 유용하게 사용할 수 있다.null
을 세팅하는 방법이 있지만, 의미없이 메모리를 낭비시킬 뿐이다.null
여부를 체크해야 하므로 개발자에게 번거롭다.class Test() {
lateinit var dao: String // 지연 초기화
fun init() {
dao = "Dao"
}
}
fun main() {
val test = Test()
test.init()
print(test.dao)
}
lateinit
과 by lazy
키워드가 있는데, 이에 대한 자세한 글은 여기에 있다.참고 및 출처