seoft

[kotlin/android] 리플랙션을 사용해 KClass로 인스터스 생성하기 본문

android

[kotlin/android] 리플랙션을 사용해 KClass로 인스터스 생성하기

seoft 2020. 6. 18. 23:58

[요약]

코틀린에서 리플랙션을 사용하면 KClass를 가지고 있다가 필요시 인스턴스화 할 수 있다.

 

 

다음과 같은 상황이 있다고 가정해보자(억지지만)

 

 

sealed class Animal(open val age: Int) {
data class Dog(override val age: Int) : Animal(age)
data class Cat(override val age: Int) : Animal(age)
data class Bird(override val age: Int) : Animal(age)
}
view raw Animal.kt hosted with ❤ by GitHub

먼저 서버에서도 받아오고 다른대서도 공통으로 사용되는 데이터 클래스가 존재한다.

 

 

 

enum class AnimalType(
val selectText: String,
val hungryText: String
) {
DogType("개를 선택했어요", "멍멍이는 배고파요"),
CatType("고양이를 선택했어요", "야옹이는 배고파요"),
BirdType("새를 선택했어요", "구구는 배고파요")
}
view raw AnimalType.kt hosted with ❤ by GitHub

또한 특정 페이지에 공통으로 사용되는 프로퍼티들이 있어 타입별 enum으로 정의하여 사용한다.

공통으로 사용하는 페이지가 있고, 진입시 타입에 따라 알맞은 인스터스를 생성해 사용해야 한다.

 

 

 

if (animalType == AnimalType.DogType) {
animal = Animal.Dog(age)
} else if (animalType == AnimalType.CatType) {
animal = Animal.Cat(age)
} else if (animalType == AnimalType.BirdType) {
animal = Animal.Bird(age)
}
// 중략...
onSelect {
println("${animal.age}살의 ${animalType.selectText}")
}
onHungry {
println("${animal.age}살의 ${animalType.hungryText}")
}
view raw if.kt hosted with ❤ by GitHub

enum을 통해 분기를 타지 않고 사용할 수 있지만,

어쩔수 없이 인스터스 생성은 다음과같이 타입별로 if분기를 타서 정의한다.

 

 

 

지금은 분기가 크게 많지 않지만 타입이 3개가아니라 10개, 100개 if분기가 늘어날것이다 .

 

이런 분기를 없애기위해 enum으로 다른 분기는 제외시켰으나 

인스턴스 생성은 따로 해줘야되는게 좀 비효율적인거같아 리서치 해본 결과,


다음처럼 사용할 수 있다.

 

 

implementation 'org.jetbrains.kotlin:kotlin-reflect:1.3.72'
view raw build.gradle hosted with ❤ by GitHub
inline fun <reified T : Any> createObject(clazz: KClass<out T>): T {
return clazz.java.newInstance()
}
inline fun <reified T : Any> createObject(clazz: KClass<out T>, vararg args: Any?): T {
return clazz.constructors.first().call(*args)
}

우선 클래스를 인스턴스화하는 코드를 준비한다. (디펜던시 추가 + 유틸함수 준비)

 

 

 

 

Before :

enum class AnimalType(
val selectText: String,
val hungryText: String
) {
DogType("개를 선택했어요", "멍멍이는 배고파요"),
CatType("고양이를 선택했어요", "야옹이는 배고파요"),
BirdType("새를 선택했어요", "구구는 배고파요")
}
view raw AnimalType.kt hosted with ❤ by GitHub

After :

enum class AnimalType2(
val createClass: KClass<out Animal>,
val selectText: String,
val hungryText: String
) {
DogType(Animal.Dog::class, "개를 선택했어요", "멍멍이는 배고파요"),
CatType(Animal.Cat::class, "고양이를 선택했어요", "야옹이는 배고파요"),
BirdType(Animal.Bird::class, "새를 선택했어요", "구구는 배고파요")
}
view raw NewEnum.kt hosted with ❤ by GitHub

그리고 enum타입에 Animal의 하위 kclass를 추가로 받는다.

 

 

 

 

 

 

 

Before :

if (animalType == AnimalType.DogType) {
animal = Animal.Dog(age)
} else if (animalType == AnimalType.CatType) {
animal = Animal.Cat(age)
} else if (animalType == AnimalType.BirdType) {
animal = Animal.Bird(age)
}
// 중략...
onSelect {
println("${animal.age}살의 ${animalType.selectText}")
}
onHungry {
println("${animal.age}살의 ${animalType.hungryText}")
}
view raw if.kt hosted with ❤ by GitHub

After :

animal = createObject(animalType2.createClass, age)
// 중략...
onSelect {
println("${animal.age}살의 ${animalType2.selectText}")
}
onHungry {
println("${animal.age}살의 ${animalType2.hungryText}")
}
view raw after.kt hosted with ❤ by GitHub

마지막으로 다음 코드를 통해 enum타입에서 사전에 받은 kclass로 인스터스를 생성한다.

 

 

 

[결론]
리플랙션을 사용하면 KClass를 인스턴스화 할 수 있고
사용성 중 하나로 공통화된 모듈에서 분기를 최소화하고 enum으로만 관리하고자 할때 사용 할 수있다.

 

하지만 적지않은 kotlin-reflect.jar 의 용량, 리플랙션의 위험성, 비교적 성능저하 을 고려한다면 if분기로 가는 방향도 알맞을 수 있다.

 

상황에 맞게 사용하면 좋게 쓰일 수 있을 것 같다.

 

[참조]

stackoverflow.com/questions/47499838/how-to-pass-vararg-to-a-varag-function-or-constructor-in-kotlin

 

How to Pass vararg to a varag function or constructor in Kotlin?

Kotlin does not allow my subclass to pass vararg to my super class constuctor Here is my Operation class: package br.com.xp.operation import java.util.ArrayList abstract class Operation(vararg v...

stackoverflow.com

 

Comments