[Kotlin] 타입 시스템 (Any, Unit, Nothing)
코틀린에서는 원시 타입과 참조 타입(래퍼 타입, 포인터 변수)을 별도 타입으로 따로 구분하지 않는다.
- 예를 들어 java의 int와 Integer 같이 구분하지 않고, Int 하나로 쓴다.
- 널이 될 수 없는 타입은 컴파일 시 알아서 원시 타입으로 표현할 수 있는건 원시 타입으로 표현해주고, 메소드를 호출하는 등 래퍼 타입이어야 하는 경우 래퍼 타입으로 변환해준다.
- 널이 될 수 있는 타입의 경우 ``kt null``은 원시 타입에는 들어갈 수 없고, 참조 타입에만 들어갈 수 있으므로 무조건 래퍼 타입으로 컴파일된다.
타입 상한 : 제네릭의 타입 파라미터 T는 T?가 아니어도 널이 될 수 있는 타입이다.
- 제네릭 클래스(T)의 경우 T에 원시 타입을 지정하더라도 내부적으로는 항상 그에 대한 박스 타입을 사용한다. JVM이 타입 인자로 원시 타입을 허용하지 않기 때문.
- 타입 파라미터가 널이 아님을 확실히 해주기 위해서는 반드시 타입 상한(upper bound)을 정해주어야 한다.
```kt
fun <T> printHashCode(t: T) { // T는 Any?로 추론된다.
println(t?.hashCode()) // 따라서 ?. 를 사용해야 한다.
}
>>> printHashCode(null) // 에러 안남
null
```
```kt
fun <T: Any> printHashCode(t: T) { // T는 Any로 추론된다.
println(t.hashCode())
}
>>> printHashCode(null) // 에러남
error: type parameter bound for T in fun <T : Any> printHashCode(t: T): Unit
is not satisfied: inferred type Nothing? is not a subtype of Any
```
비교 시에는 묵시적 형변환 해주지 않는다.
묵시적 형변환 해주지 않기 때문에 주의해야 한다. 특히 ``kt Int``와 ``kt Long``을 비교할 때.
```kt
>>> val x = 1
>>> val list = listOf(1L, 2L)
>>> x in list
error: type inference failed.
```
연산자는 묵시적 형변환을 지원하도록 오버로딩 되어 있다.
```kt
val i: Int = 1024
val l: Long = i + 1024L
```
원시 타입 리터럴
```kt
Long : 10000L
Double : 0.12 1.2e-5
Float : 12.4F
0xDEADBEEF
0b101110
```
Any, Any? : 최상위 타입
- 자바에서는 참조 타입만 ``java Object``를 정점으로 하는 타입 계층에 포함되며, 원시 타입은 계층에 속해있지 않다.
- 코틀린에서는 ``kt Any``가 원시 타입을 포함한 모든 타입의 조상이다. 그래서 원시 타입을 ``kt Any``에 담을 수 있으며 담게되면 ``kt Any``는 참조 타입이므로 박싱된다.
- ``kt Any``는 ``kt java.lang.Object``에 대응하기는 하지만, ``kt toString(), equals(), hashCode()``를 제외한 다른 메소드(``java wait(), notify()`` 등)은 ``kt Any``에서 사용할 수 없다. 사용하려면 ``kt java.lang.Object``로 캐스트해야 한다.
Unit 타입 : void
반환 타입 없이 선언한 block body 함수는 자동으로 리턴 타입이 ``kt Unit``이다.
``kt Unit``은 ``java void``와 달리 타입이다. 따라서 타입 파라미터`` T``로 쓸 수 있다. ``kt Unit`` 타입에 속하는 값은 딱 하나 있으며, 그 이름도 ``kt Unit``이다.
리턴 타입이 ``kt Unit``인 함수는 묵시적으로 ``kt Unit``을 반환한다.
즉, 반환하는 값이 없는게 아니다. 이 것이 ``kt Nothing``과의 차이다.
* 함수형 프로그래밍에서 ``kt Unit``은 '단 하나의 인스턴스만 갖는 타입'을 의미한다.
Nothing 타입 : 엘비스 연산자의 우항에 들어가는 함수의 리턴 타입
```kt
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
>>> val company = Company("n", Address("seoul", "kor"))
>>> val address = company.address
>>> println(address.city)
Error: Only safe (?.) or non-null asserted (!!.) calls...
>>> val address = company.address ?: fail("No address")
>>> println(address.city)
seoul
```
- ``kt ?.``가 아니라 그냥 `` .``으로 사용할 수 있는 것은 엘비스 연산자 덕분이다.
- ``kt Nothing`` 타입은 "이 함수는 항상 실패하는 함수"라는 정보를 컴파일러에게 알려주기는 하지만, ?: 뒤에 다른 것을 적어도 그냥 .으로 연결 가능하다.
- 그럼에도 ``kt Nothing`` 타입을 사용해야 하는 이유는,
- 타입을 생략해서 함수의 리턴 타입이 ``kt Unit``이 되면, 다른 아무거나 타입을 리턴 타입으로 지정하는 경우 엘비스 연산자의 우항이 절대 실행되지 않더라도 스마트 캐스트가 동작해 ``kt address``의 타입이 ``kt Any``가 되어 버린다.
- 따라서 별도의 캐스팅이 필요하거나, null 아님을 다시 체크해줘야 한다.
```kt
fun fail(message: String): Unit {
throw IllegalStateException(message)
}
>>> val address = company.address ?: fail("No address")
>>> val i : Int = address
Error:(13, 19) Kotlin: Type mismatch: inferred type is Any but Int was expected
>>> println(address.city)
Error:(14, 21) Kotlin: Unresolved reference: city
```
그래서 엘비스 연산자를 사용할 때 우항에 적는 함수의 리턴 타입은 좌항과 동일(이 경우 ``kt Address``)하거나, ``kt Nothing``이어야만 한다
'Java Stack > Kotlin' 카테고리의 다른 글
[Kotlin] 제네릭 : 타입 파라미터 소거(erasure), inline 실체화(reified) (1) | 2017.12.08 |
---|---|
[Kotlin] 컬렉션과 배열 (0) | 2017.12.07 |
[Kotlin] Nullability 관련 연산자 (0) | 2017.12.06 |
[Kotlin] 수신 객체 지정 람다 : with / apply / let / run / takeif / also (1) | 2017.12.05 |
[Kotlin] 함수형 인터페이스(SAM)에 람다 사용하기 (2) | 2017.12.05 |