KotlinのパラダイムはJavaのパラダイムと似ていますが、Javaパラダイムの紹介に関する私の記事を参照できます。
上記のブログ投稿では、Javaがバージョン1.5でパラダイムを導入する理由と、Javaパラダイムに関するいくつかの基本的な知識ポイントについて説明しています。
オブジェクトをステートメントに分割し、2つの部分で使用する場合。ジェネリックは主に型宣言のコードの再利用に焦点を当て、ワイルドカードは使用中のコードの再利用に焦点を当てています。ジェネリックは内部データ型のパラメーター化を定義するために使用され、ワイルドカードは使用されるオブジェクト型のパラメーター化を定義するために使用されます。
ジェネリックとワイルドカードを使用すると、コードの再利用が向上します。同時に、オブジェクトのタイプのタイプセーフティがチェックされ、タイプ変換プロセス中のエラーが減少します。
パラダイムと配列型の変更
Javaの配列は共変です
次のコードはコンパイルして正しく実行できます。
Integer[] ints = new Integer[3];
ints[0] = 0;
ints[1] = 1;
ints[2] = 2;
Number[] numbers = new Number[3];
numbers = ints;
for (Number n : numbers) {
System.out.println(n);
}
Javaでは、IntegerはNumberのサブタイプであるため、配列型Integer []もNumber []のサブタイプであるため、Number []値が必要な場合はいつでもInteger []値を提供できます。
Javaはリスト<T>ジェネリックに共変ではありません
つまり、リスト<整数>はリスト<数値>のサブタイプではなく、リスト<数値>が必要な場所にリスト<整数>を提供しようとすると、タイプエラーになります。次のコードでは、コンパイラーが直接エラーを報告します。
ワイルドカードを使用する場合でも、次のように記述します。
まだエラーです。
NumberオブジェクトをIntegerでインスタンス化できるのに、ArrayList <Number>オブジェクトをArrayList <Integer>でインスタンス化できないのはなぜですか?リストの<?extends Number>は、その要素が数値または数値の派生クラスであることを宣言していますが、なぜ整数を追加できないのですか?これらの問題を解決するには、Javaの反転と共分散、およびジェネリックでのワイルドカードの使用を理解する必要があります。
反転、共分散、および不変性はすべて、型変換後の継承関係を表すために使用されます。その定義:AとBが型を表す場合、f(⋅)は型変換を表し、≤は継承関係を表します(たとえば、A ≤Bは、AがBから派生したサブクラスであることを意味します。
- A≤Bのとき、f(A)≤f(B)が成り立つとき、f(⋅)は共変です
- A≤B、f(B)≤f(A)が成立する場合、f(⋅)は反変
- A≤Bの場合、上記の2つの式は真ではありません。つまり、f(A)とf(B)は互いに継承関係を持たず、f(⋅)は不変です。
共分散と逆共分散はどちらもタイプセーフです。
コトリンの配列は共変ではありません
abstract class Animal(val size: Int)
class Dog(val cuteness: Int): Animal(100)
class Cat(val terrorFactor: Int): Animal(1)
次の配列コンパイルエラー:
val dogArr: Array<Dog> = arrayOf(Dog(1), Dog(2))
val animalArr: Array<Animal> = dogArr
Javaの通常のオブジェクトと同様に、次のコードをコンパイルできます。
val dog: Dog = Dog(10)
var animal: Animal = dog
Kotlinでは、パラダイムクラスは次のように定義されており、使用するとエラーが発生してコンパイルされます。
class ReadableList<T>{
}
val dogReadable: ReadableList<Dog> = ReadableList()
//提示报错,需要ReadableList<Animal>,但却传了ReadableList<Dog>
val animalReadable: ReadableList<Animal> = dogReadable
Kotlin はList <T>ジェネリックに共変です
つまり、次のコードは、
val dogList: List<Dog> = listOf(Dog(10), Dog(20))
playAnimal(dogList)
fun playAnimal(animalList: List<Animal>) {
...
}
JavaとKotlinに共変とインバーターのサポートを追加する方法
Javaのジェネリックスは変更されていませんが、場合によっては反転と共分散を実装する必要があります。どうすればよいですか?現時点では、前に説明したワイルドカードを使用する必要があります?
。
JavaとKotlinでは、デフォルトで共分散をサポートしないパラメーター型のサポートを追加できます。ただし、JavaとKotlinの2つの言語は異なる方法で処理されます。
- Java:使用サイトの差異
- Kotlin:宣言サイトの差異
Kotlinが宣言型の端末タイプを使用するのに対し、Javaは端末タイプの変更を使用することがわかります。2つの違いは何ですか?
個人的な理解では、Classオブジェクトを具体的に使用(初期化)する場合は、エンドサイト差異(使用サイト差異)を共変量化することです。
Java <? extends T>
は一般的な共分散を実装します
List<? extends Number> list = new ArrayList<>();
ここ? extends Number
での表現はNumberクラスまたはそのサブクラスであり、Cと省略します。
ここではC <= Number
、この関係が成り立つList<C> <= List< Number >
真:。あります:
List<? extends Number> list1 = new ArrayList<Integer>();
List<? extends Number> list2 = new ArrayList<Float>();
次のコードに示す別の例:
List<Cat> catList = new ArrayList<>();
List<? extends Animal> animalList = catList;
ご覧のように、animalListを宣言したときに、ジェネリック型に少し変更を加えました?Extends Animalを使用して変更した後、上記のコードを正常にコンパイルして実行できます。次のように、このパラメーター型を受け入れるメソッドを定義することもできます。
List<Cat> cats = new ArrayList<>();
playAnimal(cats);
public static void playAnimal(List<? extends Animal> animal) {
...
}
コンパイルはスムーズに渡せるので、コードはよりスケーラブルになります!
⚠️ 注:現時点では、nullを除いて、AnimalサブクラスのオブジェクトをanimalListに追加することはできません。つまり、次のコードはエラーを報告します。
如果可以添加的话,List<? extends Number>
さまざまなNumberサブタイプ(Byte、Integer、Float、Doubleなど)のオブジェクトを保持します。タイプの一貫性を保護するために、JavaはList <?Extends Number>へのオブジェクトの追加を禁止していますが、nullを追加することもできます。
Java <? super T>
は一般的な反転を実装します
List<? super Number> list = new ArrayList<>();
? super Number
ワイルドカードは、タイプの下限が数値であることを示します。つまり、親タイプFはhere ? super Number
であり、子タイプCはNumberです。つまり、F <= Cの場合、f(C)<= f(F)があり、これが逆になります。コード例:
List<? super Number> list3 = new ArrayList<Number>();
List<? super Number> list4 = new ArrayList<Object>();
list3.add(new Integer(3));
list4.add(new Integer(4));
つまり、List<? super Number >
Numberの親オブジェクトを追加することはできません。ただし、NumberおよびそのサブクラスオブジェクトをList <?Super Number>に追加できます。
PECS:いつ使用するのですか?いつスーパーを使うのですか?
Joshua Blochは、プロデューサーとしてのみ読み取ることができるオブジェクトと、コンシューマーとしてのみ書き込むことができるオブジェクトを指します。「柔軟性を最大にするために、プロデューサーまたはコンシューマーを表す入力パラメーターにワイルドカードタイプを使用する」と提案し、次のニーモニックを提案しました。
PECSは、Producer-Extens、Consumer-Super(Producer-Extends、Consumer-Super)の略です。
注:たとえばList<? extends Foo>
、プロデューサーオブジェクトを使用する場合 、オブジェクトの呼び出しadd()
や 呼び出しは許可されません set()
。ただし、これはオブジェクトが不変であることを意味するわけではありません。たとえば、 パラメーターがまったく必要ないclear()
ため、リストからすべての項目を削除するための呼び出しを 妨げるものはありません clear()
。ワイルドカード(または他のタイプのタイプ変更)によって保証される唯一のものは、タイプセーフです。不変性はまったく別の問題です。
タイプ変更を宣言する
汎用インターフェースSource<T>
があり、インターフェースT
にパラメーターとしてメソッドがないと仮定 します が、メソッドはT
タイプ値を返し ます。
// Java
interface Source<T> {
T nextT();
}
その後、 インスタンスの参照Source <Object>
を型の変数に格納Source <String>
すること は非常に安全です 。consumer-methodは呼び出せません。しかし、Javaはこれを認識しておらず、そのような操作を禁止しています。
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!!在 Java 中不允许
// ……
}
これを修正するには、オブジェクトのタイプをとして宣言する必要があります Source<? extends Object>
。これは無意味です。以前と同じようにオブジェクトに対してすべて同じメソッドを呼び出すことができるため、より複雑なタイプは値をもたらさないからです。しかし、コンパイラは知りません。
Kotlinでは、これをコンパイラーに説明する方法があります。これは宣言型の変更と呼ばれます。型パラメーター をマークして Source
、 メンバーからのみ返され(生成され)、消費されないようにすることができ ます。このために、out 修飾子を提供します 。 T
Source<T>
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
一般的な原則は次のとおりです。クラスの C
型パラメーターが outT
として宣言され ている 場合C
、メンバーのoutput -position にしか出現できません が、戻り値はC<Base>
安全に使用できる C<Derived>
スーパークラスです 。
要するに、彼らはそのクラスが言う C
パラメータである T
上の共変、または 共変の型パラメータ。あなたは考えることができ ている プロデューサー、ない 消費者。T
C
T
T
out修飾子は型変更注釈と呼ばれ、型パラメーター宣言で提供されるため、型変更宣言について説明します。これは、型のワイルドカードを使用した型の変更をJavaで使用する場合とは逆です。
別の加算 OUT、Kotlinは、変数の型注釈を追加します。で。型パラメーターの反転を行います:消費のみ可能で、生成はできません。インバータタイプの良い例はComparable
次のとおりです 。
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
// 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量
val y: Comparable<Double> = x // OK!
}
in とoutの 単語は自明であると 信じてい ます(C#で長い間正常に使用されているため)。そのため、上記のニーモニックは実際には必要なく、より高い目標に書き換えることができます。
実在 する変革:消費者はイン、生産者はアウト! :-)
次のように、クラスを定義するときに処理します。
// 使用out关键字
class ReadableList<out T>{
}
val dogReadable: ReadableList<Dog> = ReadableList()
val animalReadable: ReadableList<Animal> = dogReadable
上記のコード唯一の違いは、今、私たちはReadableListは少し一般的な制限を追加することである前に定義されたということである OUT、その後、あなたはdogReadable animalReadableオブジェクトへの成功代入することができます。Kotlin APIなぜ推測することができます前に、私たちはこれを見るべきですリスト<generic>は共分散をサポートしています。
⚠️注:ただし、outキーワードで変更した後は、ReadableList内のパラメータータイプとしてTを使用するメソッドはありません。
参照:
- https://github.com/EasyKotlin/chapter6_generics
- https://huanglizhuo.gitbooks.io/kotlin-in-chinese/content/ClassesAndObjects/Generics.html
- JavaおよびKotlinのジェネリック型と分散の深い理解
- Kotlin中国のWebサイト:https : //www.kotlincn.net/docs/reference/generics.html