Chandroid
Android Development Diary
Chandroid
전체 방문자
오늘
어제
  • 분류 전체보기
    • Today I Learned (TIL)
    • Android
      • Android Weekly
      • Android 13
      • Android Debug Bridge (ADB)
      • Library
      • View
      • Jetpack
      • Gist
    • Kotlin
      • Docs
      • Clean Code
    • Object-Oriented Programming
    • Etc
      • 정보처리기사 실기
      • 주절주절

블로그 메뉴

  • 홈
  • 태그

공지사항

인기 글

태그

  • kotlin
  • customview
  • Solid
  • Extensions
  • clean code
  • RecyclerView
  • Realm
  • Today I Learned
  • til
  • Sealed classes
  • OOP
  • MVVM
  • Data classes
  • Database
  • Agile Software
  • Android 13
  • Android Debug Bridge
  • GIST
  • Glide
  • Kotlin Docs
  • DataBinding
  • DiffUtil
  • ADB
  • Android
  • generics

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Chandroid

Android Development Diary

[Kotlin Docs] Generics: in, out, where
Kotlin/Docs

[Kotlin Docs] Generics: in, out, where

2022. 5. 4. 17:04

본 글은 https://kotlinlang.org/docs/home.html 를 기반으로 작성자 마음대로 번역한 글입니다.
오역 & 의역이 빈번하며 모든 질문 및 태클 환영합니다!
2022-05-04 기준으로 작성되었습니다.

코틀린의 클래스는 자바와 동일하게 타입 파라미터를 가질 수 있습니다:

 

 

이러한 클래스의 인스턴스를 생성하려면, 간단하게 타입 인자를 제공하면 됩니다.

 

 

그러나 생성자의 인자와 같이 파라미터를 추론할 수 있다면 타입 인자를 생략할 수 있습니다.

 

 


Variance

자바 유형 시스템의 가장 까다로윤 유형 중 하나는 와일드카드 유형입니다. (Java Generics FAQ↗). 코틀린은 이들 대신에 선언부의 변화와 타입 예측이 있습니다.

 

자바가 이런 신비한 와일드카드를 필요로하는 이유에 대해 생각해봅시다. Effective Java, 3rd Edition↗의 31항목 : Use bounded wildcards to increase API flexibility에 문제가 잘 설명되어 있습니다. 첫째로, 자바의 제네릭 타입은 List<String>이 List<Object>의 서브타입이 될 수 없는 것처럼 변하지 않습니다. 만약 List가 변했다면, 다음 코드가 컴파일되지만 런타임에 예외가 발생하는 것처럼 자바의 배열보다 나을 것이 없습니다.

 

 

자바는 런타임 안전을 보장하기 위해 이러한 것들을 금지합니다. 그러나 이것은 의미가 있습니다. 예를 들어, Collection 인터페이스의 addAll() 메소드를 생각해보세요. 이 메소드의 시그니쳐는 무엇일까요? 직관적으로 당신은 이렇게 작성할 것입니다:

 

 

그러나, 다음 코드를 수행할 수 없습니다. (완벽하게 안전합니다)

 

 

(자바에서는, 이것을 어렵게 배웠을 것입니다, Effectiva Java, 3rd Edition↗, 항목 28: Prefer lists to arrays)

 

결국 addAll()의 실제 시그니쳐는 다음과 같습니다:

 

 

와일드카드 타입 인자 ? extends E 는 이 메소드가 E 자신뿐만 아니라 E 객체의 컬렉션, 혹은 E의 서브타입까지 허용함을 나타냅니다. 이것은 아이템으로부터 E를 안전하게 읽을 수 있다는 것을 의미하지만 어떤 객체가 알려지지 않은 E의 서브타입으로 있는지 모르기 때문에 쓰기는 불가능합니다. 이러한 제한의 대가로, 원하는 동작을 얻을 수 있습니다: Collection<String>은 Collection<? extends Object>의 서브타입입니다. 즉, extends-bound (upper bound)이 있는 와일드 카드는 타입 covariant를 만듭니다.

 

