KotlinのGenericsに関するこの記事-JavaのGenericsとの使用、類似点、および相違点の機能。
ジェネリックについての簡単な説明
要するに、ジェネリックとは、クラス、インターフェイス、またはメソッドが特定のタイプではなく、単に何らかの種類で機能することを示す方法です。 どちらがコンテキストから決定されます。 例:
どのクラスオブジェクトがリストに含まれるかは事前にはわかりませんが、これを使用するときにこれが決定されます。
これは単なるリストではなく、文字列のリストです。 ジェネリックは、型の安全性を確保するのに役立ちます。リストには任意のオブジェクトを配置できますが、リストには<String>にのみStringまたはその子孫のいずれかを配置できます。
public interface List<E> extends Collection<E> { //... }
どのクラスオブジェクトがリストに含まれるかは事前にはわかりませんが、これを使用するときにこれが決定されます。
List<String> list = new ArrayList<>();
これは単なるリストではなく、文字列のリストです。 ジェネリックは、型の安全性を確保するのに役立ちます。リストには任意のオブジェクトを配置できますが、リストには<String>にのみStringまたはその子孫のいずれかを配置できます。
ジェネリックに関する話は、実際のジェネリックとワイルドカードの使用という2つの部分に分けます。 Wildcardsになるまで、KotlinでGenericsを使用することはJavaと大差ありません。
同じジェネリッククラス:
// Java public class SomeGenericClass <T> { private T mSomeField; public void setSomeField(T someData) { mSomeField = someData; } public T getSomeField() { return mSomeField; } }
// Kotlin class SomeGenericClass <T> { private var mSomeField: T? = null fun setSomeField(someData: T?) { mSomeField = someData } fun getSomeField(): T? { return mSomeField } }
同じ一般的な方法:
// Java public <K> K makeSomething(K someData) { K localData = someData; //... return localData; }
// Kotlin fun <K> makeSomething(someData : K) : K { var localData = someData //... return localData }
Javaではジェネリックがさらに制限される場合があります。
// Java public <K extends Number> K makeSomething(K someData) { K localData = someData; //... return localData; }
そして、コトリンでは:
// Kotlin fun <K : Number> makeSomething(someData : K) : K { var localData = someData //... return localData }
このような制限は、Kの代わりにどのクラスも使用できず、条件(この場合はNumberまたはそれを継承するクラス)のみを満たすことができることを示します。
// makeSomething(1) // makeSomething(“string”)
たとえば、メソッドに渡されるオブジェクトがクラスを継承し、インターフェイスを実装する必要があることを示すなど、制限は複雑になる場合があります。
//Java public static <T extends Interaction & Fragment> SomeFragment newInstance(T interactor) { SomeFragment fragment = new SomeFragment(); fragment.setTargetFragment(interactor, 0); return fragment; }
//Kotlin fun <T> newInstance(interactor : T) : SomeFragment where T : Interaction, T : Fragment { val fragment = SomeFragment() fragment.setTargetFragment(interactor, 0) return fragment }
Kotlinは複雑な制約に異なる構文を使用していることに注意してください。いくつかの構文糖が追加されています。 コンテキストによって決定できる場合は、typeパラメーターを省略できます。
// Kotlin val someGenericClassInstance = SomeGenericClass("This is String")
また、Javaでは次のことが必要です。
// Java SomeGenericClass<String> someGenericClassInstance = new SomeGenericClass<>("This is String");
したがって、JavaからKotlinに切り替えるときにジェネリックについて知っておく必要がある主なことは、Javaで行ったのと同じ方法ですべてを行うことです。 「コトリンの方法で」新しい方法で何かをしようとする試みは、新しい困難にのみつながる可能性があります。
ワイルドカード
2番目の部分に進みましょう。 ワイルドカードは、KotlinとJavaの両方で最も困難を引き起こす特殊なケースです。 Genericsの主な問題は、その不変性です。リスト<String>はリスト<Object>の子孫ではありません。 それ以外の場合、次のようなエラー:
//Java List<String> strs = new ArrayList<String>(); List<Object> objs = strs; //objs - List<Object>, Integer objs.add(1); // strs - List<String>, get() String String s = strs.get(0);
Genericsの不変性により、これを防ぐことができますが、一方で、追加の制限が導入されます。 そのため、通常のジェネリックを使用する場合、パラメーターとしてリスト<Object>を必要とするメソッドにリスト<String>を渡すことはできません。 多くの場合、このような機会があると便利です。
ワイルドカードを使用すると、この時点で特定のタイプのパラメーターではなく何らかのタイプのパラメーターが必要であることを示すことで、この動作を許可できます。 同時に、ワイルドカードを特別に制限することもできます。これにより、質問は3つの部分に分割されます。
- 無制限のワイルドカード
- 共変ワイルドカード
- 反変ワイルドカード
限定的な「下から」共変ワイルドカードは、あるクラスまたはその子孫からジェネリッククラスが期待される場合に使用されます。 例:
// Java public interface Container<T> { T getData(); void putData(T data); } static void hideView(Container<? extends View> viewContainer) { viewContainer.getData().setVisibility(View.GONE); }
ここで、hideViewメソッドは、Containerインターフェースを実装するオブジェクトを期待しますが、Viewを含むだけでなく、ViewまたはViewを継承する他のクラスを含むオブジェクトを必要としません。 これは共分散と呼ばれます。
Kotlinでは、この動作は同様の方法で実装できます。
// Kotlin interface Container<T> { fun getData() : T fun putData(data : T) } fun hideView (viewContainer : Container<out View>) { viewContainer.getData().visibility = View.GONE; }
同時に、ワイルドカードとして宣言されたパラメーターの使用には追加の制限が課せられます。
Javaでは、共変ワイルドカードを使用して制限なしでデータを受信できますが、データは指定された境界線に従って返されます(上記の例では、コンテナに実際にTextViewが含まれていてもgetData()はViewを返します)。 ただし、null以外は何も入力できません。そうしないと、ジェネリックが不変でなければ問題が発生します。
//Java static void hideView(Container<? extends View> viewContainer) { //getData() View , , TextView viewContainer.getData().setVisibility(View.GONE); // null, , // viewContainer.putData(null); // - , , , Container<ImageView> viewContainer.putData(new View(App.getContext())); }
Kotlinでは、制限はほとんど同じです。 Kotlinの型の性質により、そのようなパラメーターにnullを入れることさえできません。 outキーワードは、何が起こっているかを完全に説明します。
//Kotlin fun hideView (viewContainer : Container<out View>) { viewContainer.getData().visibility = View.GONE; // , , View ( - ) View? viewContainer.putData(null) }
限定された「上位」の反変ワイルドカードは、クラスまたはその祖先からジェネリッククラスが予想される場所を示すために使用されます。 反変ワイルドカードの伝統的な例はコンパレータです:
// Java public static <T> void sort(List<T> list, Comparator<? super T> comparator) { //... }
List <String>が最初のパラメーターとしてメソッドに渡され、String祖先のいずれかのコンパレーターが2番目のパラメーターとして渡されるとします(例:CharSequence:Comparator <CharSequence>)。 StringはCharSequenceの子孫であるため、コンパレータに必要なフィールドとメソッドもStringクラスのオブジェクトに含まれます。
//Java class LengthComparator implements Comparator<CharSequence> { @Override public int compare(CharSequence obj1, CharSequence obj2) { // String if (obj1.length() == obj2.length()) return 0; if (obj1.length() < obj2.length()) return -1; return 1; } }
Kotlinでは、実装は同様です:
// Kotlin fun <T> sort(list : List<T>, comparator: Comparator<in T>) { //… }
反変ワイルドカードにはかなりの制限があります。このようなワイルドカードから値を読み取ることは可能ですが、オブジェクトはJavaおよびAnyで返されますか? コトリンで。
この時点で繰り返しますが、JavaからKotlinに移行する場合は、すべてを同じ方法で行う必要があります。 Wildcardsの公式ドキュメントには「Kotlinには何もありません」(「Kotlinにはありません」)と書かれていますが、提案された型射影メカニズム(前述)はすべての通常のケースで同様に機能し、新しいアプローチは必要ありません。
しかし、革新なしではありません。 Javaの通常のワイルドカードモデルに完全に類似した型射影に加えて、Kotlinは宣言側の分散という別のメカニズムを提供します。
ジェネリッククラスが共変としてのみ(または反変としてのみ)使用されることが事前にわかっている場合、これはジェネリッククラスの使用時にではなく、ジェネリッククラスの作成時に示すことができます。 例として、コンパレータが再び適しています。 Kotlinで書き直されたjava.util.Comparatorは次のようになります。
// Kotlin interface Comparator<in T> { fun compare(lhs: T, rhs: T): Int override fun equals(other : Any?): Boolean }
そして、その使用は次のようになります。
// Kotlin fun <T> sort(list : List<T>, comparator: Comparator<T>) { //… }
同時に、コンパレータパラメータの使用に関する制限は、<in T>がインターフェイス宣言の側ではなく、その使用側で示された場合と同じになります。
同様に、クラスを宣言するときに、共変の動作を定義できます。
最後の非公開のケースは、制限のないワイルドカードです。 明らかに、ジェネリックがどのクラスからも適している場合に使用されます:
// Java public interface Container<T> { T getData(); void putData(T data); } static boolean isNull(Container<?> container) { return container.getData() != null; }
Kotlinでは、同様のメカニズムがスタープロジェクションと呼ばれます。 どんな些細な場合でも、Javaの無制限のワイルドカードとの唯一の違いは、「?」の代わりに記号「*」を使用することです。
// Kotlin interface Container<T> { fun getData() : T fun putData(data : T); } fun isNull(container : Container<*>) : Boolean { return container.getData() != null; }
Javaでは、次のルールに従って無制限のワイルドカードが使用されます:ヌルのみを入れることができ、オブジェクトは常に読み取られます。 Kotlin内には何も配置できませんが、クラスAnyのオブジェクトは読み取られますか?..
宣言側の分散とスター射影を一緒に使用する場合、制限は累積的であることに注意してください。 したがって、反変の宣言側の分散を使用すると(内部に何かを入れることができますが、Anyだけをカウントできますか?)スター投影と一緒に、何かを入れることはできません(スター投影制限)が、同じAnyが返されますか? (これで、それらの制限は一致します)。
一般的なジェネリック医薬品については、次のリンクで読むことができます。
www.oracle.com/technetwork/articles/java/juneau-generics-2255374.html
www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html