Generic은 클래스 내부에서 사용할 자료형을 나중에 인스턴스를 생성할 때 확정한다.
자료형의 객체들을 다루는 메서드나 클래스에서 컴파일 시간에 자료형을 검사해 적당한 자료형을 선택할수 있도록 한다.
제네릭의 일반적인 사용 방법
제네릭은 앵글 브래킷(<>) 사이에 형식 매개변수를 넣어 선언하며 이때 형식 매개변수는 하나 이상 지정할 수 있다.
형식 매개변수는 자료형을 대표하는 용어로 T와 같은 특정 영문의 대문자로 사용하며 나중에 필요한 자료형으로 대체된다.
제네릭은 다양한 자료형을 다뤄야 하는 컬렉션에 많이 사용되고 있다.
class Demo<T>(t: T){
var name = t
}
fun main() {
val d = Demo<Int>(1)
val d2 = Demo<String>("yopi")
println(d.name)
println(d2.name)
//객체 생성 시 생성자에서 유추할 수 있는 자료형이라면 앵글 브래킷을 생략할 수 있다.
val d3 = Demo(1)
val d4 = Demo("yopi")
println(d3.name)
println(d4.name)
}
//1
//yopi
//1
//yopi
형식 매개변수 이름 | 의미 |
E | 요소(Element) |
K | 키(Key) |
N | 숫자(Number) |
T | 형식(Type) |
V | 값(Value) |
제네릭 클래스
형식 매개변수를 1개 이상 받는 클래스.
클래스를 선언할 때 자료형을 특정하지 않고 인스턴스를 생성하는 시점에서 클래스의 자료형을 정하는 것.
제네릭 클래스 안의 메서드에도 다음과 같이 형식 매개 변수를 사용할 수 있다.
class Demo<T> { // 1개의 형식 매개변수를 가지는 클래스
fun f(a: T){ // 매서드의 매개변수를 자료형에 사용
...
}
}
형식 매개변수를 클래스의 프로퍼티에 사용하는 경우 클래스의 내부에서는 사용할 수 없다.
자료형이 특정되지 못하므로 인스턴스를 생성할 수 없기 때문.
class Demo<T> {
var v : T // Error! 초기화 되거나 abstact로 선언돼야 함
}
대신에 주 생성자나 부 생성자에 형식 매개변수를 지정할 수 있다.
class Demo<T>(val v: T) // 주 생성자의 프로퍼티
class Demo2<T> {
val v: T
constructor(v: T) { // 부 생성자 이용
this.v = v
}
}
자료형 변환
일반적으로 상위 클래스와 하위 클래스의 선언 형태에 따라 클래스의 자료형을 변환할 수 있지만
제네릭 클래스는 가변성을 지정하지 않으면 형식 매개변수에 상/하위 클래스가 지정되어도 자료형이 변환되지 않음.
open class Parent
class Child: Parent()
class Cup<T>
fun main() {
val v1: Parent = Child() // Parent 형식의 v1은 Child의 자료형으로 변환될 수 있음
val v2: Child = Parent() // Error! 자료형 불일치
val v3: Cup<Parent> = Cup<Child>() // Error!
val v4: Cup<Child> = Cup<Parent>() // Erorr!
val v5 = Cup<Child>()
val v6 = Cup<Child> = v5 // 자료형 일치
}
형식 매개변수의 null 제어
제네릭의 형식 매개변수는 기본적으로 null 가능한 형태로 선언됨.
class Demo<T> {
fun f(arg1: T, arg2: T){ // 기본적으로 null이 허용되는 형식 매개변수
println(arg1?.equals(arg2))
}
}
fun main() {
val d = Demo<String>() // non-null로 선언
d.f("hi", "bye") // null이 허용되지 않음
val d2 = Demo<Int?>() // null이 가능한 형식으로 선언됨
d2.f(null, 10) // null 사용
}
//false
//null
null을 허용하지 않으려면 다음과 같이 특정 자료형을 지정하면 된다.
class Demo<T: Any>{}
fun main() {
val d = Demo<Int?>() // Error!
제네릭 함수 혹은 메서드
형식 매개변수를 받는 함수나 메서드를 제네릭 함수 또는 메서드라고 하며 해당 함수나 메서드 앞쪽에 <T>와 같이 형식 매개변수를 지정한다.
<K, V>와 같이 형식 매개변수를 여러개 사용할 수도 있다.
fun <T> genericFunc(arg: T): T? {...} // 매개변수와 반환 자료형에 형식 매개변수 T가 사용
fun <K, V> put(key: K, value: V): Unit {...} // 형식 매개변수가 2개인 경우
fun <T> find(arr: Array<T>, target: T): Int {
for (i in arr.indices) {
if (arr[i] == target) return i
}
return -1
}
fun main() {
val arr = arrayOf("A", "B", "C")
val arr2 = arrayOf(1, 2, 3)
println(arr.indices)
println(find<String>(arr, "B"))
println(find(arr2, 3))
}
//0..2
//1
//2
제네릭과 람다식
형식 매개변수로 선언된 함수의 매개변수를 연산할 경우에는 자료형을 결정할 수 없기 때문에 오류가 난다.
하지만 람다식을 매개변수로 받으면 자료형을 결정하지 않아도 실행 시 람다식 본문을 넘겨 줄 때 결정되므로 이런 문제를 해결할 수 있다.
/*fun <T> add(a: T, b: T): T {
return a + b // Error! 아직 자료형을 결정할 수 없음
}*/
fun <T> add2(a: T, b: T, op: (T, T) -> T): T {
return op(a, b)
}
fun main() {
val a = add2(2, 3, { a, b -> a + b })
// val a = add2(2, 3) { a, b -> a + b } // 위랑 동일
println(a)
}
//5
typealias를 사용해 람다식 매개변수의 이름을 줄수 있다.
typealias f<T> = (T, T) -> T
fun <T> add(a: T, b: T, op: f<T>): T {
return op(a, b)
}
자료형 제한하기
제네릭 클래스나 메서드가 받는 형식 매개변수를 특정한 자료형으로 제한할 수 있다.
자바에서는 extends나 super를 사용해 자료형을 제한했는데 코틀린에서는 콜론(:)을 사용해 제한한다.
클래스에서 형식 매개변수의 자료형 제한하기
class Demo<T : Number> {
fun f(a1: T, a2: T): Double {
return a1.toDouble() + a2.toDouble()
}
}
fun main() {
val d = Demo<Int>()
println(d.f(10,20))
// val d2 = Demo<String>() // Error!
}
//30.0
함수에서 형식 매개변수의 자료형 제한하기
fun <T : Number> f(a: T, b: T, op: (T, T) -> T): T {
return op(a, b)
}
fun main() {
// f("abc", "abc", { a, b -> a + b }) // Error!
}
다수 조건의 형식 매개변수 제한하기
형식 매개변수의 자료형을 제한할 때 하나가 아닌 여러 개의 조건에 맞춰 제한하고자 할 때가 있다.
형식 매개변수의 사용 범위를 지정하는 where 키워드를 사용한다.
interface Iface
interface Iface2
class Demo : Iface, Iface2
class Cemo : Iface
class Bemo<T> where T : Iface, T : Iface2 // 2개의 인터페이스를 구현하는 클래스로 제한
fun main() {
val b = Bemo<Demo>()
// val b2 = Bemo<Cemo>() // Error! 범위에 없음
}
다음은 함수에서 두 조건을 만족하는 경우.
fun <T> f(a: T, b: T): T where T:Number, T:Comparable<T> {
return if(a > b) a else b
}
상/하위 형식의 가변성
가변성(Variance)이란 형식 매개변수가 클래스 계층에 영향을 주는 것을 말함.
예를 들어 형식 A의 값이 필요한 모든 클래스에 형식 B의 값을 넣어도 아무 문제가 없다면 B는 A의 하위 형식(subtype)이 된다.
클래스와 자료형
우리가 사용하는 모든 클래스는 자료형으로 취급할 수 있다.
형태 | 클래스인가? | 자료형인가? |
String | Y | Y |
String? | N | Y |
List | Y | Y |
List<String> | N | Y |
가변성의 3가지 유형
(여기서부터 어렵다.. 넘어가자)
무변성
공변성
반공변성
공변성에 따른 자료형 제한
자료형 프로젝션
가변셩의 2가지 방법
사용 지점 변성과 자료형 프로젝션
스타 프로젝션
자료형 프로젝션의 정리
reified 자료형
reified의 사용 사례
'코틀린' 카테고리의 다른 글
Do it! 코틀린 프로그래밍, 03-1 함수 선언하고 호출하기 (0) | 2021.08.23 |
---|---|
Do it! 코틀린 프로그래밍, 08-2 배열 다루기 (0) | 2021.08.22 |
Do it! 코틀린 프로그래밍, 07-2 데이터 클래스와 기타 클래스 (0) | 2021.08.01 |
Do it! 코틀린 프로그래밍, 07-1 추상 클래스와 인터페이스 (0) | 2021.07.21 |
Do it! 코틀린 프로그래밍 - 06 프로퍼티와 초기화 (0) | 2021.07.13 |