이것이 작동하는 이유는 간단하게 이해할 수 있습니다. 컬렉션으로부터 아이템 하나를 get할 때, String의 컬렉션을 사용해 Object를 읽는 것은 정상입니다. 반대로 아이템을 컬렉션에 put할 경우에는 Object의 컬렉션을 가져와 안에 String을 넣는 것이 가능합니다: 자바에는 List<Object>의 슈퍼타입인 List<? super String>이 있습니다.

 

후자는 contravariance라고 불리며 List<? super String>에서 인자를 String으로 사용하는 메소드만 호출할 수 있습니다. List<T>에서 T를 반환하는 것을 호출하면, String이 아닌 Object를 가져올 것입니다.

 

Joshua Bloch는 읽기 전용 오브젝트에는 Producers, 쓰기 전용 오브젝트에는 Consumers라고 이름 붙였습니다.

"유연성의 최대화를 위해, producers 혹은 consumers를 나타내는 입력 파라미터에 와일드카드 타입을 사용하십시오." 라고 다음 니모닉을 제시합니다.

PECS stands for Producer-Extends, Consumer-Super.
만약 producer-object를 사용한다면, List<? extends Foo> 이 객체로 add()나 set()을 호출하는 것은 허용되지 않지만, 불변이라는 것을 의미하는 것은 아닙니다: 예를 들어, clear()는 어떠한 파라미터도 받지 않기 때문에 리스트에서 clear()를 호출하는 것은 허용됩니다, 

와일드카드(혹은 다른 타입의 변동)으로 보장하는 것은 type safety 한가지입니다.
불변성은 완전히 다른 이야기입니다.

 

- Declaration-site variance

T를 파라미터로 하는 메소드 없이, T만 반환하는 제네릭 인터페이스 Source<T>가 있다고 가정해보겠습니다.

 

 

Source<Object> 타입의 변수에 Source<String>의 인스턴스에 참조된 것을 저장하는 것은 완벽하게 안전합니다. - 호출하는 consumer-method가 없습니다. 그러나 자바는 알지 못하므로 금지시킵니다.

 

 

이것을 수정하기 위해서는 반드시 Source<? extends Object> 타입의 객체를 선언해야합니다. 이러한 것은 이전과 같은 변수에 대해 동일한 메소드를 모두 호출할 수 있어서 더 복잡한 유형에 의해 추가되는 값이 없으므로 무의미합니다. 그러나 컴파일러는 그것을 알지 못합니다.

 

코틀린에서는, 컴파일러에게 이러한 것들을 설명하는 방법이 존재합니다. 이것은 declaration-site variance라고 불립니다: Source의 타입 파라미터 T가 Source<T>의 멤버로부터 반환(produced)되지만 절대 생성하지는 않는 것을 알려줄 수 있습니다. 이러한 것을 위해 out 지시자를 사용하세요.

 

 

일반적인 규칙: 클래스 C의 타입 파라미터 T가 out으로 선언되면, C의 멤버 out-position에서만 발생할 수 있지만, 그 덕분에 C<Base>은 안전하게 C<Derived>의 슈퍼타입이 될 수 있습니다.

 

다시 말해서, 클래스 C는 파라미터 T의 covariant이며, T는 covariant 타입 파라미터라고 말할 수 있습니다. C를 T의 producer라고 생각할 수 있으나, T의 consumer은 아닙니다.

 

out 지시자를 variance annotation라고 하며 타입 파라미터 선언부에서 제공되므로, declaration-site variance를 제공합니다. 이는 타입 사용의 와일드카드가 타입을 covariant하게 만드는 자바의 use-site variance와 대조됩니다.

 

out 외에도, 코틀린은 보완적인 변화 어노테이션인 in을 제공합니다. 이것은 타입 파라미터를 contravariant하게 만들며, consume만 가능하고 절대 produce되지 않는 다는 것을 의미합니다. contravariant 타입의 좋은 예시는 Comparable입니다.

 

 

