아직까지 물어볼 수 없는 Kottlin Generics 입문

대상


Generics에 접촉하지 않았다고 자꾸 오시는 분들을 향해서(맞아요. 바로 저예요)

Generics는


총칭형으로도 불리며 형을 매개 변수로 사용할 수 있다.
일반적으로 <>의 섹션은 와 같은 Generics입니다.
Box 클래스의 스토리지 유형 변수의 예를 살펴보겠습니다.이때 Box 클래스는 다음과 같습니다.
class Box<T>(t: T) {
  var value = t
}
이것은 실례를 생성할 때 저장할 유형을 정의할 수 있는 기술이다.예를 들어 Int형 상자로 사용하려면 다음과 같은 내용을 실현할 수 있다.
val box: Box<Int> = Box<Int>(1)
또한, 스트링형 상자로 만들려면 다음과 같이 설치하십시오.
val box: Box<String> = Box<String>("test")
이 Generics의 변수명은 E와 K 등 많은 곳에서 볼 수 있지만 Generics의 뜻은 변수명으로 바뀌지 않을 것이라고 생각합니다.그러나 일반적으로 다음과 같이 구분된다.
  • T...Type(유형. 유형을 매개변수로 사용할 경우)
  • E...Element(요소, List 등에 사용)
  • K...Key(열쇠, 맵 등에서 사용)
  • V...Value(값.Map 등에서 사용)
  • R...Result(반환값)
  • Generics가 필요한 이유 (Generics 없이 이렇게 됨)


    어떤 유형이든 저장할 수 있다는 뜻이라면 앤(Java의 경우 Object)은 문제없다.
    다만 이 경우 무엇이든 저장할 수 있기 때문에 예측할 수 없는 데이터 유형을 포함할 수 있다.이것은 실행할 때classCastException이 판명하는 원인이 될 가능성이 높으며, 이 문제를 방지하기 위해 본질적으로 존재하지 않는 코드를 기술하는 경우도 증가할 수 있다.
    class Box(a: Any) {
       var value = a
    }
    	
    val box = Box(1111)
    val box2 = Box("test")
    arrayListOf<Box>(box,box2) //数値と思い込むとあとでclassCastExceptionになる
    
    이것은 형 안전이라고 할 수 없다.따라서 Generics를 사용하여 Box가 Int에만 들어갈 수 있음을 명시적으로 선언하는 것을 목표로 합니다.
    class Box<T>(t: T) {
      var value = t
    }
    	
    val box = Box(1111)
    val box2 = Box("test")
    arrayListOf<Box<Int>>(box,box2) //box2がType miss matchでコンパイルエラー
    

    이 기능의 사용처


    이 기능을 이용하면 추상화의 실현을 실현할 수 있다.우리 몇 가지 사용 예를 봅시다.

    Collection 등 컨테이너


    데이터의 조작 방법을 제공했지만 데이터 내용 자체가 통일되면 된다.예를 들어 Kotlin의 Collection 유형 정의는 다음과 같습니다.
    public interface List<out E> : Collection<E> 
    

    지정한 분류의 되돌아오는 값을 지정합니다


    DDD의 상하문에서 Application층이 응용 프로그램 논리만 기록하고 무상태를 원한다면 Stream이subscribe에 의해 디스포즈되었는지 여부가 있습니다.따라서 이러한 abstract class를 준비하여 반환 값을 제한합니다.
    abstract class FlowUseCase<in P, out R> {
        abstract fun execute(parameters: P): Flow<R>
    }
    
    class FlowUseCaseImpl: FlowUseCase<Command, Result>(){
        override fun execute(command: Command): Flow<Result> {
            return flow {
                emit(Result.Failed)
            }
        }
    
        data class Command(
            val value: String
        )
    
        sealed class Result {
            object Failed : Result()
    	object Success : Result()
        }
    }
    

    Generics


    Kotlin의 Generics는 Java의 Generics와 약간 다른 점이 있습니다.
    Kotlin Reference[1]의 경우 설명합니다.

    분산 선언


    Java를 사용하여 다음 처리를 고려해 보십시오.
    // Java
    List<String> strs = new ArrayList<String>();
    List<Object> objs = strs; // type mismatch
    
    언뜻 보면 그런 것 같지만 타입 미스매치가 발생한다.이것은 List이List의 하위 유형이 아니라는 것을 나타낸다.왜 이렇게 됐는지는 다음과 같은 처리를 고려할 필요가 있기 때문이다.
    objs.add(1); // Integer を Strings(Object) のリストへ入れる
    String s = strs.get(0); // !!! ClassCastException: Integer を String へキャストできない
    
    List을List의 하위 유형으로 설정하면 Object 클래스로 추상화한 다음에 Integer를 추가할 수 있습니다.이렇게 하면 집행시의 안전성을 보장할 수 없다.따라서 Java는 이를 금지합니다.
    그럼 이하 반은 어때요?
        abstract class Box<T> {
            abstract fun get(): T
        }
    
        fun test(box: Box<String>) {
            val anyBox : Box<Any> = box // type mismatch
        }
    
    박스에 새로 추가할 방법이 없기 때문에 박스를 대입해도 안전합니다.그런데 컴파일러는 그걸 몰라요.
    Kotlin에서 컴파일러에게 이것을 가르칠 수 있습니다.(성명서 분산)
    이 유형 T는 값이 반환될 때만 out 코스메틱 서브를 사용하여 표시됩니다.
        abstract class Box<out T> {
            abstract fun get(): T
        }
    
        fun test(box: Box<String>) {
            val anyBox : Box<Any> = box // OK!
        }
    
    그리고 in 수식자.이것은 값을 되돌릴 때 사용하지 않고 매개 변수로 사용되는 것을 나타낸다.예를 들어 다음과 같은 처리를 고려한다.
        abstract class Comparable<T> {
            abstract fun compareTo(other: T): Int
        }
    
        fun demo(x: Comparable<Number>) {
            x.compareTo(1.0)
            val y: Comparable<Double> = x // type mismatch
        }
    
    이때 T는 환가로 사용되지 않기 때문에 안전형이다.in 코스메틱 서브를 사용하여 컴파일러에 표시합니다.
        abstract class Comparable<in T> {
            abstract fun compareTo(other: T): Int
        }
    
        fun demo(x: Comparable<Number>) {
            x.compareTo(1.0)
            val y: Comparable<Double> = x // OK!
        }
    

    유형 투영


    아웃 파라미터를 이용하는 것은 매우 편리하지만 사용할 수 없는 패턴도 있다.예를 들면 ArrayList입니다.여기에dd/get 방법이 있기 때문에 T반환값만 한정할 수 없습니다.
    class Array<T>(val size: Int) {
      fun get(index: Int): T { /* ... */ }
      fun set(index: Int, value: T) { /* ... */ }
    }
    
    이렇게 하면 예를 들어 코피 방법을 사용할 때 오류가 발생할 수 있습니다.
    @SuppressLint("Assert")
    fun copy(from: Array<Any>, to: Array<Any>) {
      assert(from.size == to.size)
      for (i in from.indices)
        to[i] = from[i]
    }
    
    val ints: Array<Int> = arrayOf(1, 2, 3)
    val any = Array<Any>(3){}
    copy(ints, any) // エラー: (Array<Any>, Array<Any>) が期待されている
    
    그러나 이copy는 새로운 유형의 값을 증가시키는 것이 아니기 때문에 유형적으로 안전합니다.근데 컴파일러가 그걸 식별할 수가 없어요.Copy 에 기록될 수 있기 때문입니다.
    이 때form은 투영만 되고 out 연산자는 쓰기를 하지 않는 방법으로 사용할 수 있습니다.이것은 형투영이라고 한다.
    @SuppressLint("Assert")
    fun copy(from: Array<out Any>, to: Array<Any>) {
      assert(from.size == to.size)
      for (i in from.indices)
        to[i] = from[i]
    	
      from.set(1, "test") // エラー。書き込みはできない
    }
    
    val ints: Array<Int> = arrayOf(1, 2, 3)
    val any = Array<Any>(3){}
    copy(ints, any) // OK
    
    또한 in 코스메틱 서브를 사용하여 투영할 수 있습니다.예를 들어, 매개변수에 이전 Compoarator를 전달하려면 다음과 같이 하십시오.
    public fun <T> Array<out T>.sortWith(comparator: Comparator<in T>, fromIndex: Int = 0, toIndex: Int = size): Unit {
        java.util.Arrays.sort(this, fromIndex, toIndex, comparator)
    }
    

    성형 투영


    특별히 유형을 고려할 필요가 없는 경우도 있다.예를 들어, Log 정렬에 모든 값 출력이 투입되는 경우입니다.이것들은 out 수식자를 이용하여 다음과 같다.
        fun printLog(array: Array<out Any?>) {
            array.forEach { Timber.d(it.toString()) }
        }
    
    이것들은 다음과 같이 생략할 수 있다.(star-projections)
        fun printLog(array: Array<*>) {
            array.forEach { Timber.d(it.toString()) }
        }
    

    Generics 함수


    함수에서도 Generics를 사용할 수 있습니다.확장 함수도 마찬가지다.
    Kotlin reference 사용 예는 다음과 같습니다.
    fun <T> singletonList(item: T): List<T> {
      // ...
    }
    
    fun <T> T.basicToString() : String {  // 拡張関数
      // ...
    }
    
    일반 함수를 호출하려면 함수 이름 다음에 호출 위치에서 형식 파라미터를 지정하십시오.
    val l = singletonList<Int>(1)
    

    Generics 제약 조건


    이 T에 대해 어떤 때는 무엇이든 되는 것이 아니라 일정한 제약을 설정하려고 한다.예를 들어sort를 했기 때문에 비교할 수 있는 것만 처리하고 싶은 경우.이때 다음과 같다.
    fun <T : Comparable<T>> sort(list: List<T>) {
      // ...
    }
    
    이렇게 기재하면 Comporable를 계승한 클래스만 T로 처리할 수 있다.
    이때 Int가 Comporable을 상속받았지만 해시맵이 상속하지 않아 오류가 발생했습니다.
    sort(listOf(1, 2, 3)) // OK. Int は Comparable<Int> のサブタイプです
    sort(listOf(HashMap<Int, String>())) // エラー: HashMap<Int, String> は Comparable<HashMap<Int, String>> のサブタイプではない
    
    설정하지 않을 때 기본 제한은 Any입니까?네.
    여러 개의 제한을 설정하려면where문장을 사용하십시오.
    fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
        where T : Comparable,
              T : Cloneable {
      return list.filter { it > threshold }.map { it.clone() }
    }
    

    총결산


    일반 클래스나 설치에 제한을 가할 때 사용할 수 있습니다.
    하지만 이런 기회는 프로젝트 시작 후 한두 번 정도밖에 없기 때문에 잊어야 할 운명이다...

    참고 문헌


    [1] 제니스-kotlin reference
    https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/generics.html

    좋은 웹페이지 즐겨찾기