티스토리 뷰
자바에서 제네릭을 이용할때 가장 불편한 부분은 타입 소거로 인해 타입 토큰을 전달해야하는 점이다.
// 런타임에 T 정보를 알 수 없으므로 이렇게 작성하면 컴파일 에러가 발생한다
public <T> T create() throws Exception {
return T.class.getConstructor().newInstance();
}
// 타입 파라미터와 별개로 타입 토큰을 전달해야한다
public <T> T create(Class<T> clazz) throws Exception {
return clazz.getConstructor().newInstance();
}
코틀린은 inline + reified 키워드를 이용해 이런 부분을 개선했다.
// 두 코드 모두 정상적으로 컴파일되고, 실행된다
inline fun <reified T> create(): T {
return T::class.java.getConstructor().newInstance()
}
inline fun <reified T> create(): T {
return T::class.constructors.first { it.parameters.isEmpty() }.call()
}
그래서 코틀린으로 작성된 코드들은 제네릭을 사용하더라도 타입 토큰을 직접 전달하는 경우가 거의 없다. 다만 코드를 작성하다보면 이런 문제가 생길때가 있다.
class SomeClass(
private val someProperty: String,
) {
inline fun <reified T> create(): T {
println(someProperty)
return T::class.constructors.first { it.parameters.isEmpty() }.call()
}
}
이런 형태로 inline 으로 공개하고 싶은 메서드가 내부에서 private 프로퍼티 혹은 private 메서드를 호출하는 경우다. inline 키워드는 해당 메서드를 호출하는 클라이언트에 직접 코드가 복사되게 되는데, 이렇게 되면 컴파일 된 결과물에선 외부에서 private 프로퍼티에 접근하게 되기 때문에 컴파일이 되지 않는다. 이럴땐 어쩔 수 없이 inline을 포기하고 자바처럼 타입 토큰을 전달하거나 inline을 유지하고 싶다면 프로퍼티의 접근 제어자를 변경해주는 방법이 있다.
class SomeClass(
val someProperty: String,
) {
inline fun <reified T> create(): T {
println(someProperty)
return T::class.constructors.first { it.parameters.isEmpty() }.call()
}
}
class SomeClass(
private val someProperty: String,
) {
fun <T : Any> create(clazz: KClass<T>): T {
println(someProperty)
return clazz.constructors.first { it.parameters.isEmpty() }.call()
}
}
물론 둘 다 썩 마음에 드는 방법은 아니다. 이럴때 권장하는 방법은 확장함수를 제공하는 것이다.
class SomeClass(
private val someProperty: String,
) {
fun <T : Any> create(clazz: KClass<T>): T {
println(someProperty)
return clazz.constructors.first { it.parameters.isEmpty() }.call()
}
}
inline fun <reified T : Any> SomeClass.create(): T {
return this.create(T::class)
}
이 방식은 실제로 많은 코틀린 라이브러리에서 사용하는 방식이고, 기존 자바 라이브러리들이 코틀린용 모듈을 제공할때 사용하는 방식이다. 두번째 방식은 @PublishedApi 애노테이션을 사용하는 방식이다.
class SomeClass(
@PublishedApi
internal val someProperty: String,
) {
inline fun <reified T> create(): T {
println(someProperty)
return T::class.constructors.first { it.parameters.isEmpty() }.call()
}
}
private 이었던 프로퍼티의 접근제어자를 internal 로 변경하고, @PublishedApi 를 붙여주면 된다. 이렇게하면 외부 모듈에서 create() 메서드를 호출할 수 있다. 다만 이 방식은 공개 라이브러리를 만드는 경우 바이너리 호환성 문제가 발생할 수 있다. 또한 처음 설계의도가 internal 이면 모르겠지만 여전히 타입때문에 접근제어자를 변경해야하는 점, 변경하고나면 모듈 내에선 public 과 마찬가지가 되는 부분 때문에 이 방식보다는 확장함수를 이용하는 방식이 더 낫다고 생각한다.
'kotlin' 카테고리의 다른 글
| kotlin JPA 에서 entity ID 를 어떻게 선언하는게 좋을까?#2 (1) | 2023.08.15 |
|---|---|
| kotlin JPA 에서 entity ID 를 어떻게 선언하는게 좋을까?#1 (3) | 2023.08.13 |
| mockk 의 mocking 방식 (1) | 2022.12.24 |
| kotest 훑어보기 (1) | 2022.10.17 |
| kotlin(+JPA) entity 에서 setter 를 막는 법 (4) | 2022.08.27 |
- Total
- Today
- Yesterday
- 정규표현식
- frontcode
- OOP
- db
- programming
- Design Pattern
- http
- javascript
- clean code
- Git
- Kotlin
- servlet
- Jackson
- mariadb
- JPA
- java
- DesignPattern
- toby
- generics
- code
- Spring
- backend개발환경
- spring cloud
- TEST
- MySQL
- EffectiveJava
- JavaScript Core
- java8
- go-core
- frontend개발환경
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 | 31 |
