6장 코틀린 타입 시스템
null 가능성 : nullability
NullPointerException을 피할 수 있게 돕기 위한 특성
type뒤에 ?를 붙이면 nullable해진다. nullabe타입은 몇몇 함수는 사용이 제한되며 nullable하지 않은 변수에 대입할 수 없다. 또한 ?를 쓰면 복잡한 if문을 쓰지 않아도 안전하게 호출 할 수 있다.
fun printAllCaps(s: String?){
val allCaps: String? = s?.toUpperCase()
// val len : Int = s?.length
println(allCaps)
}
엘비스 연산자는 null 대신 default값을 지정할 때 사용하는 연산자이다. 대입하는 변수에 ?: 를 쓰고 옆에 default값을 쓴다.
fun foo(s:String?){
val t : String = s ?: "empty"
println(t)
}
foo(null)
as?는 어떤 값을 지정한 타입으로 캐스트 하는데 변환할 수 없으면 null을 반환한다.
fun change(o: Any?){
val name = o as? String ?: "not String!!"
println(name)
}
change(22)
null이 아님을 단언하는 !!도 있다. null이 아니면 원래 연산을, null이면 NullPointerException을 발생시킨다. 아래의 코드결과로 NullPointerException이 발생한다. 항상 null이 아닌 값을 전달받는다면 !!쓰는것이 nullable을 쓰는것 보다 나을 수 있다.
fun exact(s: String?){
val exactS = s!!
println(exactS.length)
}
exact(null)
긴 식이 있고 그 값이 null이 아닐 때 수행해야 하는 로직이 있을 때 let함수를 사용해서 null 이 될 수 있는 식을 쉽게 다룰 수 있다. null이면 수행하지 않는다.
var email : String? = "su84053@gmail.com"
email?.let { println(it) }
코틀린은 클래스 안의 null이 될 수 없는 프로퍼티를 생성자 안에서 초기화하지 않고 특별한 메서드 안에서 초기화할 수 없다. 그래서 nullable한 타입을 사용해야 하며 이러한 프로퍼티 접근에 null 검사를 하거나 !!를 써야한다. 그래서 나중에 초기화할 수 있는 lateinit변경자를 사용해서 나중에 초기화할 수 있다. 이는 항상 var여야 한다. 아래 코드를 실행하면 컴파일 에러가 아닌 런타임에러로 lateinit property가 초기화되지 않았다고 나온다
lateinit var tree : String
fun main(args: Array<String>) {
println(tree)
플랫폼 타입은 코틀린이 null 관련 정보를 알 수 없는 타입이다. 컴파일러는 모든 연산을 허용하고 책임은 프로그래머에게 있다. null이 될수 있으면 안전성 검사를 해도 되며 아니면 값을 직접 사용하면 된다. 자바 API를 다룰때 대부분의 라이브러리는 null 관련 annotation을 안 쓰므로 null일 수 있기 때문에 nullabe한지 확인하고 null검사를 추가해야 한다.
원시 타입 Int, Boolean등은 자바의 원시타입 처럼 null이 될수 없다. Int?, Boolean?등 nullable한 원시 타입들은 자바의 원시 타입이 될 수 없고 자바의 레퍼 타입으로 컴파일된다. 아래의 코드에서 java 클래스를 다룰 때 java.lang.Integer로 저장된다.
data class Person(val name: String, val age: Int? = null) {
fun isOlderThan(other: Person): Boolean? {
if (age == null || other.age == null) {
return null
}
return age > other.age
}
}
println(Person("Sam", 35).isOlderThan(Person("Amy", 42)))
println(Person("Sam", 35).isOlderThan(Person("Jane")))
숫자 변환은 자동변환 하지 않고 직접 변환 메서드를 호출해야 한다.
val i :Int = 0
val j : Long = i.toLong()
자바의 최상위 타입 object처럼 코틀린에는 Any가 있다. 원시 타입들의 조상 타입이다.
Unit 타입은 자바의 void와 같은 기능으로 관심을 가질 만한 내용을 반환하지 않는 함수의 반환 타입으로 쓴다. 자바의 void와 다른점은 Unit은 타입 인자로 쓸 수 있다.
fail이라는 특별한 메시지가 들어있는 예외를 던져서 현재 테스트를 실패시키는 함수가 있으며 이러한 함수를 호출할 경우 표현하기 위한 타입이 Nothing 타입이다. Nothing 타입은 아무 값도 포함하지 않고 함수의 반환 타입이나 반환 타입으로 쓰일 파라미터로만 쓸 수 있다.
fun fail(message: String): Nothing{
throw IllegalStateException(message)
}
val address = email ?: fail("no email address")
컬랙션에도 null값을 넣을 수 있는지 여부도 컬렉션의 변수 타입 뒤에 ?를 붙이면 ?타입으 변수를 저장할 수 있다. 컬랙션 자체에도 ?를 주어 nullable하게 할 수 있으며 전자는 컬랙션 안의 요소들이 nullable이지만 후자는 컬랙션 전체가 nullable이다.
코틀린의 컬랙션은 자바의 컬랙션과 달리 읽기 전용, 변경 가능한 컬랙션을 구분하였다. 아래의 코드처럼 source는 add등 변경이 불가능하다.
val source : Collection<Int> = arrayListOf(1,2,3,4)
val dest : MutableCollection<Int> = arrayListOf()
dest.addAll(source)
자바에서 선언한 컬렉션 타입의 변수를 코틀린에서는 플랫폼 타입으로 본다. 따라서 변경 가능성에 대해 알 수 없어서 읽기 전용, 변경 가능한 컬렉션 어느 쪽으로도 다룰 수 있다. 반영해야 하는 것은 컬렉션이 null이 될 수 있는지, 컬렉션의 원소가 null이 될 수 있는지, 오버라이드하는 메서드가 컬렉션을 변경할 수 있는지를 확인하고 컬렉션 타입에 반영해야 한다.