Kotlin

코틀린 스터디 - 12장

끄공 2023. 10. 15. 17:18

제네릭

  • 타입 매개변수(Type Parameter) : 클래스나 함수의 자료형을 임의의 문자로 지정해서 컴파일 타임에 자료형 저검을 할 때 사용한다.
  • 타입 인자(Type Argument) : 객체 생성, 함수 호출할 때 실제 자료형을 지정해서 정해진 임의의 타입을 실제 타입으로 변경한다.

 

제약 사항

  • 제네릭을 구성할 수 있는 것은 함수, 클래스, 추상 클래스, 인터페이스, 확장함수, 확장 속성이다.
  • 제네릭을 구성할 수 없는 것은 하나의 객체만 만드는 object 정의, companion object, object 표현식이다. 하나의 객체만 만드므로 특별히 일반화할 필요가 없기 때문이다.
  • 클래스와 obejct 내의 멤버는 별도의 제네릭으로 만들 수 없다.

 

타입 매개변수와 타입 인자를 지정하는 위치

  • 타입 매개변수와 타입 꺽쇠 괄호 안에 하나 이상 정의할 수 있다. 타입 매개변수는 영어 대문자로 표시한다. 보통 T, R, P, U를 사용한다.
  • 함수는 호출할 때 타입 인자를 지정하고 확장 속성은 값을 할당한다.
  • 클래스는 객체를 생성할 때 타입 인자를 지정한다.
  • 추상 클래스와 인터페이스는 객체를 생성하지 못하므로 클래스가 상속할 때 타입 인자를 위임 호출 처리해야 한다.
  • 대부분은 타입 인자를 지정하지 않아도 명시적인 타입 추론으로 자동으로 타입 인자를 유추한다.

 

타입 매개변수 처리 기준

프로그램 언어의 타입 시스템은 보통 상속관계를 기반으로 만들어진다. 그래서 상위 클래스나 상위 인터페이스를 자료형으로 지정하면 하위 클래스의 객체가 할당된다. 반대는 예외를 처리한다.
 
 

제네릭 함수

제네릭 함수는 동일한 매개변수이지만 각각 자료형이 다를 경우 더 효과가 있다.

fun <T> add1(x:T, y:T, op : (T,T)->T) : T = op(x,y)
println(add1<Int>(10,10,{x,y -> x+y}))
//20

 
 

타입 매개변수의 매개변수와 반환 자료형 분리

제네릭 함수를 정의하다 보면 입력과 반환하는 결과가 다른 자료형일 때가 많다. 그래서 반환하는 자료형을 분리해서 타입 매개변수를 지정한다. 보통 반환 자료형의 타입 매개변수는 R로 표시한다.
 

fun <T,R> sum(x:T, y:T, op:(T,T)->R) : R{
    return op(x,y)
}
println(sum(100,200) {x,y->x+y})
//300

 

함수 반환을 함수 자료형으로 처리할 때 타입 매개변수 사용

함수를 실행해서 반환된 결과가 함수일 경우 함수 자료형을 반환 자료형에 지정한다.

fun <T> func1(value : T) : () -> T = {
    println("람다표현식2")
    value
}

println(func1(1111)())
//람다표현식2
//1111

 
 

타입 매개변수에 특정 자료형을 제한하기

제네릭 함수의 타입 매개변수에 특정 자료형으로 처리하도록 제한할 수 있다. 제한은 콜론을 붙인 후에 특정 자료형을 적는다. 이 타입 매개변수는 지정된 자료형과 그 하위 자료형만 타입 인자로 처리할 수 있다.
 
가령 <T : Number>와 같이 제한을 하면 이 타입은 Number와 그 하위 자료형만 타입 인자에 지정할 수 있다.

fun <T : Number> sumA(x: T, y: T, //타입 매개변수에 타입 제한 처리
                     action : (T,T) -> T) : T { //숫자 자료형만 처리 가능
    return action(x,y)
}
println(sumA(100,200,{ x,y -> x+y }))
//300

 
타입 제한이 2개일 때는 타입 매개변수에 표시하지 않고, 반환 자료형 다음에 where를 사용해 타입 제한을 쉼표로 구분해서 작성한다.

fun <T> suffix(str:T) where T: CharSequence, //타입 매개변수에 대한 제한
                           T: Appendable { //문자 시퀀스와 추가가 가능
    str.append("코틀린")
}

 

제네릭으로 확장하기

  • 제네릭 확장함수: 제네릭 확장함수를 사용하면 클래스의 상속관계에 따라 다양한 객체에서 확장함수를 사용할 수 있다.
  • 제네릭 확장속성: 주의할 것은 클래스의 속성에 있는 배킹필드를 사용할 수 없다는 점이다. 그래서 특히 더 주의해서 사용해야 한다.

 
확장함수를 제네릭 확장함수로 변환할 때는 실제 클래스 이름 대신 타입 매개변수의 일므으로 리시버를 지정하고 함수의 매개변수나 반환자료형을 타입 매개변수로 지정한다.
 

fun <T> T.map(block : (T)->T) : T {
    return block(this) //전달받은 함수를 실행: 인자로 리시버 객체
}

println(11.map { it * it}) //확장함수 실행

//121

 
 
infix??? (451pg)
 
제네릭 확장속성 만들기

class View<T:Any> {
    lateinit var position : T
}

var <T:Any> View<T>.newPosition: T
    get() {
        return position
    }
    set(value) {
        this.position.value
    }
    
val v = View<String>() //객체 생성
v.newPosition = "가을" //사용하기 전에 지연 초기화 처리
println(v.newPosition) //조회

//가을