본문 바로가기

코틀린

[코틀린] Inline 클래스

안드로이드 개발하는 kancho입니다.

이번 포스팅에서는 코틀린의 Inline(인라인) 클래스에 대해 알아보고자 합니다.

 

'코틀린 완벽 가이드' 책을 참고하였습니다.

 

 

 

코드를 작성하다 보면 Wrapper(래퍼) 클래스를 만드는 일이 생길 수 있다.

Wrapper 클래스는 런타임 부가 비용이 발생한다.

코틀린에서는 이러한 부가 비용을 줄이기 위해 Inline 클래스를 사용할 수 있다.

 

Inline 클래스(값 클래스)

원시 타입의 값과 마찬가지로 부가 비용 없이 쓸 수 있기 때문에 value class(값 클래스)라고도 한다.

 

클래스 이름 앞에 value class를 붙여서 정의한다.

 

코틀린 1.3에서는 inline 키워드를 사용했지만 자바에 값 클래스가 생겨 

코틀린 1.5에서 value로 키워드가 변경되었다.

 

// JVM을 사용하는 경우에는 JvmInline 어노테이션을 반드시 붙여야 한다.
@JvmInline
value class Dollar(val amount: Int)

 

 

인라인 클래스의 주 생성자에는 불변 프로퍼티를 하나만 선언해야 한다.

런타임에 별도의 래퍼 객체를 생성하지 않고 이 프로퍼티의 값으로 표현된다.

 

뒷받침하는 필드나 위임 객체 프로퍼티를 사용할 수 없다.

또한 초기화 블록에서 프로퍼티를 조작하는 등의 작업은 할 수 없다.

 

/*
inline 클래스도 내부 함수와 프로퍼티를 가질 수 있다.
코틀린 컴파일러는 한 프로퍼티만 인라인 하도록 지원하기 때문에 
주 생성자 이외의 프로퍼티는 상태를 포함할 수 없다.
*/
@JvmInline
value class Dollar(val amount: Int) {
    init {
        println(amount)
    }
    
    fun add(d: Dollar) = Dollar(amount + d.amount)
    val isDebt get() = amount < 0
    
    // error: Value class cannot have properties with backing fields
    val notAvailable = true
}

fun main() {
    println(Dollar(15).add(Dollar(20)).amount)  // 35
    println(Dollar(-100).isDebt)    // true
}

 

 

컴파일러는 최적화를 위해 가능하면 값을 박싱 하지 않은 상태로 사용하려고 한다.

박싱 하지 않은 값을 사용할 수 없는 경우 인라인 되지 않는 형태로 클래스를 사용한다.

 

fun safeAmount(dollar: Dollar?) = dollar?.amount ?: 0

fun main() {
    println(Dollar(15).amount)  	// inline
    println(Dollar(15)) 		// Any?로 사용되어 inline 되지 않음
    println(safeAmount(Dollar(15))) 	// Dollar?로 사용되어 inline 되지 않음
}

 

 

 

부호 없는 정수

코틀린 1.5부터 표준 라이브러리로 도입된 부호 없는 정수(unsigned integer)는 

코틀린에 내장된 부호 있는 타입을 기반으로 인라인 클래스로 작성된 타입이다.

 

타입의 이름은 부호 있는 정수 타입의 이름 앞에 U를 붙여서 만든다.

 

부호 없는 정수의 타입과 크기, 범위는 아래와 같다.

 

UByte -> unsigned 8-bit integer, ranges from 0 to 255
UShort -> unsigned 16-bit integer, ranges from 0 to 65535
UInt -> unsigned 32-bit integer, ranges from 0 to 2^32 - 1
ULong -> unsigned 64-bit integer, ranges from 0 to 2^64 - 1

 

타입은 리터럴이 대입될 변수의 타입에 따라 결정되고 타입 지정이 안된 경우 크기에 따라 추론된다.

 

fun main() {
    val uByte: UByte = 1u	// 명시적 UByte
    val uShort: UShort = 100u	// 명시적 UShort
    val uInt = 1000u		// UInt 추론
    val uLong: ULong = 1000u	// 명시적 ULong
    val uLong2 = 1000uL		// L이 붙어 명시적 ULong
}

 

 

unsigned integer는 아래와 같이 사용할 수 있다.

 

fun main() {
    // unsigned, signed integer 타입은 호환되지 않는다.
    val long: Long = 1000uL 	// error

    // 타입 변환은 가능하다.
    println(1.toUByte())    	// Int -> UByte
    println((-100).toUShort())  // Int -> UShort 
    println(200u.toByte())  	// UInt -> Byte
    
    // 연산자도 사용 가능하다.
    println(1u + 2u)
    println(3u * 2u)
    println(7u % 3u)
    
    // error: Conversion of signed constants to unsigned ones is prohibited
    println(1u + 2)
    
    // 증가, 감소, 복합 대입 연산자 사용
    var uInt: UInt = 1u
    ++uInt
    uInt -= 3u

    // 비트 연산을 지원한다.
    val ua: UByte = 67u
    val ub: UByte = 139u
    println(ua.inv())       // 188
    println(ua xor ub)      // 200
    
    // <, >, <=, >=, ==, != 연산 사용 가능
    println(1u < 2u)        // true
    println(2u >= 3u)       // false
}

 

 

unsigned array를 표현하는 UByteArray, UShortArray, UIntArray, ULongArray 보조 타입도 있지만 아직 완전한 단계는 아니라 사용하는 데에 주의해야 한다.

'코틀린' 카테고리의 다른 글

[코틀린] 제네릭  (0) 2022.11.13
[코틀린] 상속(Inheritance)  (0) 2022.11.06
[코틀린] Data 클래스  (1) 2022.10.23
[코틀린] Enum 클래스  (0) 2022.10.21
[코틀린] 람다와 고차함수  (0) 2022.10.17