[Kotlin] Nothing


모든 객체의 조상 Any

Any는 Root 타입으로 코틀린의 모든 타입은 Any를 상속한다.
이는 Java Object와 같은 개념이고 실제로 바이트코드로 컴파일 했을 때 Object와 일치한다.

val a:Any = "Hello, World!"
public final Object a = "Hello, World!"

"Hello, World!" 는 String 타입이지만 kotlin은 Any, java는 Object를 상속하고 있기 때문에 String을 Any에 대입할 수 있다.



코틀린에서 void Unit

함수의 리턴 타입을 Unit으로 지정하게 되면 Java에서 void 처럼 실제로 리턴 타입이 없어야 하고 명시적으로 return을 작성하지 않아도 된다.



코틀린이 void가 아닌 Unit을 만든 이유는?

1) Unit은 싱글톤 인스턴스이다.

따라서 코틀린에서 Unit이라는 키워드는 타입이면서도 동시에 객체이다.

val unit: Unit = Unit

2) Unit은 객체이기도 하기 때문에 “코틀린의 모든 객체는 Any의 자식이다.” 라는 설명에 따라 Unit도 Any의 서브 클래스이다.

val unit: Any = Unit



Nothing? Any?

Nothing has no instances. You can use Nothing to represent "a value that never exists"

생성자도 접근할 수 없고 어떤 값도 얻을 수 없는데 Nothing은 어떤 용도로 활용하는지 알아보자.


Nothing이 필요한 이유

1) 함수가 리턴될 일이 없을 경우

Unit은 “아무런 값도 리턴을 하지 않는다.” 라는 의미다.
즉 리턴의 대상이 없을 뿐 리턴이라는 행위 자체를 하지 않는 것은 아니다.
반면 Nothing은 리턴이라는 행위 자체를 하지 않음을 의미한다.

fun infiniteLoop(): Nothing {
    while(true) {
        println("hello");
    }
}

위 코드는 무한루프이기 때문에 함수가 종료되지 않는다.
코틀린 컴파일러는 위 함수가 종료되지 않을 것이라는걸 유추할 수 있는데
그렇기 때문에 만약 리턴 가능한 함수에서 리턴 타입을 Nothing으로 정의할 경우 컴파일러가 에러를 발생시킨다.

즉 함수가 리턴되 경우가 없기에 Nothing을 쓰는 적절한 상황이다.


2) 예외를 던지는 함수의 리턴 타입

Nothing은 예외를 던지는 함수에서의 리턴 타입으로도 사용된다.

fun throwException(): Nothing {
    throw IllegalStateException()
}

함수에서 예외를 던지는 것은 정상적으로 종료되는 것이 아니기 때문에 함수가 리턴되었다고 보지 않는다.
따라서 함수가 리턴될 일이 없는 경우와 같은 상황이다.

코틀린의 모든 타입은 기본적으로 non-null 이다.
즉 타입이 null이 아님을 보장하고 만약 null 값을 가질 수 있는 경우 타입 뒤에 ? 을 붙여 Nullable 임을 명시해주면 된다.


함수의 리턴 타입이 Nothing? 인 경우

fun mayThrowAnException(throwException: Boolean): Nothing? {
    return if (throwException) {
        throw IllegalStateException()
    } else {
        println("EXception not thrown")
        null
    }
}

이렇게 선택사항이 늘어난다.

따라서 위 코드를 호출하게 되면 return 될 수 있는 값은 반드시 null 이다.

fun main() {
    val result = mayThrowAnException(true)
    if (result == null) { // Always true
        println("Ignored code")
    }
}


Expression (Nothing은 모든 타입의 서브 타입이다.)

val nullbleValue: String? = null 
val value = nullableString ?: throw IllegalStateException()

위 코드에서 nullbleValue은 null 값이 들어올 수 있는 변수이다.
value는 엘비스 연산자를 사용했기 때문에 nullableString이 null 이 아니면 값을 대입하고 null이면 Exception을 던진다.

엘비스 연산자에 대응되는 nullbleValueString? 타입이고,
throw IllegalStateException()Nothing 타입이라서 컴파일 에러가 날 것 같지만
위 코드는 정상적으로 컴파일이 가능하다.

그 이유는 Nothing이 모든 타입의 서브 클래스이기 때문이다.

위 코드의 value 변수에 대입되는 Expression은 항상 String 타입으로 평가된다.


val nullableValue: String? = null
val value: Int = nullableValue?.toInt() ?: return

위 코드에서는 throw 대신 return이 사용되었다.
return의 타입은 Nothing이다.

따라서 throw를 사용했던 예제와 유사하다.

코틀린에서 Any는 모든 타입의 조상이고 Nothing은 모든 타입의 자식이라고 했다.


Q. Nothing은 개발자가 새로 만든 클래스의 자식이기도 할까?
A. OK! 👌🏻

코틀린에서 제공하고 있던 모든 클래스 뿐만 아니라 내가 만든 클래스도 포함하여 모든 클래스의 서브타입으로 보면 된다.