Chapter 9. 제네릭스
9.1 제네릭 타입 파라미터
9.1.1 제네릭 함수와 프로퍼티
fun main(args: Array<String>) {
val letters = ('a'..'z').toList()
println(letters.slice<Char>(0..2)) // 타입 인자를 명시적으로 지정
println(letters.slice(10..13)) // 컴파일러는 여기서 T가 Char라는 사실을 추론
val authors = listOf("Dmitry", "Svetlana")
val readers = mutableListOf<String>("Dmitry", "AA", "CC")
println(readers.filter { it !in authors }) // [AA, CC]
}
val <T> List<T>.penultimate: T // 모든 리스트 타입에 이 제네릭 확장 프로퍼티를 사용할 수 있음
get() = this[size - 2]
fun main(args: Array<String>) {
println(listOf(1, 2, 3, 4).penultimate) // 이 호출에서 타입 파라미터 T는 Int로 추론됨
}
9.1.2 제네릭 클래스 선언
interface List<T> { // List 인터페이스에 T라는 타입 파라미터를 정의함
operator fun get(index: Int) : T // 인터페이스 안에서 T를 일반 타입처럼 사용가능
}
class StringList: List<String> { // 이 클래스는 구체적인 타입 인자로 String을 지정해 List를 구현함
override fun get(index: Int) : String = this[index]
}
class ArrayList<T> : List<T> { // ArrayList의 제네릭 타입 파라미터 T를 List의 타입 인자로 넘김
override fun get(index: Int) : T = this[index]
}
interface Comparable<T> {
fun compareTo(other: T) : Int
}
class String : Comparable<String> {
override fun compareTo(other: String) : Int {
return if (this === other) 1
else 0
}
}
9.1.3 타입 파라미터 제약
타입 파라미터 제약 : 클래스타 함수에 사용할 수 있는 타입 인자를 제한하는 기능
fun <T : Number> oneHalf(value: T) : Double { // Number를 타입 파라미터 상한으로 정함
return value.toDouble() / 2.0 // Number 클래스에 정의된 메소드를 호출함
}
// 타입 파라미터를 제약하는 함수 선언하기
fun <T: Comparable<T>> max(first: T, second: T) : T { // 이 함수의 인자들은 비교 가능해야 함
return if(first > second) first else second
}
fun main(args: Array<String>) {
println(oneHalf(3)) // 1.5
// 문자열은 알파벳순으로 비교됨
println(max("kotlin", "java")) // kotlin
// println(max("kotlin", 42)) 컴파일 오류
}
fun <T> ensureTrailingPeriod(seq: T)
where T : CharSequence, T : Appendable { // 타입 파라미터 제약 목록
if (!seq.endsWith('.')) { // CharSequence 인터페이스의 확장 함수를 호출
seq.append('.') // Appendable 인터페이스의 메소드를 호출
}
}
fun main(args: Array<String>) {
val helloWorld = StringBuilder("Hello World")
ensureTrailingPeriod(helloWorld)
println(helloWorld) // Hello World.
}
9.1.4 타입 파라미터를 널이 될 수 없는 타입으로 한정
class Processor<T: Any> { // "null"이 될 수 없는 타입 상한을 지정
fun process(value: T) {
value.hashCode() // T 타입의 "value"는 "null"이 될 수 없음
}
}
fun main(args: Array<String>) {
val notNullStringProcessor = Processor<String>()
// notNullStringProcessor.process(null) 컴파일 오류
}
9.2 실행 시 제네릭스의 동작: 소거된 타입 파라미터와 실체화된 타입 파라미터
9.2.1 실행 시점의 제네릭: 타입 검사와 캐스트
fun printSum(c: Collection<*>) {
val intList = c as? List<Int> // Unchecked cast 경고 발생
?: throw IllegalArgumentException("Array is expected")
println(intList)
}
fun main(args: Array<String>) {
printSum(listOf(1, 2, 3))
printSum(setOf(1, 2, 3)) // IllegalArgumentException 예외 발생
printSum(listOf("a", "b", "c")) // classCastException 발생
}
9.2.2 실체화한 타입 파라미터를 사용한 함수 선언
인라인 함수의 타입 파라미터는 실체화되므로 실행 시점에 인라인 함수의 타입 인자를 알 수 있음
inline fun <reified T> isA(value: Any) = value is T
/* 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(args: Array<String>) {
println(isA<String>("abc")) // true
println(isA<String>(123)) // false
val items = listOf("one", 2, "three")
println(items.filterIsInstance<String>()) // [one, three]
}
9.2.3 실체화한 타입 파라미터로 클래스 참조 대신
inline fun <reified T> loadService(): ServiceLoader<T>? {
return ServiceLoader.load(T::class.java)
}
fun main(args: Array<String>) {
val serviceImpl = loadService<Service>()
}
9.2.4 실체화한 타입 파라미터의 제약
- 타입 파라미터 클래스의 인스턴스 생성하기
- 타입 파라미터 클래스의 동반 객체 메소드 호출하기
- 실체화한 타입 파라미터를 요구하는 함수를 호출하면서 실체화하지 않은 타입 파라미터로 받은 타입을 타입 인자로 넘기기
- 클래스, 프로퍼티, 인라인 함수가 아닌 함수의 타입 파라미터를 reified로 지정하기
9.3 변성: 제네릭과 하위 타입
9.3.1 변성이 있는 이유: 인자를 함수에 넘기기
어떤 함수가 리스트의 원소를 추가하거나 변경한다면 <Any>
대신 <String>
등을 넘길 수 x
9.3.2 클래스, 타입, 하위 타입
fun test(i: Int) {
val n: Number = i // Int가 Number의 하위 타입이어서 컴파일 됨
fun f(s: String) {
println(s)
}
// f(i) // Int가 String의 하위 타입이 아니어서 컴파일 되지 x
}
9.3.3 공변성: 하위 타입 관계를 유지
out 키워드 사용
class Herd<out T : Animal>(vararg animals: T) { // T는 이제 공변적이다.
val size: Int get() = this.size
operator fun get(i: Int) : T = this[i]
}
9.3.4 반공변성: 뒤집힌 하위 타입 관계
공변성 | 반공변성 | 무공변성 |
---|---|---|
Producer<out T> | Consumer<in T> | MutableList<T> |
타입 인자의 하위 타입 관계까 제네릭 타입에서도 유지 | 타입 인자의 하위 타입 관계가 제네릭 타입에서 뒤집힘 | 하위 타입 관계가 성립하지 않음 |
Producer<Cat> 은 Producer<Animal> 의 하위 타입 | Consumer<Animal> 은 Consumer<Cat> 의 하위 타입 | |
T를 아웃 위치에서만 사용 가능 | T를 인 위치에서만 사용 가능 | T를 아무 위치에서나 사용 가능 |
9.3.5 사용 지점 변성: 타입이 언급되는 지점에서 변성 지정
사용 지점 변성 : 타입 파라미터가 있는 타입을 사용할 때 마다 해당 타입 파라미터를 하위 타입이나 상위 타입 중 어떤 타입으로 대치할 수 있는지 명시하는 방식
/* 무공변 파라미터 타입을 사용하는 데이터 복사 함수
fun <T> copyData (source: MutableList<T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}
*/
/*
// 타입 파라미터가 둘인 데이터 복사 함수
fun <T: R, R> copyData(source: MutableList<T>, destination: MutableList<R>) { // source 원소 타입은 destination 원소 타입의 하위 타입이어야 함
for (item in source) {
destination.add(item)
}
}*/
/*
// 아웃-프로젝션 타입 파라미터를 사용하는 데이터 복사 함수
fun <T> copyData(source: MutableList<out T>, destination: MutableList<T>) {
// out 키워드를 타입을 사용하는 위치 앞에 붙이면 in 위치에 사용하는 메소드를 호출하지 않는다는 뜻
for (item in source) {
destination.add(item)
}
}
*/
// in 프로젝션 타입 파라미터를 사용하는 데이터 복사 함수
fun <T> copyData(source: MutableList<T>, destination: MutableList<in T>) {
// 원본 리스트 원소 타입의 상위 타입을 대상 리스트 원소 타입으로 허용
for (item in source) {
destination.add(item)
}
}
fun main(args: Array<String>) {
val ints = mutableListOf(1, 2, 3)
val anyItems = mutableListOf<Any>()
copyData(ints, anyItems) // Int가 Any의 하위 타입이므로 이 함수를 호출할 수 있음
println(anyItems) // [1, 2, 3]
val list: MutableList<out Number> = mutableListOf(1, 2, 3)
//list.add(42) // 컴파일 오류
}
9.3.6 스타 프로젝션: 타입 인자 대신 * 사용
interface FieldValidator<in T> { // T에 대해 반공변인 인터페이스를 선언
fun validate(input: T): Boolean // T를 "인" 위치에만 사용함(이 메소드는 T 타입의 값을 소비함)
}
object DefaultStringValidator : FieldValidator<String> {
override fun validate(input: String) = input.isNotEmpty()
}
object DefaultIntValidator : FieldValidator<Int> {
override fun validate(input: Int) = input >= 0
}
// 검증기 컬렉션에 대한 접근 캡슐화하기
object Validators {
private val validators = mutableMapOf<KClass<*>, FieldValidator<*>>()
fun <T: Any> registerValidator(
kClass: KClass<T>, fieldValidator: FieldValidator<T>) {
validators[kClass] = fieldValidator // 어떤 클래스와 검증기가 타입이 맞아 떨어지는 경우에만 그 클래스와 검증기 정보를 맵에 키/값 쌍으로 넣음
}
@Suppress("UNCHECKED_CAST") // FieldValidator<T> 캐스팅이 안전하지 않다는 경고를 무시하게 만듦
operator fun <T: Any> get (kClass: KClass<T>) : FieldValidator<T> =
validators[kClass] as? FieldValidator<T>
?: throw IllegalArgumentException(
"No Validator for ${kClass.simpleName}"
)
}
fun main(args: Array<String>) {
val validators = mutableMapOf<KClass<*>, FieldValidator<*>>()
validators[String::class] = DefaultStringValidator
validators[Int::class] = DefaultIntValidator
//validators[String::class]!!.validate("") // 컴파일 오류
// 검증기를 가져오면서 명시적 타입 캐스팅 사용하기
val stringValidator = validators[String::class] as FieldValidator<String>
println(stringValidator.validate("")) // false
// 검증기를 잘못 가져온 경우
val stringValidator2 = validators[Int::class] as FieldValidator<String> // 검증기를 잘못 가져왔지만 컴파일과 타입 캐스팅시 아무 문제 없음
//stringValidator2.validate("") // 검증기를 사용해야 비로소 오류 발생
// 검증기 컬렉션에 대한 접근 캡슐화 테스트
Validators.registerValidator(String::class, DefaultStringValidator)
Validators.registerValidator(Int::class, DefaultIntValidator)
println(Validators[String::class].validate("Kotlin")) // true
println(Validators[Int::class].validate(42)) // true
}
Author And Source
이 문제에 관하여(Chapter 9. 제네릭스), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@rosesua318/Chapter-9.-제네릭스저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)