Kotlin 익명 클래스 (Anonymous class)

홍성덕·2024년 8월 1일

익명 클래스는 언제 필요할까?

interface Engine {
    fun start()
}

class Car(private val engine: Engine) {
    fun engineStart() {
        engine.start()
    }
}

fun main() {
    // Car 클래스의 engineStart()를 호출하고 싶다
}

만약 이러한 코드가 있다고 가정했을 때, main()에서 Car 클래스의 engineStart()를 호출하려면 먼저 val car = Car() 인스턴스를 생성해야 할 것이다. 그런데 Car 객체는 생성자 파라미터로 interface Engine 타입을 받고 있다. interface는 자체적으로 객체 생성이 불가능하기 때문에 Engine interface를 구현한 클래스를 만들고 그 클래스의 객체를 생성자로 전달해야 한다.

class EngineImpl: Engine {
    override fun start() {
        println("engine start")
    }
}

fun main() {
    // val car = Car(Engine()) // 불가능!!
    val car = Car(EngineImpl())
    car.engineStart()
}

Engine interface가 여러 곳에서 사용되거나 선언된 추상 메서드가 굉장히 많다면, EngineImpl 구현 클래스를 따로 만들어주는 것이 좋다. 하지만 여기서는 단순히 일회성으로 사용되었다. 그렇다면 EngineImpl 구현 클래스를 따로 만들어주는 것이 과연 효율적일까?

이럴 때 사용하는 것이 바로 익명 클래스(Anonymous class)이다. 익명 클래스는 클래스 정의와 동시에 객체를 생성할 수 있기 때문에 익명 객체(Anonymous object)라고 불러도 상관없다. Kotlin에서는 object 키워드를 통해서 익명 객체를 생성한다.

// 익명 객체로 수정 후
fun main() {
    val car = Car(object : Engine {
        override fun start() {
            println("engine start")
        }
    })
    car.engineStart()
}

예시로는 interface를 사용했지만 abstract class도 익명 객체로 생성 가능하다.


익명 클래스(객체)에 일반 함수도 선언 가능

fun main() {
    val engine = object : Engine {
        override fun start() {
            println("engine start")
        }

        fun stop() {
            println("engine stop")
        }
    }

    engine.start()
    engine.stop()
}

아까와는 코드가 약간 다르니까 헷갈리지 않도록 주의하자. 이번엔 car 인스턴스가 아닌 engine 인스턴스를 생성했다.

일반 클래스에서 상속 또는 구현을 하면서 해당 클래스만의 프로퍼티나 메서드를 선언하는 것도 가능하다. 익명 클래스에서도 그렇게 하는 것이 가능하고, 무려 외부에서 호출도 가능하다. 위의 코드에서 engine.stop() 호출은 정상적으로 이루어진다.

익명 클래스는 가독성이 떨어진다

지금은 간단한 interface로 예시를 보였기 때문에 가독성이 떨어지지 않는다. 하지만 interface 혹은 추상클래스에 선언하는 추상 프로퍼티 및 추상 메서드가 지나치게 많아진다면, 익명 객체를 사용할 때 가독성이 굉장히 많이 떨어진다. 그래서 가독성이 떨어지는 코드를 작성하지 않도록 주의해야 한다. 이런 경우에는 차라리 구현(또는 상속) 클래스를 따로 만드는 것이 낫다.

안드로이드에서의 익명 클래스 사용 예시

class ImagesListAdapter : ListAdapter<DocumentEntity, ItemSearchedViewHolder>(
    object : DiffUtil.ItemCallback<DocumentEntity>() {
        override fun areItemsTheSame(oldDocument: DocumentEntity, newDocument: DocumentEntity): Boolean {
            return oldDocument.thumbnailUrl == newDocument.thumbnailUrl
        }

        override fun areContentsTheSame(oldDocument: DocumentEntity, newDocument: DocumentEntity): Boolean {
            return oldDocument == newDocument
        }
    }
)

안드로이드에서 익명 클래스를 사용하는 여러가지 예시가 있지만, 대표적인 예시로 ListAdapter의 생성자로 DiffUtil.ItemCallback<T> 타입의 객체를 전달할 때이다.
DiffUtil.ItemCallback은 추상 클래스이므로 해당 클래스를 상속한 클래스의 객체를 전달해야 하지만, ImagesListAdapter에서만 사용되고 다른 곳에서는 사용되지 않을 것이기 때문에 익명 객체를 생성하여 전달하였다.


참고자료

profile
안드로이드 주니어 개발자

0개의 댓글