제네릭은 인자로 사용하는 타입에 따라 구체화되는 클래스나 인터페이스를 의미한다.
코틀린에서도 자바와 동일하게 <>로 제네릭을 사용한다.
//java
List<String> names;
Map<String, String> entries;
//kotlin
val names: List<String>
val entries: Map<String, String>
제네릭클래스에 타입을 넣지 않고 선언이 가능한 자바와 달리, 코틀린은 반드시 타입을 넣어 주어야 한다. -> 컴파일에러
//java
//List<Object>로 암시적으로 선언됨.
List names;
//kotlin
//컴파일 에러
val names: List
제네릭을 사용하는 클래스나 인터페이스를 정의하는 방법도 자바와 동일하다.
//java
class Car {
...
}
//항목을 담거나 뺄 수 있는 지네릭 인터페이스 정의
interface Container<T> {
void put(T item);
T take();
}
class Garage implements Container<Car> {
@Override
public void put(Car item) {
...
}
@Override
public Car take() {
...
}
}
//kotlin
class Car {
...
}
interface Container<T> {
fun put(item: T)
fun take(): T
}
class Garage: Container<Car> {
override fun put(item: Car) {
...
}
override fun take(): Car {
...
}
}
인자로 받을 수 있는 타입을 한정하는 방법 또한 동일하다.
//java
interface Container<T extends Car> {
void put(T item);
T take();
}
//kotlin
interface Container<T: Car> {
fun put(T item)
fun take(): T
}
타입이 정의되어 있는 제네릭을 인자로 받거나 호출시점에 타입을 지정하는 함수는 자바와 동일한 방법으로 정의한다. 단, 호출시점에 타입을 정의하는 함수는 타입정의 위치가 자바와 약간 다르다.
//java
//이미 타입이 정해진 제네릭을 인자로 받는 예
void processItems(List<String> items) {
...
}
//호출시점에 타입이 정해지는 제네릭을 인자로 받는 예
public <T> void processItems(List<T> items) {
List<T> datas = new ArrayList<>(items);
}
//kotiln
//이미 타입이 정해진 제네릭을 인자로 받는 예
fun processItems(items: List<Strings>) {
...
}
//호출시점에 타입이 정해지는 제네릭을 인자로 받는 예
fun <T> processItems(items: List<T>) {
...
}
호출시점에 타입이 정해지는 제네릭을 인자로 받는경우, 정해지는 타입 및 그 하위 타입을 받도록 지정하거나 (upper bound) 정해지는 타입 및 그 상위 타입을 받도록 (lower bound) 지정할 수 있다.
자바에서의 ? super T, ? extends T는 각각 코틀린에서 in T, out T로 사용한다.
class Car { ... }
class Sedan extends Car { ... }
class Truck extends Car { ... }
//dst로 받은 목록을 dest에 추가한다.
<T> void append(List<? super T> dest, List<? extends T> dst) {
dest.addAll(dst);
}
//사용 예
List<Sedan> sedans = ...;
List<Truck> trucks = ...;
List<Car> cars = ...;
append(cars, sedans);
append(cars, trucks);
//kotlin
open class Car { ... }
class Sedan: Car() { ... }
class Truck: Car() { ... }
fun <T> append(dest: MutableList<in T>, src: List<out T>) {
dest.addAll(dst)
}
//사용 예
val sedans: List<Sedan> = ...
val trucks: List<Truck> = ...
val cars: MutableList<Car> = ...
append(cars, sedans)
append(cars, trucks)