in과 out이란 단어는 자명한 것처럼 보이며(꽤 오랫동안 C#에서 성공적으로 사용되었습니다), 따라서 위에서 언급한 니모닉은 실제로 불필요합니다. 실제로 더 높은 수준의 추상화에서 다음과 같이 바꿀 수 있습니다.

 

The Existential↗ Transformation: Consumer in, Producer out:-)

 


Type projections

- Use-site variance: type projections

타입 파라미터 T를 out으로 선언하고 use-site에서 서브 타이핑 문제를 피하는 것은 매우 쉬운 일입니다, 그러나 일부 클래스들은 실제로 T만 반환하도록 제한할 수 없습니다. Array는 이것의 좋은 예시입니다.

 

 

이 클래스는 T에서 covariant일 수도 contravariant일 수도 없습니다. 그리고 이것은 특정 유연성을 부과합니다. 다음 함수를 보십시오.

 

 

이 함수는 한 배열에서 다른 배열로 아이템을 복사합니다. 그것을 실제로 적용해봅시다.

 

 

여기서 당신은 같은 익숙한 문제에 다다릅니다: Array<T>에서 T는 불변이므로, Array<Int>도 Array<Any>도 다른 것의 서브타입이 아닙니다. 왜일까요? 다시 말하지만, 이것은 copy가 예기치 못한 동작을 할 수 있기 때문입니다. 예를 들어, from에 String을 쓰려할 수도 있고, 실제로 Int 배열을 넘기면, ClassCastException이 나중에 발생할 것입니다.

 

copy 함수가 from에 쓰기을 방지하려면, 다음과 같이 할 수 있습니다.

 

 

이것이 from이 단순한 배열이 아닌 제한되었다는(projected) 것을 의미하는 type projection입니다. 타입 파라미터 T만 반환하는 메소드만 호출할 수 있으며, 이 상황에서는 get()만 호출할 수 있다는 것을 의미합니다. 이것이 use-site variance를 사용하는 방법이며, 자바의 Array<? extends Object>에 대응하지만 더 간단합니다.

 

in을 사용하여 타입을 프로젝트할 수 있습니다.

 

 

Array<in String>은 자바의 Array<? super String>에 대응합니다. 이것은 CharSequence 배열 또는 Object 배열을 fill() 함수에 전달할 수 있음을 뜻합니다.

 

- Star-projections

때로는 타입 인자에 대해 아무 것도 알지 못하면서, 안전한 방법으로 사용하고 싶을 때가 있습니다. 여기서 안전한 방법은 제네릭 유형의 프로젝션을 정의하는 것입니다. 이러한 제네릭 타입의 구체적인 모든 인스턴스화는 프로젝션의 서브타입이 됩니다.

 

코틀린은 소위 말하는 star-projection 구문을 제공합니다.

 

  • Foo<out T : TUpper>의 경우, T는 상위 TUpper를 갖는 공변 타입 파라미터이며, Foo<*>는 Foo<out TUpper>와 동일합니다. 이것은 T를 알 수 없을때 Foo<*>에서 TUpper 값을 안전하게 read할 수 있다는 것을 의미합니다.
  • Foo<in T>의 경우, T는 반공변 타입 파라미터이며, Foo<*>는 Foo<in Nothing>과 동일합니다. 이것은 T를 알 수 없을 때 안전한 방법으로 Foo<*>에 무엇도 write할 수 없다는 것을 의미합니다.
  • Foo<T : TUpper>의 경우, T는 상위 TUpper를 갖는 불변 타입 파라미터이며, Foo<*>는 읽기에 있어 Foo<out TUpper>와, 쓰기에 있어 Foo<in Nothing>과 동일합니다.

 

제네릭 타입이 여러 타입 파라미터를 가진다면, 그들은 각각 독립적으로 예상될 수 있습니다. 예를 들어, 타입이 interface Function<in T, out U>로 선언되었다면 다음과 같은 star-projection을 사용할 수 있습니다.

  • Function<*, String>은 Function<in Nothing, String>을 의미합니다.
  • Function<Int, *>은 Function<Int, out Any?>를 의미합니다.
  • Function<*, *>는 Function<in Nothing, out Any?>를 의미합니다.
