In, Out에 관하여

최희창·2022년 6월 1일
0

Kotlin

목록 보기
5/13

제네릭(Generic)

  • in,out 키워드를 알아보기 전에 먼저 제네릭에 대해 알아야 합니다.
  • 자주 보았던 <> 요런 모양을 가진 친구입니다.
  • 클래스, 인터페이스, 함수 등에서 동일한 코드를 재사용하고 싶을 때 여러 타입을 지원하기 위한 유용한 기능입니다.
fun <T> wrap(value: T) {
    println(value)
}

fun main() {
    wrap(1)
    wrap("abc")
    wrap(1.3)
}

//1
//abc
//1.3

불변성(Invariance)

  • 타입 불변성이란, 제네릭 타입을 사용하는 클래스, 인터페이스에는 해당 타입의 자식이나 부모를 대입할 수 없고 오직 일치하는 타입만을 대입할 수 있는 것을 말합니다.
open class Animal
class Cat : Animal()
class Dog : Animal()
val cats: Array<Cat> = arrayOf(Cat(), Cat())

// Error - Type mismatch: inferred type is Array<Cat> but Array<Animal> was expected
val animals: Array<Animal> = cats

-> 여기서 컴파일 에러가 발생하니다. 즉, A가 B를 상속받아도 Class<'A'>는 Class<'B'>를 상속받지 않는다라는 것입니다.

  • 이러한 Invariance가 존재하는 이유는 아래와 같은 문제를 피하기 위함이다.
fun myAnimals(animals: Array<Animal>) {
    animals[0] = Dog() // Array<Cat> cats[0] = Dog() (??!)
}

fun main() {
    val cats: Array<Cat> = arrayOf(Cat(), Cat())
    myAnimals(cats)
}

-> Cat 자료형의 ArrayList에 Dog타입의 값이 들어가므로 문제가 발생한다.

하지만 아래의 코드는 에러가 발생하지 않는다.

fun myAnimals(animals: List<Animal>) {
    println(animals[0])
}

fun main() {
    val cats: List<Cat> = listOf(Cat(), Cat())
    myAnimals(cats)
}

->차이는 Array를 List로 바꿨을 뿐입니다. Array는 값을 바꿀 수 있는 가변, List는 값을 바꿀 수 없는 불변입니다.

Array와 List의 내부 선언의 형태는 아래와 같은데 out/in에 대해서는 밑에서 알아보도록 합니다.

public class Array<T>
public interface List<out E>

<'out T'>, 공변성으로의 변환

  • 위와 같은 불변성에 대한 제약은 코드의 안전성을 보장하는 데에 큰 도움을 주지만, 아래와 같은 상황때문에 만능은 아닙니다.
fun copyFromTo(from: Array<Animal>, to: Array<Animal>) {
    for (i in from.indices) {
        to[i] = from[i]
    }
}

fun main() {
    val animals: Array<Animal> = arrayOf(Animal(), Animal())
    val cats: Array<Cat> = arrayOf(Cat(), Cat())

    // Error - Type mismatch: inferred type is Array<Cat> but Array<Animal> was expected
    copyFromTo(cats,animals)
}

-> Animal 타입의 리스트에 Cat 타입의 리스트 요소를 넣는 과정은 문제가 발생하지 않지만, A가 B를 상속받아도 Class<'A'>는 Class<'B'>를 상속받지 않는다 라는 불변성의 원리로 컴파일 에러가 발생합니다.

  • 이를 해결하기 위해서는 A가 B를 상속받으면 Class<'A'>는 Class<'B'>를 상속받는다라고 바꿔주어야 한다. 이를 공변성이라고 하는데 사용하는 키워드가 out이다.
fun copyFromTo(from: Array<out Animal>, to: Array<Animal>) {
    for (i in from.indices) {
        to[i] = from[i]
    }
}

fun main() {
    val animals: Array<Animal> = arrayOf(Animal(), Animal())
    val cats: Array<Cat> = arrayOf(Cat(), Cat())

    copyFromTo(cats,animals)
}
  • out 키워드를 사용하는 from 리스트는 공변성을 가지게 되는데, 이때 무조건 read만 가능하다. write할 시 문제가 발생한다.

<'int T'>, 반공변성으로의 변환

  • 공변성과 반대로 write만 할 수 있고 read는 할 수 없는 특성이다.
  • 다시 코드로 돌아가면
fun copyFromTo(from: Array<out Animal>, to: Array<Animal>) {
    for (i in from.indices) {
        to[i] = from[i]
    }
}

fun main() {
    val anys: Array<Any> = arrayOf(Any(), Any())
    val cats: Array<Cat> = arrayOf(Cat(), Cat())

    // Error - Type mismatch: inferred type is Array<Any> but Array<Animal> was expected
    copyFromTo(cats,anys)
}

-> 역시나 위 코드에서는 문제가 발생하는데, 이를 해결하기 위해서는 A가 B를 상속받으면 Class<'B'>는 Class<'A'>를 상속받는다로 바꿔주어야 한다. 이를 반공변성이라 하며 키워드는 in이다.

fun copyFromTo(from: Array<out Animal>, to: Array<in Animal>) {
    for (i in from.indices) {
        to[i] = from[i]
    }
}

fun main() {
    val anys: Array<Any> = arrayOf(Any(), Any())
    val cats: Array<Cat> = arrayOf(Cat(), Cat())
    
    copyFromTo(cats,anys)
}
  • 반공변성의 경우 write는 가능하지만 제네릭 타입으로의 read는 불가능하다.

참고 : https://medium.com/mj-studio/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%A0%9C%EB%84%A4%EB%A6%AD-in-out-3b809869610e

profile
heec.choi

0개의 댓글