본문 바로가기

개발/Kotlin

Item 49. Consider using inline classes

 함수 뿐만 아니라 하나의 값만 가지고 있는 객체는 inline으로 나타낼 수 있다.

 

inline class Name(proivate val value: String) {
	// ...
}

이러한 클래스는 가능한 경우에 언제든 변경될 수 있다.

 

val name: Name = Name("Marcin")

// During compilation replaced with code similar to:
val name: String = "Marcin"

 

inline class Name(private val value: String) {
	// ...
    
    fun greet() {
    	print("Hello, I am $value")
    }
}

// Code
val name: Name = Name("Marcin")
name.greet()

// During compilation replaced with code similar to:
val name: String = "Marcin"
Name.`greet-impl`(name)

 

 그래서 우리는 인라인 클래스는 특정 타입을 성능 저하 없이 wrapping 할 때 사용할 수 있다. inline class 주로 사용하는 때는 두 가지가 있다.

 

- 측정의 단위를 나타낼 때

- 타입을 잘못 사용되는 경우를 방지할 때

 

interface Timer {
	fun callAfter(time: Int, callback: () -> Unit)
}

  여기서 time의 단위는 무엇일까? milliseconds, seconds 등등 다양할 수 있다. 따라서 잘못 사용될 가능성이 있다.

 

interface Timer {
	fun callAfter(timeMillis: Int, callback: () -> Unit)
}

 그래서 이렇게 property name을 명시적으로 수정하면 전보다는 낫지만 함수를 사용할 때 이름이 노출되지 않는 경우도 있다.

그리고 조금만 호출이 복잡해져도 쉽게 무시될 가능성이 있다.

 

interface User {
	fun decideAboutTime(): Int
    fun wakeUp()
}

interface Timer {
	fun callAfter(timeMillis: Int, callback: () -> Unit)
}

fun setUpUserWakeUpUser(user: User, timer: Timer) {
	val time: Int = user.decideAboutTime()
    timer.callAfter(time) {
    	user.wakeUp()
    }
}

그래서 inline class를 사용하면

inline class Minutes(val minutes: Int) {
	fun toMillis(): Millis = Millis(minutes * 60 * 1000)
}

inline class Millis(val milliseconds: Int) {
}

interface User {
	fun decideAboutTime(): Minutes
    fun wakeUp()
}

interface Timer {
	fun callAfter(timeMillis: Millis, callback: () -> Unit)
}

fun setUpUserWakeUpUser(user: User, timer: Timer) {
	val time: Int = user.decideAboutTime()
    timer.callAfter(time) {	// ERROR: Type mismatch
    	user.wakeUp()
    }
}

fun setUpUserWakeUpUser(user: User, timer: Timer) {
	val time: Int = user.decideAboutTime()
    timer.callAfter(time.toMillis()) {
    	user.wakeUp()
    }
}

 이는 단위를 계산할 때 유용하게 사용이 되고, 이것을 통해 DSL스러운 extention을 사용할 수 있다.

 

inline val Int.min
	get() = Minutes(this)
    
inline val Int.ms
	get() = Millis(this)
    
val timeMin: Minutes = 10.min

 

 잘못 사용하는 경우를 줄여주는 예시를 보자.

@Entity(tableName="grades")
classGrades(
	@ColumnInfo(name = "studentId")
	val studentId: Int,
	@ColumnInfo(name = "teacherId")
	val teacherId: Int,
	@ColumnInfo(name = "schoolId")
	val schoolId: Int,
	// ...
)

각 Id 값이 전부 Int형이기 때문에 자칫 잘못해서 섞어쓸 가능성이 많다.

inline class StudentId(val studentId: Int)
inline class TeachetId(val teacherId: Int)
inline class SchoolId(val schoolId: Int)

@Entity(tableName="grades")
classGrades(
	@ColumnInfo(name = "studentId")
	val studentId: Int,
	@ColumnInfo(name = "teacherId")
	val teacherId: Int,
	@ColumnInfo(name = "schoolId")
	val schoolId: Int,
	// ...
)