제네릭은 클래수 내부에서 사용할 자료형을 나중에 인스턴스를 생성할 때 확정합니다.
다시 말해 어떤 자료형이 올지 모를 때 객체를 생성할 때 자료형을 정할 수 있습니다.
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 만 가능하다.
'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 |