Star-projection은 Java의 raw타입과 매우 비슷하나 안전합니다.

 


Generic functions

클래스만이 타입 파라미터를 가질 수 있는 유일한 선언은 아닙니다. 함수도 가능합니다. 타입 파라미터는 함수의 이름 이전에 위치합니다.

 

 

제네릭 함수를 호출하려면, 함수의 이름 이후의 호출부에서 타입 인수를 지정합니다.

 

 

타입 인수는 context로부터 추론될 수 있다면 생략될 수 있습니다, 다음 예시가 잘 보여줍니다.

 

 


Generic constraints

특정 타입 파라미터 대신 가능한 모든 타입의 세트는 generic constraint에 의해 제한될 수 있습니다.

 

- Upper bounds

제약의 가장 일반적인 유형은 Java의 extends 키워드에 대응하는 upper bound입니다.

 

 

콜론 뒤에 지정된 타입이 Comparable<T>의 서브타입만 T를 대체할 수 있다는 것을 나타내는 upper bound입니다.

 

 

기본 upper bound (지정되지 않았을 경우)는 Any?입니다. 하나의 upper bound만이 꺾쇠 괄호 안에서 지정될 수 있습니다. 같은 타입 파라미터가 두개 이상의 upper bound를 필요로 한다면 분리된 where절이 필요합니다.

 

 

전달된 타입은 where절의 모든 조건을 동시에 만족해야 합니다, 위의 예시에서 T는 CharSequence와 Comparable을 모두 구현해야합니다.

 


Type erasure

코틀린에서 제네릭 선언에 대한 타입 안전성 검사는 컴파일 타임에 완료됩니다. 런타임에, 제네릭 타입의 인스턴스는 실제 타입 인수에 대한 어떠한 정보도 가지고 있지 않습니다. 타입 정보는 지워진다고 합니다. 예를 들어, Foo<Bar>와 Foo<Baz?>의 인스턴스들은 지워져 Foo<*>가 됩니다.

 

그러므로, 런타임에 특정 타입 인수를 사용하여 제네릭 타입의 인스턴스가 생성되었는지 확인할 일반적인 방법은 없으며, 컴파일러는 이러한 is-check를 금지↗합니다.

 

구체적인 타입 인수를 사용한 타입 캐스트, 예를 들어, foo as List<String>은 런타임에 확인될 수 없습니다. 이러한 unchecked-cast↗는 상위레벨 프로그램 로직에 의해 암시되지만 컴파일러에 의해 직접적으로 유추될 수는 없습니다. 컴파일러는 검사되지 않은 캐스트에 경고를 발생시키며, 런타임에는 제네릭이 아닌 부분만 확인될 수 있습니다. (foo as List<*>).

 

제네릭 함수의 타입 인수 또한 컴파일 타임에만 확인될 수 있습니다. 함수 본체 내부에서, 타입 파라미터는 타입 검사를 위해 사용될 수 없으며, 타입 파라미터(foo as T)로의 캐스트는 검사되지 않습니다. 그러나 인라인 함수의 reified type parameter↗는 인라인 함수 호출부의 본체에서 실제 타입 인수로 대체되므로 위에 설명된 제네릭타입 인스턴스의 제한과 동일하게 타입 검사와 캐스트에 사용될 수 있습니다.

 


https://kotlinlang.org/docs/generics.html
 

Generics: in, out, where | Kotlin

 

kotlinlang.org

 

저작자표시 비영리 (새창열림)

'Kotlin > Docs' 카테고리의 다른 글

[Kotlin Docs] Enum classes  (0) 2022.05.17
[Kotlin Docs] Nested and inner classes  (0) 2022.05.13
[Kotlin Docs] Sealed classes  (0) 2022.04.08
[Kotlin Docs] Data classes  (0) 2022.04.07
[Kotlin Docs] Extensions  (0) 2022.04.07
    Chandroid
    Chandroid

    티스토리툴바