인프런에서 제공하고 있는 코틀린 고급편 강의를 수강한 후 정리한 글입니다.
지난 시간 리뷰
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)
}
}
지난 시간에 금붕어 케이지에 금붕어를 넣었었고, 금붕어를 물고기 케이지로 옮겨 넣으려 했지만, 안됐다 근데 그냥 금붕어를 물고기 cage 에 넣는 것은 가능했다.
상속 관계의 의미는, 상위 타입이 들어가는 자리에 하위 타입이 대신 위치할 수 있다는 의미이다.
결론적으로 Cage2 에 Cage2 를 넣으려고 하고 있는 것이다.
그냥 Fish 와 GoldFish 는 상속관계를 갖고 있지만, 해당 클래스들은 아무 관계도 아니기 때문에, 에러가 난다. 이것을 무공변, 불공변하다라고 한다. (Invariate)
Java 코드를 살펴보자
Java 의 배열은 제너릭과 다르다. A 객체와 B 객체가 상/하위 타입이라면, 이것이 배열까지 유지된다. 이것을 공변하다(co-variant) 라고 부른다.
Java 의 배열은 공변하기 때문에 다음과 같은 코드를 작성할 수 있는데, 타입 안전하지 않아 위험한 코드이고, 런타임 에러가 발생한다.
String[] strs = new String[]{"A", "B", "C"};
Object[] objs = strs;
objs[0] = 1; // 가능하다. 여기가 문제다. Exception 이 난다.
List 는 무공변
List 는 무공변하기 때문에, 에러가 나는 것이다. 타입 안전하기 때문에 배열보다는 리스트를 사용해야한다.
동작하게 하기 위한 방법
말그대로, moveFrom 함수를 호출할 때, 상속관계가 유지되면 된다. 즉, cage2 가 cage2 관계를 유지시켜주면 된다.
공변
다음과 같은 out
키워드를 붙여주면 된다. 매우 간단하다.
공변을 준다라는 것을 변성이라고 부른다. 다음과 같이 out
을 통해서 변성을 주었기 때문에, 이것을 variance annotation
이라고도 부른다.
fun moveFrom(cage: Cage2<out T> {
this.animals.addAll(cage.animals)
}
fun main() {
val cage = Cage2<Fish>()
cage.moveFrom(goldFishCage)
val fish: Fish = cage.getFirst() // Fish Type
}
out 이란?
otherCage 는 데이터를 꺼내는 getAnimals() getFirst() 함수만 호출할 수 있다.
fun moveFrom(otherCage: Cage2<out T> {
otherCage.getFirst() // 꺼내는 건 가능하지만
otherCage.put(this.getFirst()) // 불가. 넣는건 할 수 없다.
this.animals.addAll(otherCage.animals)
}
otherCage 는 생산자 역할만 할 수 있다고도 할 수 있다. 왜 막을까?
다음과 같은 경우, 타입 안정성이 깨지기 때문이다.
otherCage.put(this.getFirst()) // 된다고 가정하자
val goldFishCage = Cage2<GoldFish>() goldFishCage.put(GoldFish("금붕어"))
val cage2 = Cage2<Fish>()
cage2.put(Carp("잉어"))
cage2.moveFrom(goldFishCage)
때문에, out
키워드가 생기면 꺼내는 함수만 호출이 가능한 것이다.
반공변 (contra-variant)
다음과 같은 경우는 어떻게 해야할까? 현재 Cage 에서 동물을 주어진 Cage 로 보내야 하는 경우이다. 이번에는 Cage2 가 Cage2 의 상위 타입으로 간주되어야 하는 경우이다.
다음과 같이 in
키워드를 붙인다.
fun main() {
val fishCage = Cage2<Fish>()
val godFishCage = Cage2<GoldFish>()
goldFishCage.put(GoldFish("goldfish"))
goldFishCage.moveTo(fishCage)
}
fun moveTo(otherCage: Cage2<in T>) {
otherCage.animals.addAll(this.animals)
}
otherCage 는 데이터를 받을 수만 있다. otherCage 는 소비자이다.
정리
'[ Kotlin ]' 카테고리의 다른 글
코틀린의 제네릭 제약과 제너릭 함수 (0) | 2024.08.29 |
---|---|
코틀린의 선언 지점 변성/ 사용 지점 변성 (0) | 2024.08.29 |
코틀린의 제너릭과 타입 파라미터 (0) | 2024.08.29 |
코틀린의 scope function - 20강 (0) | 2024.08.29 |
코틀린에서 부가적으로 알아둘만한 것들 - 19강 (0) | 2024.08.29 |