Kotlin Generic #1
들어가면서
Kotlin을 공부하면서 새로 알게되는 부분을 정리합니다.
package function
abstract class Animal(
val name: String,
)
abstract class Fish(name: String): Animal(name)
class GoldFish(name: String): Fish(name)
class Carp(name: String): Fish(name)
class Cage{
private val animals: MutableList<Animal> = mutableListOf()
fun getFirst(): Animal{
return animals.first()
}
fun put(animal: Animal){
this.animals.add(animal);
}
fun moveFrom(cage: Cage){
this.animals.addAll(cage.animals);
}
}
class Cage2<T>{
private val animals: MutableList<T> = mutableListOf()
fun getFirst(): T{
return animals.first()
}
fun put(animal: T){
this.animals.add(animal);
}
fun moveFrom(cage: Cage2<T>){
this.animals.addAll(cage.animals);
}
}
fun main(){
val cage = Cage()
cage.put(Carp("잉어"))
// 1. val carp: Carp = cage.getFirst() as Carp // 타입케스팅을 하는데, 다른 객체를 넣었을땐 runtime error가 발생함
// 2. Safe Type Casting과 Elvis Operator
// 3. Generic
val cage2 = Cage2<Carp>()
cage2.put(Carp("잉어"))
val carp = cage2.getFirst()
val goldFish = Cage2<GoldFish>()
goldFish.put(GoldFish("금붕어"))
val cage3 = Cage2<Fish>()
cage.moveFrom(goldFish) // Type MisMatch
}
상위 타입과 하위 타입의 의미
- Int -> Number
- 상위 타입이 들어가는 자리에 하위타입이 들어갈수있다.
Cage2
JAVA의 배열은 제네릭과 다르다. A 객체가 B객체의 하위 타입이면 상위 타입으로 간주 상속관계가 그대로 간다.
Java의 배열은 이런 코드가 가능하다.
String [] strs = new String[] {"A","B","C"};
Object[] objs = strs;
objs[0] = 1; // 런타임때 오류가 난다.
즉, 자바도 타입 안전하지 않는 코드가 작성될수 있다.
List는 제네릭을 사용하기 때문에 무공변하다.
배열보다는 리스트를 사용하는 이유.
List<String> strs = List.of("A","B","C")
List<Object> objs = strs; // Type Mismatch!
상속관계가 Fish와 GoldFish가 상속관계가 유지되게 하면 컴파일 오류를 해결할 수 있다.
out을 써서 변성을 주다.
out을 붙이게 되면, otherCage로 부터 꺼내게 된다. out을 넣으면 생산자 역할만 할수있고, getAnimals(), getFirst() 함수만 호출할 수 있다.
타입 안정성이 깨질수 있기 때문에, 생산자 역할만 한다.
fun main() {
val cage: Cage2<out Fish> = Cage2<GoldFish>()
}
class Cage2<T : Any> {
private val animals: MutableList<T> = mutableListOf()
fun getFirst(): T {
return animals.first()
}
fun put(animal: T) {
this.animals.add(animal)
}
fun moveFrom(otherCage: Cage2<out T>) {
this.animals.addAll(otherCage.animals)
}
}
in을 쓰는 케이스, 반 공변하다.
in은 소비자
fun main() {
val fishCage = Cage2<Fish>()
val goldFishCage = Cage2<GoldFish>()
goldFishCage.put(GoldFish("금붕어"))
goldFishCage.moveTo(fishCage)
}
금붕어를 Fish Cage로 옮기는 것도 Type mismatch가 발생한다.
fun moveTo(otherCage: Cage2<in T>) {
otherCage.animals.addAll(this.animals)
}
제네릭은 JDK 초기 버전부터 있던게 아니다.
자바 5부터 List
런타임 때는 타입정보가 사라진다. -> 타입 소거
star projection을 활용해서 최소한 List인지는 확인할 수있다. 해당 타입 파라미터에 어떤 값이 들어있는지는 모른다. 그러나 리스트는 확실함
if( data is List<*>){
val element: Any? = data[0]
}
mutableList에선 함부로 데이터를 넣을 수는 없다. 제네릭 함수에서도 타입정보는 사라진다.
주어진 리스트에 T 타입의 정보를 알아야하는 상황 일때 inline 함수와 reified 지시어를 사용해서 처리한다.
fun main() {
val numbers = listOf(1,2f,3)
println(numbers.filterIsInstance<Float>())
}
inline fun <reified T> List<*>.hasAnyInstanceOf() : Boolean{
return this.any {it is T}
} // T의 인스턴스를 만들거나 T의 companion을 쓸순없음