반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[이펙티브 코틀린] Item47. 인라인 클래스의 사용을 고려하라 본문

Kotlin/이펙티브 코틀린

[이펙티브 코틀린] Item47. 인라인 클래스의 사용을 고려하라

어리둥절범고래 2024. 1. 21. 17:28
반응형

기본 생성자 프로퍼티가 하나인 클래스 앞에 inline을 붙이면, 해당 객체를 사용하는 위치가 모두 해당 프로퍼티로 교체됩니다.

 

프로퍼티가 2개 이상이면 안된다.

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

// 코드
val name: Name = Name("Marcin")

// 컴파일 때 다음과 같은 형태로 바뀐다.
val name: String = "Marcin"

또한, inline 클래스의 메서드는 모두 정적 메서드로 만들어집니다.

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

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

// 컴파일 때 다음과 같은 형태로 바뀐다.
val name: String = "Marcin"
Name.`greet-impl`(name)

인라인 클래스는 다른 자료형을 래핑해서 새로운 자료형을 만들 때 많이 사용되며, 어떠한 오버헤드도 발생하지 않습니다.

inline 클래스는 다음과 같은 상황에서 많이 사용됩니다.

 

  • 측정 단위를 표현할 때
  • 타입 오용으로 발생하는 문제를 막을 때

 

 

측정 단위를 표현할 때

 

프로젝트를 진행할 때, 측정 단위에 혼동이 생긴다면 큰 문제를 초래할 수 있습니다.

가장 좋은 해결 방법은 타입에 제한을 거는 것이며, 제한을 걸면 제네릭 유형을 잘못 사용하는 문제를 줄일 수 있습니다.

이때 코드를 더 효율적으로 만들려면, 다음과 같이 인라인 클래스를 활용합니다.

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: Minutes = user.decideAboutTime()
    timer.callAfter(time) { // 오류: Type mismatch
        user.wakeUp()
    }
}

 

타입 오용으로 발생하는 문제를 막을 때

 

SQL 데이터베이스는 일반적으로 ID를 Int 자료형으로 저장합니다.

만약 실수로 잘못된 값을 넣는다면, 문제가 발생했을 때 오류를 발생하지 않으므로 문제를 찾는 게 힘들어집니다.

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

이러한 문제를 미리 막으려면, Int 자료형의 값을 inline 클래스로 래핑합니다.

inline class StudentId(val id: Int)
inline class TeacherId(val id: Int)
inline class SchoolId(val id: Int)

@Entity(tableName = "grades")
class Grades(
    @ColumnInfo(name = "studentId")
    val studentId: StudentId,
    @ColumnInfo(name = "teacherId")
    val teacherId: TeacherId,
    @ColumnInfo(name = "schoolId")
    val schoolId: SchoolId
    //...
)

 

인라인 클래스와 인터페이스

 

인라인 클래스도 다른 클래스와 마찬가지로 인터페이스를 구현할 수는 있지만, inline으로 동작하지 않습니다.

인터페이스를 통해서 타입을 나타내려면, 객체를 래핑해서 사용해야하기 때문입니다.
인터페이스를 구현하는 인라인 클래스는 아무 의미가 없습니다.

 

 

typealias

 

typealias를 사용하면, 타입에 이름을 붙여줄 수 있으며, 타입이 길고 반복으로 사용해야 할 때 유용합니다.

typealias ClickListener = (view: View, event: Event) -> Unit

class View {
    fun addClickListener(listener: ClickListener) {}
    fun removeClickListener(listener: ClickListener) {}
    //...
}

 

하지만 typealias를 혼용해서 사용하면, 문제가 발생했을 때 문제를 찾는 것을 어렵게 만들 수 있습니다.

typealias Seconds = Int
typealias Millis = Int

fun getTime(): Millis = 10
fun setUpTimer(time: Seconds) {}

fun main() {
    val seconds: Seconds = 10
    val millis: Millis = seconds // 컴파일 오류가 발생하지 않는다.
    
    setUpTimer(getTime())
}

 

 


정리

 

  • 인라인 클래스를 사용하면 성능적인 오버헤드 없이 타입을 래핑할 수 있다.
  • 인라인 클래스는 타입 시스템을 통해 실수로 코드를 잘못 작성하는 것을 막아주므로, 코드의 안정성을 향상시켜 준다.
  • 의미가 명확하지 않은 타입, 특히 여러 측정 단위들을 함께 사용하는 경우에는 인라인 클래스를 꼭 활용하라.
반응형