타입 소거(type erasure)
- 실행 시점에 제네릭 클래스의 인스턴스에 타입 인자 정보가 들어있지 않다는 뜻
- 코틀린 제네릭 타입 인자 정보는 런타임에 지워짐
> 제네릭 클래스 인스턴스가 그 인스턴스를 생성할 때 쓰인 타입 인자에 대한 정보를 유지하지 않는다는 뜻
> List<String> 객체를 만들고 그 안에 문자열을 여럿 넣어도 실행 시점에는 해당 객체를 List로만 볼 수 있음 - 타입 인자를 따로 저장하지 않기 때문에 실행 시점에 타입 인자를 검사할 수 없음
> 어떤 리스트가 문자열로 이뤄진 리스트인지 다른 객체로 이뤄진 리스트인지 실행 시점에 검사할 수 없음
> is 검사에서 타입 인자로 지정한 타입을 검사할 수는 없음 - 저장해야 하는 타입 정보의 크기가 줄어들어서 전반적인 메모리 샤용량이 줄어든다는 장점
스타 프로젝션
- 타입 인자를 명시하지 않고 제네릭 타입을 사용할 수 없으므로 어떤 값이 집합이나 맵이 아니라 리스트라는 사실을 확인할 때 사용
> if (value is List<*>) {…}
inline 함수
- 인라인 함수의 타입 파라미터는 실체화되므로 실행 시점에 인라인 함수의 타입 인자를 알 수 있음
- 어떤 함수에 inline 키워드를 붙이면 컴파일러는 그 함수를 호출한 식을 모두 함수 본문으로 바꾼다. 함수가 람다를 인자로 사용하는 경우 그 함수를 인라인 함수로 만들면 람다 코드도 함께 인라이닝 되고, 그에 따라 무명 클래스와 객체가 생성되지 않아 성능이 더 좋아질 수 있다.
- reified 키워드와 함께 타입 파라미터를 사용
> inline fun <reified T> isA(value: Any) = value is T - 타입 인자를 실행 시점에 알 수 있게 됨
인라인 함수에서만 실체화한 타입 인자를 쓸 수 있는 이유
- 컴파일러는 인라인 함수의 본문을 구현한 바이트코드를 그 함수가 호출되는 모든 지점에 삽입한다. 컴파일러는 실체화한 타입 인자를 사용해 인라인 함수를 호출하는 각 부분의 정확한 타입 인자를 알 수 있다. 따라서 컴파일러는 타입 인자로 쓰인 구체적인 클래스를 참조하는 바이트코드를 생성해 삽입할 수 있다. 타입 파라미터가 아니라 구체적인 타입을 사용하므로 만들어진 바이트코드는 실행 시점에 벌어지는 타입 소거의 영향을 받지 않는다
- 자바 코드에서는 reified 타입 파라미터를 사용하는 inline 함수를 호출할 수 없다. 자바에서는 코틀린 인라인 함수를 다른 보통 함수처럼 호출하며 이 경우 인라인 함수를 호출해도 실제로 인라이닝 되지 않는다.
실체화한 타입 파라미터를 사용할 수 있는 경우
- 타입 검사와 캐스팅(is, !is, as, as?)
- 코틀린 리플렉션 API (::class)
- 코틀린 타입에 대응하는 java.lang.Class를 얻기(::class.java)
- 다른 함수를 호출할 때 타입 인자로 사용
실체화한 타입 파라미터를 사용할 수 없는 경우
- 타입 파라미터 클래스의 인스턴스 생성하기
- 타입 파라미터 클래스의 동반 객체 메서드 호출하기
- 실체화한 타입 파라미터를 요구하는 함수를 호출하면서 실체화하지 않은 타입 파라미터로 받은 타입을 타입 인자로 넘기기
- 클래스, 프로퍼티, 인라인 함수가 아닌 함수의 타입 파라미터를 reified로 지정하기
(마지막의 경우로 인해 실체화한 타입 파라미터를 인라인 함수에만 사용할 수 있으므로 실체화한 타입 파라미터를 사용하는 함수는 자신에게 전달되는 모든 람다를 인라이닝 함)
변성 (Variance)
- 제네릭 클래스가 아닌 클래스에서는 클래스 이름을 바로 타입 사용 가능
- 제네릭 클래스에서는 제네릭 타입의 타입 파라미터를 구체적인 타입 인자로 바꿔줘야 함
- 하위 타입(subtype): 어떤 타입 A의 값이 필요한 모든 장소에 어떤 타입 B의 값을 넣어도 아무 문제가 없다면 타입 B는 타입 A의 하위 타입이다.
- 상위 타입(supertype): A타입이 B타입의 하위타입이라면 B는 A의 상위 타입이다.
- 어떤 값의 타입이 변수 타입의 하위 타입인 경우에만 값을 변수에 대입하게 허용
- 널이 될 수 없는 타입은 널이 될 수 있는 타입의 하위 타입이지만 두 타입 모두 같은 클래스에 해당
- 무공변(invariant): 제네릭 타입을 인스턴스화할 때 타입 인자로 서로 다른 타입이 들어가면 인스턴스 타입 사이의 하위 타입 관계가 성립하지 않음
- A가 B의 하위 타입이면 List<A>는 List<B>의 하위 타입이고, 이런 클래스나 인터페이스를 공변적이라 함
공변성: 하위 타입 관계 유지
- 코틀린에서 제네릭 클래스가 타입 파라미터에 대해 공변적임을 표시하려면 타입 파라미터 이름 앞에 out을 넣어야 함
- 타입 안전성을 보장하기 위해 공변적 파라미터는 항상 아웃 위치에 있어야 하며 이는 클래스가 T 타입의 값을 생산할 수는 있지만 T 타입의 값을 소비할 수는 없다는 뜻
반공변성: 뒤집힌 하위 타입 관계
- 반공변 클래스의 하위 타입 관계는 공변 클래스의 경우와 반대
- 타입 B가 타입 A의 하위 타입인 경우 Consumer<A>가 Consumer<B>의 하위 타입인 관계가 성립하면 제네릭 클래스 Consumer<T>는 타입 인자 T에 대해 반공변
- in이라는 키워드를 넣으며, 그 키워드가 붙은 타입이 이 클래스의 메서드 안으로 전달 돼 메서드에 의해 소비된다는 뜻
사용 지점 변성: 타입이 언급되는 지점에서 변성 지정
- 선언 지점 변성: 클래스를 선언하면서 변성을 지정하면 그 클래스를 사용하는 모든 장소에 변성 지정자가 영향을 끼침
- 사용 지점 변성: 타입 파라미터가 있는 타입을 사용할 때마다 해당 타입 파라미터를 하위 타입이나 상위 타입 중 어떤 타입으로 대치할 수 있는지 명시해야 함
스타 프로젝션: 타입 인자 대신 * 사용
- 재네릭 타입 인자 정보가 없음을 표현하기 위해 사용