Generic Function

sumi Yoo·2022년 10월 9일
0

제네릭스(Generics)는 클래스나 함수를 정의할 때 타입을 확실히 정하지 않는 것을 말한다. 그렇기 때문에 다양한 타입으로 클래스를 여러개 정의하지 않아도 된다.

Generic 함수 정의

Generic 함수를 정의할 때, 타입이 정해지지 않은 변수는 함수 이름 앞에 <T>처럼 정의되어야 한다. 아래 코드는 타입 T 변수 num1과 num2를 더하고 타입 T 변수를 리턴하는 함수다.

fun <T> addNumbers(num1: T, num2: T): T {
    return (num1.toDouble() + num2.toDouble()) as T
}

위 함수는 아래 처럼 호출할 수 있다.

fun main(args: Array<String>) {
    println(addNumbers(10, 20))      // 결과: 30
    println(addNumbers(10.1, 20.1))  // 결과: 30.200000000000003
}

T는 타입이 정해지지 않았기 때문에 어떤 타입이든 올 수 있다.

Generic 클래스 정의

Generic 클래스를 정의할 때 타입이 정해지지 않은 변수는 클래스 이름 다음에 와 같이 정의한다. Generic 함수와 다른 것은 이름 다음에 <T>가 온다는 것이다.

class Rectangle<T>(val width: T, val height: T) {
}

아래 코드는 위에서 정의한 Rectangle 클래스를 생성하는 코드다. 객체를 생성할 때 Rectangle<Double>처럼 T의 타입이 무엇인지 써줘야 한다.

fun main(args: Array<String>) {
    val rec = Rectangle<Double>(10, 20)
    val rec1 = Rectangle<String>("aa", "bb")
}

하지만 코틀린은 전달된 인자로 부터 T의 타입을 추론하기 때문에 아래와 같이 Rectangle만 써줘도 된다.

fun main(args: Array<String>) {
    val rec = Rectangle(10, 20)
    val rec1 = Rectangle("aa", "bb")
}

만약 두개 이상의 다른 타입의 변수를 Generic으로 정의하려면 <T, K>처럼 두개의 변수를 써주면 된다.

class Rectangle<T, K>(val width: T, val height: T, val name: K) {
}

Constraints(제한, 제약)

위에서 정의한 클래스의 문제점은 Rectangle("aa", "bb")와 같이 숫자가 아닌 인자도 허용이 된다는 것이다. 우리는 width와 height 변수가 숫자만 허용되도록 만들고 싶다. 아래 코드에서 <T: Number>는 super type이 Number인 객체만 T로 받도록 허용한다.

class Rectangle<T: Number>(val width: T, val height: T) {
    fun getArea(): T {
        return (width.toDouble() * height.toDouble()) as T
    }
}

아래 코드에서 Rectangle을 생성할 때, 인자가 String인 경우 컴파일 에러가 발생하고 Int와 Double 일 때 객체가 생성된 것을 볼 수 있다. (Int와 Double는 Number 클래스를 상속받았다.)

fun main(args: Array<String>) {
    val rec = Rectangle(10, 20)
    val rec1 = Rectangle(10.5, 20.5)
//    val rec2 = Rectangle("aa", "bb") // compile error
}

Invariance(불변성)

Double은 Number를 상속하고, Double의 Super class는 Number이다.

class Double : Number, Comparable<Double>

하지만 Rectangle<Dobule>의 Super class는 Rectangle<Number>가 아니다. 두개의 타입이 서로 상속 관계이지만, Generic 클래스의 상속 관계는 아니라는 것을 Invariance(불변성)라고 한다.

코틀린에서는 Generic의 모든 타입은 Invariance이다. Invariance의 반대말은 Covariance인데, in/out 키워드로 Generics를 Covariance로 변경할 수 있다.

Covariance(공변성, 함께 변하는 속성)

Number가 Double의 super class일 때 Rectangle<Number>가 Rectangle<Dobule>의 super class이면, 이것을 Covariance(공변성)라고 한다.

out 키워드

out 키워드는 두개의 타입이 Invariance일 때, Covariance로 만들어준다. 즉, out 키워드는 Rectangle<Number>가 Rectangle<Dobule>의 super class가 되도록 한다.

아래 코드는 Rectangle<Double> 객체를 Rectangle<Number>에 대입하는 코드다. 이 코드는 빌드에러가 발생한다.

class Rectangle<T: Number>(val width: T, val height: T) {
}

fun main(args: Array<String>) {
  val derivedClass = Rectangle<Double>(10.5, 20.5)
  val baseClass : Rectangle<Number> = derivedClass
}

하지만, T 앞에 out을 붙여주면 컴파일이 된다. 바로 out이 타입의 상속구조가 Generic의 상속구조와 같다는 것을 정의하였기 때문이다. 컴파일러는 Rectangle<Double>가 Rectangle<Number>의 하위 클래스라고 인식하고 있다.

class Rectangle<out T: Number>(val width: T, val height: T) {
}

fun main(args: Array<String>) {
  val derivedClass = Rectangle<Double>(10.5, 20.5)
  val baseClass : Rectangle<Number> = derivedClass
}

Contravariance(반공변성)

Contravariance는 Covariance의 반대 방향으로 공변성 조건을 만족하는 것을 말한다. 위의 예제에서, Number가 Double의 super class일 때 Rectangle<Dobule>가 Rectangle<Number>의 super class라면 Contravariance(반공변성)라고 한다.

in 키워드

in 키워드는 out 키워드의 반대다. in은 타입의 상위/하위 클래스 구조가 Generic에서는 반대 방향의 상위/하위 클래스 구조를 갖는다는 것을 정의한다. Rectangle<Double>이 Rectangle<Number>의 상위 클래스가 되었다.

class Rectangle<in T: Number>(val width: T, val height: T) {
}

fun main(args: Array<String>) {
    val baseClass = Rectangle<Number>(10.5, 20.5)
    val derivedClass : Rectangle<Double> = baseClass
}

https://codechacha.com/ko/generics-class-function-in-kotlin/#generic-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%A0%95%EC%9D%98

0개의 댓글

관련 채용 정보