コトリンのジェネリック医薬品
1概要
この記事では、Kotlin言語の
ジェネリック型
に注目します。
それらはJava言語のものと非常に似ていますが、Kotlin言語の作成者は
out
や
in.
のような特別なキーワードを導入することによってそれらをもう少し直感的で理解しやすいものにすることを試みました。
2パラメータ化クラスの作成
パラメータ化されたクラスを作成したいとしましょう。ジェネリック型を使用することで、Kotlin言語でこれを簡単に行うことができます。
class ParameterizedClass<A>(private val value: A) {
fun getValue(): A {
return value
}
}
コンストラクタを使用するときにパラメータ化された型を明示的に設定することで、そのようなクラスのインスタンスを作成できます。
val parameterizedClass = ParameterizedClass<String>("string-value")
val res = parameterizedClass.getValue()
assertTrue(res is String)
幸いなことに、Kotlinはパラメータ型からジェネリック型を推論することができるので、コンストラクタを使用するときはそれを省略できます。
val parameterizedClass = ParameterizedClass("string-value")
val res = parameterizedClass.getValue()
assertTrue(res is String)
3 Kotlin
out
および
in
キーワード
3.1.
out
キーワード
T型の結果を生成するプロデューサークラスを作成したいとしましょう。その生成された値を、タイプTのスーパータイプの参照に割り当てます。
Kotlinを使ってそれを実現するには、
総称型で
out
キーワードを使う必要があります。つまり、この参照をそのすべてのスーパータイプに割り当てることができます。 out値は与えられたクラスによってのみ生成されますが消費されません
:
class ParameterizedProducer<out T>(private val value: T) {
fun get(): T {
return value
}
}
T型の値を生成できる
ParameterizedProducer
クラスを定義しました。
次;
ParameterizedProducer
クラスのインスタンスを、そのスーパータイプである参照に割り当てることができます。
val parameterizedProducer = ParameterizedProducer("string")
val ref: ParameterizedProducer<Any> = parameterizedProducer
assertTrue(ref is ParameterizedProducer<Any>)
ParamaterizedProducer
クラスの
T
型が
out
型にならない場合、指定された文はコンパイラエラーを生成します。
3.2.
in
キーワード
時々、私たちは
T
型の参照を持ち、それを
T
のサブタイプに代入できるようにしたいという反対の状況を持っています。
-
サブタイプの参照に割り当てたい場合は、総称型に
in
キーワードを使用できます。
in keyword
は、生成されるのではなく、消費されるパラメータタイプでのみ使用できます** 。
class ParameterizedConsumer<in T> {
fun toString(value: T): String {
return value.toString()
}
}
toString()
メソッドは
T
型の値のみを消費すると宣言します。
次に、
Number
型の参照をそのサブタイプの参照 –
Double:
に割り当てることができます。
val parameterizedConsumer = ParameterizedConsumer<Number>()
val ref: ParameterizedConsumer<Double> = parameterizedConsumer
assertTrue(ref is ParameterizedConsumer<Double>)
ParameterizedCounsumer
の型
T
が
in
型にならない場合、指定された文はコンパイラエラーを生成します。
4タイプ予測
4.1. サブタイプの配列をスーパータイプの配列にコピーする
ある型の配列があり、その配列全体を
Any
型の配列にコピーしたいとしましょう。これは有効な操作ですが、コンパイラがコードをコンパイルできるようにするには、入力パラメータに
out
キーワードで注釈を付ける必要があります。
これにより、コンパイラは入力引数が
Any
のサブタイプである任意の型になることを認識できます。
fun copy(from: Array<out Any>, to: Array<Any?>) {
assert(from.size == to.size)
for (i in from.indices)
to[i]= from[i]}
from
パラメータが
out Any
タイプではない場合、
Int
タイプの配列を引数として渡すことはできません。
val ints: Array<Int> = arrayOf(1, 2, 3)
val any: Array<Any?> = arrayOfNulls(3)
copy(ints, any)
assertEquals(any[0], 1)
assertEquals(any[1], 2)
assertEquals(any[2], 3)
** 4.2. サブタイプの要素をそのスーパータイプの配列に追加する
**
次のような状況があるとしましょう。
Int
のスーパータイプである
Any
型の配列があり、この配列に
Int
要素を追加します。
Int
値をこの配列にコピーできることをコンパイラーに知らせるには、
in
キーワードを宛先配列のタイプとして使用する必要があります。
fun fill(dest: Array<in Int>, value: Int) {
dest[0]= value
}
次に、
Int
型の値を
Any:
の配列にコピーします。
val objects: Array<Any?> = arrayOfNulls(1)
fill(objects, 1)
assertEquals(objects[0], 1)
4.3. スタープロジェクション
特定の種類の値を気にしない場合があります。配列のすべての要素を印刷するだけで、この配列の要素の種類が何であるかは関係ありません。
それを達成するために、私たちは星の投影法を使うことができます:
fun printArray(array: Array<** >) {
array.forEach { println(it) }
}
それから、任意の型の配列を
printArray()
メソッドに渡すことができます。
val array = arrayOf(1,2,3)
printArray(array)
星型投影参照型を使用すると、そこから値を読み取ることができますが、コンパイルエラーが発生するため、それらを書き込むことはできません。
** 5一般的な制約
要素の配列をソートしたいとしましょう、そして各要素型は
Comparable
インターフェースを実装するべきです。一般的な制約を使用してその要件を指定できます。
fun <T: Comparable<T>> sort(list: List<T>): List<T> {
return list.sorted()
}
与えられた例では、すべての要素
T
が
Comparable
インターフェースを実装する必要があると定義しました。そうでなければ、このインタフェースを実装していない要素のリストを渡そうとすると、コンパイラエラーが発生します。
Comparableを実装する要素のリストを引数としてとる
sort
関数を定義したので、
sorted()__メソッドを呼び出すことができます。そのメソッドのテストケースを見てみましょう。
val listOfInts = listOf(5,2,3,4,1)
val sorted = sort(listOfInts)
assertEquals(sorted, listOf(1,2,3,4,5))
Int
型は
Comparable
インターフェースを実装しているので、
Ints
のリストを簡単に渡すことができます。
6. 実行時のジェネリックス
6.1. 消去タイプ
Javaと同様に、Kotlinの総称は実行時に消去されます。つまり、
ジェネリッククラスのインスタンスは実行時にその型パラメータを保持しません
。
たとえば、
Set <String>
を作成してその中にいくつかの文字列を入れると、実行時には
Set
としてしか見ることができません。
2つの異なる型パラメータを持つ2つの
Sets
を作成しましょう。
val books: Set<String> = setOf("1984", "Brave new world")
val primes: Set<Int> = setOf(2, 3, 11)
実行時には、
Set <String>
と
Set <Int>
の型情報は消去され、どちらもプレーンな
Setとして表示されます。つまり、実行時に値が
Set
であることを確認することは完全に可能ですが、それが文字列、整数、または他の何かの
Set__であるかどうかはわかりません。
その情報は消去されています。
では、Kotlinのコンパイラが
Non-String
を
Set <String>
に追加するのをどのように妨げているのでしょうか。あるいは、
Set <String>
から要素を取得したときに、その要素が
String
であることをどのように認識するのでしょうか。
答えは簡単です。
型情報の消去を担当するのはコンパイラです。
それ以前は、
books
変数に
String
要素が含まれていることを実際に知っています。
そのため、そこから要素を取得するたびに、コンパイラはそれを
String
にキャストするか、要素を追加するときには、入力をチェックします。
6.2. 具体化された型パラメータ
ジェネリックスをもっと楽しんで、その型に基づいて
Collection
要素をフィルタリングするための拡張関数を作成しましょう。
fun <T> Iterable<** >.filterIsInstance() = filter { it is T }
Error: Cannot check for instance of erased type: T
各コレクション要素の“
it is T”
部分は、その要素が
T
型のインスタンスであるかどうかをチェックしますが、型情報は実行時に消去されているため、この方法では型パラメータに反映できません。
できますか?
型消去規則は一般的に当てはまりますが、この制限を回避できる場合が1つあります。それは
Inline functions
です。
インライン関数の型パラメータは
reified
にすることができるので、実行時にそれらの型パラメータを参照することができます。
インライン関数の本体はインライン化されています。つまり、コンパイラーは、通常の関数呼び出しの代わりに、関数が呼び出される場所に直接本体を置き換えます。
前の関数を
inline
として宣言し、typeパラメータを
reified
としてマークすると、実行時にジェネリック型情報にアクセスできます。
inline fun <reified T> Iterable<** >.filterIsInstance() = filter { it is T }
インラインの具体化は魅力のように機能します。
>> val set = setOf("1984", 2, 3, "Brave new world", 11)
>> println(set.filterIsInstance<Int>())[2, 3, 11]----
別の例を書きましょう。私たちは皆、それらの典型的なSLF4jのLogger定義に精通しています。
[source,java,gutter:,true]
class User {
private val log = LoggerFactory.getLogger(User::class.java)
//... }
具体化されたインライン関数を使用して、より洗練された、構文に煩わしくない__Logger__定義を書くことができます。 [source,actionscript3,gutter:,true]
inline fun <reified T> logger(): Logger = LoggerFactory.getLogger(T::class.java)
それから我々は書くことができます: [source,java,gutter:,true]
class User {
private val log = logger<User>()
//... }
これにより、https://www.baeldung.com/kotlin-logging[logging、Kotlinの方法]を実装するためのよりクリーンなオプションが得られます。 ==== ** 6.3. インラインの具体化への深い飛び込み** それでは、インライン関数に関して特別なことは何ですか?型の具体化がそれらと一緒にしか機能しないということですか?私たちが知っているように、Kotlinのコンパイラはインライン関数のバイトコードをその関数が呼ばれる場所にコピーします。 ** 各呼び出しサイトで、コンパイラは正確なパラメータ型を知っているので、総称型パラメータを実際の型参照に置き換えることができます。 たとえば、次のように書きます。 [source,java,gutter:,true]
class User {
private val log = logger<User>()
//... }
** コンパイラが__logger <User>()__関数呼び出しをインライン化すると、実際の総称型パラメータ - ** __User.__を認識します。したがって、型情報を消去する代わりに、コンパイラは具体化の機会を捉えて実際の型パラメータを具体化します。 === ** 7. 結論** この記事では、Kotlinの総称型を調べました。 __out__および__in__キーワードを正しく使用する方法を見ました。型射影を使用し、一般的な制約を使用する一般的な方法を定義しました。 これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/core-kotlin[GitHubプロジェクト]にあります。そのままインポートして実行します。