자바에서 Object가 클래스 계층의 최상위 타입이듯 코틀린에서는 Any 타입이 모든 널이 될 수 없는 타입의 조상타입입니다. 자바에서는 참조 타입만 Object를 정점으로 하는 타입 계층에 포함되며, 원시 타입은 그런 계층에 들어있지 않습니다. 하지만 코틀린에서는 Any가 Int 등의 원시 타입을 포함한 모든타입의 조상 타입입니다. Any는 널이 될 수 없는 타입이므로 코틀린에서 널을 포함하는 모든 값을 대입할 변수를 선언하려면 Any? 타입을 사용해야 합니다.
내부에서 Any 타입은 java.lang.Object에 대응합니다. 자바 메서드에서 Object를 인자로 받거나 반환하면 코틀린에서는 Any로 그 타입을 취급하고, 코틀린 함수가 Any를 사용하면 자바 바이트코드의 Object로 컴파일됩니다.
모든 코틀린 클래스에는 toString, equals, hashCode라는 세 메서드가 들어있는데 이는 Any에 정의된 메서드를 상속한 것입니다. 하지만 java.lang.Object에 있는 다른 메서드(wait나 notify 등)는 Any에 사용할 수 없기에 만약 그런 메서드를 호출하고 싶다면 Object 타입으로 값을 캐스트해야합니다.
코틀린 Unit 타입은 자바 void와 같은 기능을 합니다. 관심을 가질 만한 내용을 반환하지 않는 함수의 반환 타입으로 Unit을 쓸 수 있습니다.
// Unit을 명시
fun f(): Unit {
}
// Unit을 명시하지 않음
fun f() {
}
코틀린 함수의 반환 타입이 Unit이고 그 함수가 제네릭 함수를 오버라이드하지 않는다면 그 함수는 내부에서 자바 void 함수로 컴파일됩니다. 그런 코틀린 함수를 자바에서 오버라이드하는 경우 void를 반환 타입으로 해야합니다.
코틀린의 Unit이 자바 void와 명확하게 다른 점이 있습니다. Unit은 모든 기능을 갖는 일반적인 타입이며, void와 달리 Unit을 타입 인자로 쓸 수 있습니다. Unit 타입에 속한 값은 단 하나뿐이며, 그 이름도 Unit입니다. Unit 타입의 함수는 Unit 값을 묵시적으로 반환합니다. 이 두 특성은 제네릭 파라미터를 반환하는 함수를 오버라이드하면서 반환 타입으로 Unit을 쓸 때 유용합니다.
interface Processor<T> {
fun process(): T
}
class NoResultProcessor: Processor<Unit> { // Unit 타입을 지정
// Unit 타입을 반환하지만 타입을 지정할 필요는 없습니다.
override fun process() {
// 코드 작성
}
}
위의 코드에서 인터페이스의 시그니처는 process 함수가 어떤 값을 반환하라고 요구합니다. Unit 타입도 Unit 값을 제공하기 때문에 메소드에서 Unit 값을 반환하는 데는 아무 문제가 없습니다. 거기다가 컴파일러가 묵시적으로 return Unit을 넣어주기에 명시적으로 반환할 필요가 없습니다.
함수형 프로그래밍에서 전통적으로 Unit은 '단 하나의 인스턴스만을 갖는 타입'을 의미해 왔고 바로 그 유일한 인스턴스의 유무가 자바 void와 코틀린 Unit을 구분 하는 가장 큰 차이입니다.
코틀린에는 결코 성공적으로 값을 돌려주는 일이 없으므로 '반환 값'이라는 개념 자체가 의미 없는 함수가 일부 존재합니다. 예를 들어 테스트 라이브러리들은 fail이라는 함수를 제공하는 경우가 많은데 이 함수는 특별한 메시지가 들어있는 예외를 던져서 현재 텍스트를 실패시킵니다. 다른 예로는 무한 루프를 도는 함수도 결코 값을 반환하며, 정상적으로 끝나지 않습니다.
그런 함수를 호출하는 코드를 분석하는 경우 함수가 정상적으로 끝나지 않는다는 사실을 유용합니다. 그런 경우를 표현하기 위해 코틀린에는 Nothing이라는 특별한 반환 타입이 있습니다.
// 함수의 반환 타입으로 Nothing 지정
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
Nothing 타입은 아무 값도 포함하지 않습니다. 따라서 Nothing은 함수의 반환 타입이나 반환 타입으로 쓰일 타입 파라미터로만 쓸 수 있습니다.
Nothing을 반환하는 함수를 엘비스 연산자의 우항에 사용해서 전제 조건(precondition)을 검사할 수 있습니다.
// 엘비스 연산자 사용
val address = company.address ?: fail("No address")
이 예제는 타입 시스템에서 Nothing이 얼마나 유용한지를 보여줍니다. 컴파일러는 Nothing이 반환 타입인 함수가 결코 정상적으로 종료되지 않음을 알고 그 함수를 호출하는 코드를 분석할 때 사용합니다. 위의 예제에서 컴파일러는 company.address가 널인 경우 엘비스 연산자의 우항에서 예외가 발생한다는 사실을 파악하고 address의 값이 널이 아님을 추론할 수 있습니다.