세상을 더 편리하게
728x90

제네릭은 클래수 내부에서 사용할 자료형을 나중에 인스턴스를 생성할 때 확정합니다.

다시 말해 어떤 자료형이 올지 모를 때 객체를 생성할 때 자료형을 정할 수 있습니다.

class Box<T>(t: T) {
    var name = t
}

fun main() {
    val box1: Box<Int> = Box<Int>(1)
    val box2: Box<String> = Box<String>("택배상자")
    println(box1.name)
    println(box2.name)
}
/*
1
택배상자
*/

위의 코드블럭처럼 자신이 원하는 자료형으로 인스턴스를 생성할 때 마다 만들 수 있다.

※ T = Type 을 뜻하는 단어의 약자입니다.

더보기

E = Element

K = Key

N = Number

T = Type

V = Value

자료형 일반

open class Parent
class Child : Parent()
class Cup<T>

fun main() {
    val obj1 : Parent = Child()
    val obj2: Child = Parent()

    val obj3:Cup<Parent> = Cup<Child>
    val obj4:Cup<Child> = Cup<Parent>
}

 부모 클래스보다 자식 클래스는 더 큰 범위를 갖고 있다.

왜냐하면 자식클래스 = 부모 클래스 + 자신만의 프로퍼티 혹은 매소드 이기에 자식 클래스는 부모 클래스를 대체 할 수 있다.

하지만 역은 불가능하다. 자식클래스가 더 크기에 부모클래스는 자식클래스를 대체할 수 없다.

그렇기에 6번 줄 코드는 가능하지만 7번 줄 코드는 불가능하다.

하지만 제네릭으로 선언된 클래스(A 클래스라 하자)는 상속관계이여도 자식 부모간 대체 할 수 없다.

왜냐하면 A 클래스는 그저 하나의 자료형으로 상속관계가 포함된 클래스를 참고한 것 뿐이지, 상속관계에 직접적인 영향을 갖고 있지 않기 때문이다.

그렇기에 서로 다른 클래스로 인식하기에 9, 10번 줄 코드는 불가능하다.

매개변수의 null 제어

class GenericNull<T> {
    fun EqualityFunc(arg1: T, arg2: T) {
        println(arg1?.equals(arg2))
    }
}

fun main() {
    val obj1 = GenericNull<String>()
    obj1.EqualityFunc("Hello", "World") // -> false

    val obj2 = GenericNull<Int?>()
    obj2.EqualityFunc(null, 10) // -> null
}

8 번줄 코드는 둘다 String 자료형이므로 실행이 arg1?.equals(arg2) 가 실행이 되지만 값은 false 이다.

9 번줄 코드는 arg1 이 null 이므로 arg1?. 에서 부터 null 반환된다.

그럼 null 을 제한 할 수는 없을까?

class GenericNull<T:Any> {
    fun EqualityFunc(arg1: T, arg2: T) {
        println(arg1?.equals(arg2))
    }
}

위에 처럼 T: Any 형식으로 null 을 방지 할 수 있다. 그러면 3번 줄 코드도 arg1?. -> arg1. 바꿀 수 있다.

매소드에서 제네릭

fun <T> find(a: Array<T>, Target: T): Int {
    for (i in a.indices) {
        if (a[i] == Target) return i
    }
    return -1
}

fun main() {
    val arr1: Array<String> = arrayOf("Apple", "Banana", "Cherry", "Durian")
    val arr2: Array<Int> = arrayOf(1, 2, 3, 4)

    println("arr1.indices ${arr1.indices}")
    println(find(arr1, "Cherry"))

    println("arr2.indices ${arr2.indices}")
    println(find(arr2, 2))
}
/*
arr1.indices 0..3
2
arr2.indices 0..3
1
*/

 ※ indices 매소드는 배열의 유효 범위를 반환한다.

함수에는 fun <T> 매소드명 형식으로 선언하여 제네릭을 사용 할 수 있다.

람다식에서 제네릭

fun <T> add(a: T, b: T, op: (T, T) -> T): T {
    return op(a, b)
}

fun main() {
    val result = add(2, 3) { a, b -> a + b }
    println(result)
}

여기서 return op(a, b) 대신에 return a + b 는 사용 할 수 없을까?

정답은 안된다. 왜냐하면 제네릭이기에 자료형이 정해지지 않기에 + 매소드는 정의되지 않기에 할 수 없다.

자료형 제한하기

fun <T : Number> add(a: T, b: T, op: (T, T) -> T): T {
    return op(a, b)
}

다음과 같이 <T : 자료형 > 을 사용하면 자료형에 한해서만 가능하다. 

다수 조건의 형식 매개변수 제한하기

interface InterfaceA
interface InterfaceB

class HandlerA : InterfaceA, InterfaceB
class HandlerB : InterfaceA
class HandlerC : InterfaceB

class ClassA<T> where T : InterfaceA, T : InterfaceB

fun main() {
    val obj1 = ClassA<HandlerA>()
    val obj2 = ClassA<HandlerB>()
    val obj3 = ClassA<HandlerC>()
}

 코드를 보면 12, 13번 코드가 에러가 날 것이다. 8번 코드에 T를 interfaceA, interfaceB를 갖춘 매개변수를 제한한다는 의미이다.

그러므로 inerfaceA와 interfaceB 모두 갖춘 HandlerA 만 가능하다. 

728x90

'Programming > Kotlin' 카테고리의 다른 글

[Kotlin/코틀린] 배열  (0) 2020.10.04
[Kotlin/코틀린] 입력과 출력  (0) 2020.10.03
[Kotlin] 연산자 오버로딩  (0) 2020.03.18
[Kotlin] 내부 클래스(2)  (0) 2020.03.18
[Kotlin] 내부 클래스(1)  (0) 2020.03.15
profile

세상을 더 편리하게

@쵱니

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!