코틀린에서 변수의 자료형을 선언하는 방법에는 타입 애노테이션과 타입 추론이 있습니다.
일반적으로 타입 애노테이션은 변수 이름 다음에 콜론(:)을 붙여 선언합니다.
다음 코드에서는 userName 변수를 String타입으로 선언하고 초기화 합니다.
val userName: String = "Jin"
그렇다면, 다음 코드와 같이 타입 애노테이션 없이 변수를 초기화하면 어떻게 될까요?
val userName = "Kazuya"
위 코드도 결과적으로 String 타입의 userName 변수가 초기화되어 선언됩니다.
코틀린에서는 컴파일러가 초기화되는 값의 타입을 변수의 타입으로 간주하여 선언해주는 타입 추론 기능이 있습니다.
val pi = 3.141592 // Type inferred : Double
val firstName = "Lee" // Type inferred : String
위와 같이, 일반적으로 부동 소수점 수인 pi
는 Double 타입으로 선언되고, 문자열 형태인 firstName
은 String으로 선언됩니다.
코틀린에서는 null 값을 가지는 변수에 대해 발생하는 NPE(NullPointerException)에러를 방지하기 위해, null 값을 지정할 수 있는 null 가능 타입을 선언할 수 있습니다. null 가능 타입은 타입 애노테이션 뒤에 물음표(?)를 붙여 선언합니다.
// without Nullable Type
val weight: Int = null
println(weight) // Kotlin: Null can not be a value of a non-null type Int
// with Nullable Type
val weight: Int? = null
print(weight) // output: null
앞서 null 가능 타입은 불필요한 null 포인터 예외를 막기 위해서 사용한다고 언급했었습니다.
만약 다음 코드와 같이 nullable 변수를 split 함수를 통해 새로운 배열을 만들려고 한다면 어떻게 될까요?
val name: String? = "Mishima heihachi"
val split = name.split(" ")
이렇게 코드를 작성한다면 다음과 같은 에러가 발생합니다.
Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
저희는 위 코드에서 "Mishima heihachi"라고 정확한 String 값을 지정해 주었지만, 컴파일러 입장은 조금 다릅니다.
컴파일러 입장에서는 정확한 값이 선언되더라도 null 값이 나올 수 있다는 불안감(?)이 찾아오게 되죠.
따라서, name
null이 아닐때만 split 함수 호출이 가능하도록 컴파일러를 안심시켜야합니다. 다음과 같은 코드로 말이죠.
if (name != null) {
val split = name.split(" ")
}
물론 이렇게 코드를 작성해도 되지만, 훨씬 효율적인 방법이 있습니다. 다음과 같이 안전 호출 연산자(?.)를 사용하는 것입니다.
val split = name?.split(" ")
위 코드로 보다 간결하게 컴파일러를 안심시킬 수 있습니다.
이 경우에도 name
이 null이 아닐 경우에만 split() 함수가 호출되어 split
에 저장할 수 있도록 합니다.
안전 호출 연산자의외에도 컴파일러를 안심시킬 수 있는 방법은 Not-null 단정자를 사용하는 것입니다. 이는 컴파일러에게 이 변수는 null이 무조건 나오지 않는다고 알려주는 연산자입니다. Not-null 단정자는 변수명 끝에 느낌표 두개(!!)로 나타냅니다.
val name: String? = "Lee"
val len = name!!.length
이 코드를 작성하면 컴파일러는 Null이 안나오는 변수인 변수라고 인지하고 해당 코드를 통과시키게 됩니다.
그러나 Not-null 단정자를 써놓고 변수의 값이 null이라면 런타임단에서 NPE에러가 발생하므로 주의해야 합니다.
val name: String? = null
val len = name!!.length
<Console>
Exception in thread "main" java.lang.NullPointerException
at MainKt.main(Main.kt:3)
null 가능 타입으로 변수를 지정하고, 만약 null 값에 대한 예외 처리를 하고 싶다면 엘비스 연산자를 사용하시면 됩니다.
엘비스 연산자는 물음표와 콜론을 붙인형태(?:)로 사용합니다.
val name: String? = "Lee"
println(name ?: "null 입니다."
<Console>
Lee
이 코드는 만약 왼쪽 항에 있는 name
값이 null 이라면 오른쪽 항에 있는 내용을 출력하고, null이 아니라면 name을 출력합니다.
따라서 엘비스 연산자는 다음과 같은 코드와 같은 의미를 지니고 있습니다.
if (name == null) {
println("null 입니다.")
} else {
println(name)
}