본문 바로가기

코틀린

코틀린(Kotlin) - 원시 타입(primitive type)

원시 타입(Primitive Type) : Int, Boolean 등...

코틀린은 원시 타입(primitive type)과 래퍼 타입(wrapper type)을 따로 구분하지 않습니다.

즉, 자바처럼 Integer와 int로 구분하지 않고 Int 하나만 존재하는 형태입니다.

 

/* 정수 표현 예제 */
val i: Int = 1
val list: List<Int> = listOf(1, 2, 3)

 

위 예제와 같이 Integer 또는 int 구분 없이 Int 하나로 사용하면 됩니다.

 

primitive와 wrapper 타입이 같다면 코틀린은 항상 객체로 표현하는 걸까요?

그렇다면 너무나 비효율적이지 않을까요? 

 

라는 의문이 생기는데요, 다행히 코틀린은 그러지 않습니다.

 

코틀린의 타입은 컴파일 시 자바의 primitive 또는 wrapper 타입으로 자동 변환됩니다!

 

런타임 시점에 숫자 타입은 가능한 한 가장 효율적인 방식으로 표현이 되며, 예를 들면 대부분의 경우 코틀린의 Int 타입은 자바의 int 타입으로 컴파일됩니다.

다만, Collection이나 Generic을 사용하는 경우에는 wrapper로 변경되고 그 외 나머지 경우에는 int로 변경됩니다.

예를 들어, Int 타입의 Collection을 만드는 경우 래퍼 타입에 해당하는 java.lang.Integer 객체가 들어가게 되며 이 부분은 자바에서 프로그래밍을 할 때와 동일합니다.

 

널이 될 수 있는 원시 타입(Primitive Type) : Int?, Boolean? 등...

null 이 될 수 있는 타입의 경우 자바에서는 표현할 수 없기 때문에 이런 경우에는 자바의 wrapper type으로 컴파일이 됩니다.

 

/* 코틀린 null이 될 수 있는 Int? 타입 */
val price: Int? = null

/* 자바로 변환된 형태 */
Integer price = (Integer)null;

 

Generic 클래스의 경우에도 wrapper type을 사용합니다.

예를 들면, 아래와 같은 코드는 원소에 null 값이나 null이 될 수 있는 타입을 전혀 사용하지 않았음에도 만들어지는 리스트는 wrapper인 Integer로 이뤄진 리스트가 됩니다.

 

/* 코틀린의 Int Collection */
val listOfInts = listOf(1, 2, 3)

/* 자바로 변환된 형태 */
List list = CollectionsKt.listOf(new Integer[]{1, 2, 3});

 

이렇게 컴파일되는 이유는 JVM에서 제네릭을 구현하는 방법 때문인데요, JVM은 타입 파라미터로 원시 타입(primitive type)을 허용하지 않기 때문에 따라서, 자바나 코틀린 모두 제네릭 클래스는 항상 래퍼 타입을 사용해야 하는 이유입니다.

 

숫자 변환

코틀린과 자바의 가장 큰 차이점 중 하나는 숫자를 변환하는 방식입니다.

코틀린은 한 타입의 숫자를 다른 타입의 숫자로 자동 변환하지 않습니다.

변환하려는 타입이 허용하는 숫자의 범위가 원래 타입의 범위보다 넓은 경우조차도 자동 변환은 불가능합니다.

즉, Int값을 Long에 대입하면 컴파일 에러가 발생합니다.

 

val i = 1
val l: Long = i //Error: type mismatch 컴파일 에러 발생

 

대신 Boolean을 제외한 모든 primitive type에 대한 변환 메소드를 제공하고, 직접 변환 메소드를 호출해야 합니다.

 

val i = 1
val l: Long = i.toLong()

 

이런 변환 함수는 toByte(), toShort(), toChar(), toLong(), toInt() 등...이 있고, 양방향 변환 함수도 모두 제공됩니다.

즉, 어떤 타입을 표현 범위가 더 넓은 타입으로 변환할 수도 있고 ( 예 : Int.toLong() ),

범위가 더 좁은 타입으로 변환하면서 값을 벗어나는 경우에는 일부를 잘라내는 함수 ( 예 : Long.toInt() )도 있습니다.

 

숫자 리터럴(L 접미사 Long, F 접미사 Float...) 도 지원하기 때문에 리터럴을 붙이는 경우에는 변환 함수를 호출할 필요는 없습니다.

 

문자열을 숫자로 변환할 때도 변환 함수를 사용할 수 있으며, 포맷에 맞지 않는 경우에는 역시 NumberFormatException이 발생하게 됩니다.

 

>>> "123".toInt()
123

>>> "123abc".toInt()
NumberFormatException 발생

 

최상위 타입 : Any, Any?

자바에서 Object가 클래스 계층의 최상위 타입이듯 코틀린에서는 Any타입이 모든 타입의 최상위 타입입니다.

Any 타입은 컴파일 시 java.lang.Object로 변환됩니다.

Any가 최상위 타입이라 해도 null은 허용하지 않기 때문에 null이 들어가야 하는 곳에는 Safe call을 사용한 Any? 로 사용해야 합니다.

 

자바 메소드에서 Object를 파라미터로 받거나 반환하는 경우 코틀린에서는 Any로 그 값의 타입을 취급합니다.

 

코틀린 Unit 타입 = 자바 void

코틀린 Unit 타입은 자바 void와 같은 기능을 합니다.

void와 동일하게 리턴 값이 없는 메소드의 리턴 타입으로 Unit을 쓸 수 있고, 인자로도 사용할 수 있습니다.

 

/* Unit 반환 타입 명시 */
fun init() : Unit { ... }

/* Unit 반환 타입 생략 */
fun init() { ... }

/* Unit 타입을 인자로 사용 */
class NoResultProcessor : Processor<Unit> {
   override fun process() { //Unit을 반환하지만 반환 타입을 생략해도 됌
      ...
   }
}

 

Nothing 타입 : 이 함수는 정상적으로 끝나지 않는다

Nothing type은 이 함수가 정상적으로 끝나지 않는다는 걸 명시적으로 표현하는 타입입니다.

예를 들면, 함수 중간에 throw 시키도록 한다면 해당 함수는 정상적으로 끝나지 않는 함수가 되겠죠?

이런 함수의 반환 type을 Nothing으로 하면 컴파일러가 정상 종료되지 않는 함수임을 미리 알 수가 있습니다.

Nothing 함수는 엘비스 연산자의 우항에 사용해서 전제 조건을 검사하는 데에 주로 사용됩니다.

 

아래는 Nothing 함수 여부에 따라 storeList의 널 체크 여부가 달라지는 예제입니다.

Nothing 함수를 우항에서 사용하게 되면 storeList의 널 체크 없이 값을 바로 호출할 수 있게 해 줍니다.

 

storeList 객체에 널 체크 필요
Nothing 반환 함수인 fail를 엘비스 연산자 우항에 사용하여 storeList의 널 체크 없이 값을 호출할 수 있다.