일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Design Pattern
- 프로토타입 패턴
- 디자인패턴 #
- ㅋㅁ
- 추상팩토리패턴
- 코틀린
- 팩토리 메소드
- r
- 디자인패턴
- Abstract Factory
- 빌터패턴
- builderPattern
- ㅓ
- F
- 추상 팩토리
- factory method
- designPattern
- El
- PrototypePattern
- Functional Programming
- Kotlin
- 함수형프로그래밍
- 옵저버 패턴
- 싱글톤
- Singleton
- a
- Observer Pattern
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
변성: 제네릭과 하위 타입 본문
변성의 개념은 List<String>과 List<Any>와 같이 기저 타입이 같고 타입 인자가 다른 여러 타입이 서로 어떤 관계가 있는지를 설명하는 개념이다.
변성이 있는 이유
List<Any> 타입의 파라미터를 받는 함수에 List<String>을 넘기면 안전할까?
자신있게 안정성을 보장할 수 없다.
fun printContents(list: List<Any>){
println(list.joinToString())
}
fun main() {
val list = listOf("abc", "efg")
printContents(list)
}
위 경우는 잘작동 한다. 각 원소를 Any 타입으로 받아들이는데 String은 Any의 하위 타입이기 때문이다.
어떤 함수가 리스트의 원소를 추가하거나 변경한다면, 타입의 불일치가 생겨 불가능하지만, 원소의 추가나 변경이 없으면 가능하다.
클래스, 타입, 하위타입
클래스 = 타입 일까?
var x: String
var x: String?
클래스 이름을 널이 될 수 있는 타입에도 사용하기에 최소한 코틀린에서 클래스에는 2개 이상의 타입을 구성할 수 있다.
제네릭 클래스는 더욱 복잡하다.
구체적인 타입을 얻기 위해서는 타입 파라미터를 구체적인 타입 인자로 치환해야된다.
List는 타입이 아닌 클래스다.
List<Int>, List<String>, List<Person>등 제네릭 클래스는 무수히 많은 타입을 생성할 수있다.
하위타입
타입 A의 값이 필요한 곳에 타입 B의 값을 넣어도 아무 문제가 없다면 B는 A의 하위타입이다.
상위타입은 반대다.
한 타입이 다른 타입의 하위타입인지가 왜 중요할까?
컴파일러는 변수 대입 및 함수로 인자 전달시 하위타입 검사를 매번 실행한다.
간단한 경우 하위 타입은 하위 클래스랑 같다.
널이 될 수 없는 타입은 널이 될 수 있는 타입의 하위 타입이다. 하지만 두 타입 모두 동일한 클래스다.
항상 널이 될 수 없는 타입의 값을 널이 될 수 있는 타입의 변수에 저장할 수는 있지만 반대로 널이 될 수 없는 타입에 널이 될수 있는 타입을 저장할 수없다.
타입 List<String>을 타입 List<Any>를 파라미터로 받는 함수에도 전달 해도 괜찮은가는
즉, List<String>이 List<Any>의 하위 타입인가?와 같다.
제네릭 타입을 인스턴스화 할 때,
타입 인자로 서로 다른 타입이 들어가면 인스턴스 타입 사이의 하위 타입관계가 성립하지 않으면 그 제네릭 타입을 무공변성이라고 한다.
A가 B의 하위 타입이면, List<A>는 List<B>의 하위 타입이다. 이런 클래스나 인터페이스를 공변적이라 말한다.
공변성: 하위 타입 관계를 유지한다.
cat은 Animal의 하위 타입이다, Producer<A>는 Producer<B> 하위 타입이다.
코틀린에서 제네릭 클래스 타입 파라미터에 대해 공변적임을 표시하려면 파라미터 앞에 out을 넣어야 한다.
interface Producer<out T>{
fun produce(): T
}
타입 파라미터를 공변적으로 만들면 함수 정의에 사용한 파라미터 타입과 타입 인자가 정확히 일치하지 않더라도 그 클래스의 인스턴스를 함수 인자나 반환 값으로 사용가능하다.
무공병선 역활을 하는 클래스 정의 하기.
open class Animal(val kind: String){
fun feed() {
println("${kind}가 먹이를 먹는다.")
}
}
class Herd<T: Animal>(val animals: List<T>) {
val size: Int
get() = animals.size
operator fun get(i: Int): T = animals[i]
}
fun feedAll(animals: Herd<Animal>){
for (i in 0 until animals.size){
animals[i].feed()
}
}
fun main() {
val animals = listOf(Animal("고양이"), Animal("개"), Animal("호랑이"))
val herd = Herd(animals)
feedAll(animals = herd)
}
무공변 컬렉션 역활을 하는 클래스 사용하기.
open class Animal(val kind: String){
fun feed() {
println("${kind}가 먹이를 먹는다.")
}
}
class Cat(val name: String) : Animal("고양이"){
fun cleanLitter(){
println("$kind $name is cleaned")
}
}
class Herd<T: Animal>(val animals: List<T>) {
val size: Int
get() = animals.size
operator fun get(i: Int): T = animals[i]
}
fun feedAll(animals: Herd<Animal>){
for (i in 0 until animals.size){
animals[i].feed()
}
}
fun takeCareOfCats(cats: Herd<Cat>){
for (i in 0 until cats.size) {
cats[i].cleanLitter()
//feedAll(cats) 에러 inferred type is Hers<Cat> but Herd<Animal> was expected
}
}
fun main() {
val cats = listOf(Cat("나비"), Cat("두부"), Cat("유자"))
val catHerd = Herd(cats)
takeCareOfCats(cats = catHerd)
}
feedAll 은 사용이 불가능하다. 타입 불일치 로 인해서,
Herd 클래스의 T타입 파라미터에 대해 어떤 변성도 지정하지 않았기 때문에 고양이 무리는 동물 무리의 하위 타입이 아니다.
공변적 컬렉션 역활하는 하는 클래스로 변경
open class Animal(val kind: String){
fun feed() {
println("${kind}가 먹이를 먹는다.")
}
}
class Cat(val name: String) : Animal("고양이"){
fun cleanLitter(){
println("$kind $name is cleaned")
}
}
class Herd<out T: Animal>(val animals: List<T>) {
val size: Int
get() = animals.size
operator fun get(i: Int): T = animals[i]
}
fun feedAll(animals: Herd<Animal>){
for (i in 0 until animals.size){
animals[i].feed()
}
}
fun takeCareOfCats(cats: Herd<Cat>){
for (i in 0 until cats.size) {
cats[i].cleanLitter()
}
feedAll(catHerd) // 호출 가능하다.
}
fun main() {
val cats = listOf(Cat("나비"), Cat("두부"), Cat("유자"))
val catHerd = Herd(cats)
takeCareOfCats(cats = catHerd)
}
모든 클래스를 공변적으로 할 수는 없다. 공변적으로 만들면 안전하지 못한 클래스도 있다.
타입 파라미터를 공변적으로 지정하면 클래스 내부에서 그 파라미터를 사용하는 방법을 제한한다.
타입 안정성을 보장하기 위해 공변적 파라미터는 항상 OUT위치에 만 있어야된다.
클래스가 T 타입의 값을 생산할 수는 있지만, 타입의 값을 소비할수는 없다
클래스 맴버를 선언할 때 타입 파라미터를 사용할 수 있는 지점은
In - T가 함수의 파라미터 타입에 쓰인다.(소비한다.)
Out - T가 함수의 반환값에 쓰인다(생산한다.)
Herd클래스 타입 파라미터 T를 사용하는 장소는 오직 get의 반환 타입뿐이다.
class Herd<out T: Animal>(val animals: List<T>) {
val size: Int
get() = animals.size
operator fun get(i: Int): T = animals[i]
}
Cat이 Animal의 하위 타입이라 Herd<Animal>의 get을 호출하는모든 코드는 Cat을 반환해도 아무문제 없이 작동한다.
공변성: 하위 타입의 관계가 콜렉션 및 타입인자로 사용될때 관계가 유지된다.
사용제한: T를 반환 위치에서만 사용가능하다.
'Kotlin in Action > 코틀린 답게 사용하기' 카테고리의 다른 글
반공변성: 뒤집힌 하위 타입 관계 (0) | 2021.03.02 |
---|---|
변성: 제네릭과 하위 타입2 (0) | 2021.03.02 |
실행시 제네릭스의 동작 (0) | 2021.03.02 |
타입 파라미터의 제약 (0) | 2021.03.02 |
제네릭스 (0) | 2021.02.26 |