일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 함수형프로그래밍
- 디자인패턴 #
- builderPattern
- 빌터패턴
- ㅓ
- El
- Design Pattern
- 팩토리 메소드
- 디자인패턴
- factory method
- 추상 팩토리
- designPattern
- 싱글톤
- 추상팩토리패턴
- Observer Pattern
- F
- Singleton
- Functional Programming
- Kotlin
- ㅋㅁ
- 프로토타입 패턴
- 옵저버 패턴
- r
- a
- PrototypePattern
- 코틀린
- Abstract Factory
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
실행시 제네릭스의 동작 본문
소거된 타입 파라미터와 실체화된 타입 파라미터
JVM의 제네릭스는 보통 타입 소거를 사용해 구현된다. 이것은 Runtime시 타입 인자 정보가 들어있지 않다
코틀린의 타입 소거가 실용적인 명에서 어떤 영향을 끼치고 함수를 inline으로 선언함으로써 이런 제약을 어떻게 우회할 수 있는지 살펴본다.
함수를 inline으로 만들면 타입 인자가 지워지지 않게 할 수 있다.(타입 실체화)
실행 시점의 제네릭스: 타입 검사와 캐스트
자바와 마찬가지로 코틀린 제네릭 타입인자 정보는 런타임에 지워진다.
제네릭 클랙스의 인스턴스는 인스턴스 생성시 쓰인 타입 인자에 대한 정보를 유지하지 않는다.
List <String>을 생성 후 안에 String 객체를 여럿 넣더라도 실행 시점에는 그 객체를 List로만 볼 수 있다. 안에 원소가 어떤 타입인지 실행 시점에 알 수 없다.
val list1: List<String> = listOf("a", "b")
val list2: List<Int> = listOf(1, 2)
컴파일러는 두 리스트를 다른 타입으로 인식하지만, 실행 시점에는 같은 타입의 객체이다. 그런데 보통 각 리스트에는 String, Int 원소만 들어 있다고 가정할 수 있는 건 컴파일러가 타입 인자를 알고 올바른 타입의 값만 리스트에 저장되도록 보장한다.
타입 소거로 인해 생기는 한계
타입인자를 따로 저장하지 않기 때문에 실행 시점에 타입 인자를 검사할 수 없다.
실행 시점 이것이 List인지는 확실히 알 수 있지만 그 원소가 String, Int인지 알 수 없다.
코틀린은 타입 인자를 명시 하지 않고는 제네릭 타입을 사용할 수가 없다. 그렇다면, 어떻게 값이 집합이나 맵이 아니라 List라는 사실을 확인할 수 있을까?
스타 프로젝션을 사용하면 된다
타입 파라미터가 2개 이상이라면 모든 타입 파라미터에 *를 포함 시켜야다한다. 앞의 예제에서 value가 List임을 알 수는 있지만, 그 원소가 어떤 타입인지는 알 수 없다.
as/as? 캐스팅에도 여전히 제네릭 타입을 사용할 수 있다. 하지만 기저클래스는 같지만 타입 인자가 다른 타입으로 캐스팅해도 여전히 캐스팅에 성공된다는 점을 조심해야 된다. 실행 시점에 제네릭 타입 인자를 알 수 없기 때문에 캐스팅은 항상 성공하고 컴파일러가 "unchecked cast" 경고를 준다.
fun printSum(c: Collection<*>){
val intList = c as? List<Int>
?: throw IllegalArgumentException("List is expected")
println(intList.sum())
}
fun main() {
printSum(listOf(1,2,3,4))
}
문자열 리스트를 전달해보자.
fun main() {
printSum(listOf("1","2","3"))
}
일단 as? 은 성공을 했기 때문에 IllgealArgumentException은 발생하지않는다. 대신 sum함수가 호출될 때 ClassCastException이 발생한다. sum은 number 타입을 더 하려고 시도를 하기 때문이다.
fun printSum(c: Collection<Int>){
val intList = c as? List<Int>
?: throw IllegalArgumentException("List is expected")
println(intList.sum())
}
위 코드는 컴파일 시점에 컬렉션이 Int 타입의 값을 저장을 한다는것을 알기 때문에 List <Int> 인지 검사할 수 있다.
실체화한 타입 파라미터를 사용한 함수 선언
inline 함수의 타입 파라미터는 실체화 되기 때문에 실행 시점에 인라인 함수의 타입인자를 알 수 있다.
위의 함수를 inline으로 바꾸고 타입 파라미터를 reified로 지정하면 value가 T의 인스턴스인지를 실행 시점에 검사할 수 있다.
inline fun <reified T> isA(value: Any) = value is T
fun main() {
val str = "kotlin"
println(isA<String>(str))
}
실체화한 타입 파라미터를 사용하는 예)
filterIsInstance (간단한 버전)
inline fun <reified T> Iterable<*>.filterIsInstance(): List<T>{
val destination = mutableListOf<T>()
for (element in this){
if (element is T) destination.add(element)
}
return destination
}
fun main() {
val items = listOf("one", 2, "thrss")
println(items.filterIsInstance<String>())
}
타입 인자로 String을 지정함으로 문자열만 필요하다는 사실을 알려주면 실행시점에 알수있고 리스트 원소중에 타입 인자와 동일한 타입만을 추려낸다.
* filterIsInstance를 인라인 함수로 정의한 이유는 람다를 인자로 받는 함수는 객체 및 클래스 생성과 같은 부가비용을 덜기 위함과 달리,
단지 실체화된 타입인자를 사용하기 위해서다.
성능을 좋게 하기 위해선 인라인 함수의 크기를 잘 살펴봐야 한다. 함수가 커지면 실체화에 의존하지 않는 부분을 일반 함수로 정의해야된다.
인라인 함수에서만 실체화한 타입인자를 사용할 수 있는 이유
- 인라인 함수는 함수의 본문을 구현한 바이트 코드를 호출되는 지점에 전부 삽입한다.
- 실체화한 타입 인자를 호출하는 각 부분마다 정확히 알수 있다.
- 컴파일러는 각 타입 인자로 쓰인 구체적인 클래스를 참조하는 바이트코드를 생성해 삽입한다.
- 더 이상 타입 인자가 아닌 구체적 타입을 사용하기 때문에 런타임시 타입소거의 영향을 받지 않는다.
실체화한 타입 파라미터로 클래스 참조를 대신해보자!
java.lang.Class 타입 인자를 파라미터로 받는 API에 대한 코틀린 어뎁터를 구축하는 경우 실체화한 타입 파라미터를 자주 사용한다.
ServiewLoader는 어떤 추상 클래스나 인터페이스를 표현하는 java.lang.Class를 받아서 그 클래스나 인스턴스를 구현한 객체를 반환해준다.
val serviceImpl = ServiceLoader.load(Service::class.java)
val serviceImpl = ServiceLoader<Service>
inline fun <reified T> loadService() = ServiceLoader.load(T::class.java)
::class.java는 Service.class라는 자바 코드와 완전히 같다.
위 2줄의 코드는 동일 하지만 클래스 타입인자로 지정하면 훨씬 더 읽고 이해하기 쉽다.
*안드로이드 startActivit 함수 단간하게 해보기.
inline fun <reified T: Activity> Context.startActivity() {
val intent = Intent(this, T::class.java)
startActivity(intent)
}
실체화한 타입 파라미터의 제약
가능한 일들
- 타입 검사와 캐스팅(is, as, as?)
- 코틀린 리플렉션 API(::class)
- 코틀린 타입에 대응 하는 java.lang.Class를 얻기. (::class.java)
- 다름 함수 호출할 때 타입 인자로 사용
불가능한 일들
- 타입 파라미터 클래스의 인스턴스 생성하기
- 타입 파라미터 클래스의 동반 객체 메소드 호출
- 실체화한 타입 파라미터를 요구하는 함수를 호출하면서 실체화되지 않은 타입 파라미터롤 받은 타입을 타입인자로 넘기기
- 클래스, 프로퍼티, 인라인 함수가 아닌 함수의 타입 파라미터를 reified로 지정할수 없다.
'Kotlin in Action > 코틀린 답게 사용하기' 카테고리의 다른 글
변성: 제네릭과 하위 타입2 (0) | 2021.03.02 |
---|---|
변성: 제네릭과 하위 타입 (0) | 2021.03.02 |
타입 파라미터의 제약 (0) | 2021.03.02 |
제네릭스 (0) | 2021.02.26 |
고차 함수 안에서 흐름 제어 (0) | 2021.02.26 |