프로그래밍 언어/코틀린

[Kotlin] 18. 제너릭

엔지니어 규 2022. 5. 24. 21:34
728x90

제너릭이란 클래스나 함수에서 사용하는 자료형을 외부에서 지정할 수 있는 기능이다.

 

예를 들어 class A 와 A를 상속받은 class B가 있을때 이 두 클래스의 인스턴스를 공용으로 사용하는 하나의 함수에 파라미터로 받으려면

super class인 A 의 자료형으로 받으면 B는 자동으로 A에 캐스팅 되면서 두 클래스 모두 함수의 패러미터로 사용이 가능하다.

하지만 캐스팅 연산을 거치는것은 프로그램의 속도를 저하시킬수 있다는 단점이 있다.

 

 

 

그래서 Generic 이라는 개념이 등장했다.

제너릭은 함수나 클래스를 선언할때 고전적인 자료형 대신 실제 자료형으로 대체되는 타입 패러미터를 받아 사용하는 방법이다.

이 타입패러미터에 특정 자료형이 할당이 되면 제너릭을 사용하는 모든 코드는 할당된 자료형으로 대체되어 컴파일이 된다.

따라서 캐스팅 연산 없이 자료형을 그대로 사용할 수 있다.

 

 

 

타입파라미터의 이름은 클래스 이름과 규칙이 같지만 일반적으로 'Type'의 이니셜인 'T'를 사용하는것이 관례이며,

여러개의 제너릭을 사용할 경우 T의 다음 알파벳인 U,V 를 사용하기도 한다.

 

또한 제너릭을 특정한 수퍼클래스를 상속받은 클래스 타입으로만 제한하려면 콜론을 쓰고

수퍼클래스를 명시하면 된다.( <T: Super Class>)

 

 

 

함수에 제너릭을 선언한 경우에는 일반적인 함수처럼 사용하면, 파라미터나 반환형을 통해 타입파라미터를 자동으로 추론하며,

클래스의 경우 인스턴스를 만들때 타입 파라미터를 수동으로 지정하거나, 생성자에 제너릭이 사용된 경우 지정하지 않아도

자동으로 추론된다.

 

fun main(){

    UsingGeneric(A()).doShouting()  //A가 소리칩니다.
    UsingGeneric(B()).doShouting()  //B가 소리칩니다.
    UsingGeneric(C()).doShouting()  //C가 소리칩니다.
}

open class A {
    open fun shout(){
        println("A가 소리칩니다")
    }
}

class B : A(){
    override fun shout(){
        println("B가 소리칩니다.")
    }
}

class C : A(){
    override fun shout() {
        println("C가 소리칩니다.")
    }
}

class UsingGeneric<T: A> (val t: T){
    fun doShouting(){
        t.shout()
    }
}

위에서는 UsingGeneric 클래스를 만들되 수퍼클래스를 A로 제한한 제너릭 T를 선언하고,

생성자에서는 제너릭 T에맞는 인스턴스를 속성 t로 받았다.

 

Main에서는 UsingGeneric 인스턴스를 만들되 패러미터로 클래스 A의 인스턴스를 넘기고 doShouting()  을 수행한다.

타입 패러미터를 UsingGeneric<A>(A()).doShouting() 처럼 수동으로 전달 할 수도 있지만,

생성자의 패러미터를 통해 클래스 A라는 것을 알수 있기 때문에 UsingGeneric(A()).doShouting() 으로 타입패러미터를 생략한다.

 

 

 

여기서 제너릭을 사용하지 않고 UsingGeneric의 생성자에서 class UsingGeneric(val t : A) 처럼 수퍼클래스의 A로 캐스팅하여 shout을 호출하여도 동작은 같겠지만 class UsingGeneric<T:A>(valt: T) 처럼 제너릭을 사용하는 경우 제너릭이 자료형을 대체하게 되어 캐스팅을 방지하여 성능을 높일 수 있다.

 

제너릭을 함수에 사용할 수 도 있다.

fun main(){

    UsingGeneric(A()).doShouting()  //A가 소리칩니다.
    UsingGeneric(B()).doShouting()  //B가 소리칩니다.
    UsingGeneric(C()).doShouting()  //C가 소리칩니다.

    doShouting(B())  //B가 소리칩니다.

}

fun <T:A> doShouting(t:T){
    t.shout()
}

open class A {
    open fun shout(){
        println("A가 소리칩니다")
    }
}

class B : A(){
    override fun shout(){
        println("B가 소리칩니다.")
    }
}

class C : A(){
    override fun shout() {
        println("C가 소리칩니다.")
    }
}

class UsingGeneric<T: A> (val t: T){
    fun doShouting(){
        t.shout()
    }
}

 

main 에서 doShouting 함수에 B의 인스턴스를 넘겨서 실행하면 함수 역시 제너릭의 타입을 자동으로 추론하므로 별도로 타입 패러미터에 자료형을 전달할 필요가 없다. 이것 역시 캐스팅 없이 B의 객체 그대로 함수에서 사용하는 것이다.

 

제너릭은 많은 기본 클래스에서 사용된다.

 

 

728x90
댓글수0