blog

Kotlinのジェネリック、再定義されたジェネリックの実現

上記のコードでは問題ないのですが、最後のコレクションにint型を挿入しているため、強制的に型変換を行うと型変換例外が発生します。 ジェネリック型のないコレクションに2つ以上の型を挿入するのは簡単で、型...

Feb 13, 2020 · 8 min. read
シェア

Kotlinのジェネリック、再定義されたジェネリックの実現

Java

では、Javaの一般的な使い方を説明するために、最も簡単なことから始めなければなりません。

JAVA 1.5でジェネリックが追加された後、1.5以前ではジェネリック・ジェネリックは使用されていませんでした。

public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        arrayList.add("1");
        arrayList.add("2");
        arrayList.add("3");
        arrayList.add(4);
        for (Object s : arrayList) {
            System.out.println((String) s);
    }
}
// 
1
2
3
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
 at TestDemo.main(TestDemo.java:16)

まず、Object型で任意の型に挿入できるコレクションを作成します。

上記のコードでは問題ないのですが、最終的なコレクションにint型が挿入さ れ、強い変換が実行されたときに型変換例外が 送信されます。 一般的な型付けがされていないコレクションを使用すると、2つ以上の型をコレクションに挿入することは簡単で、型変換が実行されたときに型が一致しないと例外がスローされます。これはコレクションを型安全でなくします。

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList();
        arrayList.add("1");
        arrayList.add("2");
        arrayList.add("3");
        arrayList.add(4);//这里会发送错误  因为没有 .add(int o); 
        for (String s : arrayList) {
            System.out.println(s);   
     }
}

ご覧のように、ジェネリック型を追加することで、コンパイル 時にエラーを検出することができ、実行時の例外によるクラッシュを防ぐために、時間内に変更を加えることができます。

一般化は何を解くのですか?
  1. 同一に実行される複数のタイプのコードの再利用が向上
  2. 型の安全性 ジェネリックスの使用によりコードの安定性が向上 型変換の強制やクラッシュの例外を見つける心配が不要
汎用クラス
class Box<T> {
        private T t;
        
        public T getBox() {
            return t;
        }
        public void setT(T t) {
            this.t = t;
        }
    }
汎用インタフェース
//MutableListはジェネリック・インターフェースを定義しているからだ。
interface Base<T>{
        T get();
}
//MutableListはジェネリック・インターフェイス1を実装している。
class View<T> implements TestDemo.Base<T> {
    @Override
    public T get() {
        return null;
    }
}
//MutableListはジェネリック・インターフェイス2を実装している。
class View implements TestDemo.Base<String> {
    @Override
    public String get() {
        return "";
    }
}
ジェネリックメソッド
public <T> T getEntity(T t) {
        return t;
}

上記はすべて基本的な操作であり、汎用的な共分散型や反転型の使用は後のKotlinに委ねられます。 また、Kotlinを使用することで、よりわかりやすく話すこともできます。

ミュータリスト

Javaのジェネリックについて最後に一つ。 ジェネリック型の消去。

public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        List<Integer> integers = new ArrayList<>();
        System.out.println(strings.getClass().equals(integers.getClass()));
}

一般的な型消去を知らなければ、出力は偽、結果は偽となるでしょう。

コンパイル時に消去されたジェネリックがオブジェクトになるわけですから、結果がtureになるのもわかります。これは

List<Object> objects = new ArrayList<>();
List<Object> objects2 = new ArrayList<>();
objects.equals(objects2)

これは配列で確認できます。

public static void main(String[] args) {
  String[] strings1 = new String[0];
        int[] ints = new int[0];
        System.out.println(ints.getClass().equals(strings1.getClass()));
}

配列はタイプ消去されていません。

では、なぜjavaは型消去を行うのでしょうか? 先に述べたように、java 1.5には総称型がなく、総称型が導入されたのは1.5以降です。ではなぜjavaは1.5以前のバージョンと互換性があるのかというと、型消去が使われているからです。しかし、型消去はまた多くの問題をもたらします。すべてのJVMベースの言語は、型消去によってジェネリックを実装しています。Kotlinはジェネリック消去の問題をいくつかのトリックで解決しています。

Kotlin つ目のコードは、MutableList

Javaのジェネリックスは以上です。 次にKotlinを見てみましょう。

KotlinはJavaと100%互換性があると主張し、JavaのKotlinの性質は、言語もJVM型消去に基づいているため、すべてでなければなりませんKotlinも例外ではありません。

fun <T> printNum(t: T) {
    println(t)
}
fun main() {
    printNum("11")
    printNum(1)
    printNum(1.234)ko
}

ジェネリックスはJavaを使うのとほとんど変わらないことがお分かりいただけたと思います。

ミュータリスト

inline

JVMはコンパイラで型消去を行わないのですか? 次のステップは、Kotlinで インラインの 助けを借りてジェネリックの実体化を実装することです。この章は主にジェネリックについてなので、 インラインについては あまり詳しく説明しません。

公式説明

各関数はオブジェクトであり、クロージャをキャプチャします。

パッケージ。 つまり、関数本体内でアクセスされる変数のことです。 メモリ割り当てと仮想呼び出しは

ランタイムオーバーヘッド。

"

Kotlinの関数は、インライン修飾子を使ってインラインで宣言できます。この関数はコンパイラによってソースコードに直接インライン化されます。

fun main() {
    hello()
    println("world")
}
private inline fun hello() {
    println("Hello")
}

上記は、バイトコードをJavaコードに逆コンパイルして生成された単純なインライン関数です。

これはインライン関数によって生成されたコードです。

public static void main() {
      String var1 = "Hello";
      System.out.println(var1);
      String var3 = "world";
      System.out.println(var3);
}

これは通常の関数によって生成されるコードです。

public static void main() {
      hello();
      String var0 = "world";
      System.out.println(var0);
}
private static final void hello() {
      String var0 = "Hello";
      System.out.println(var0);
}
reified

インライン関数を使用する場合、宣言されたジェネリックの前にあるreified修飾子はジェネリックの実現で見つけることができます。

fun main() {
    isString(1)
    isString("")
}
private inline fun <reified T> isString(t: T) {
    println("" is T)
}
false
true

is、T:class:javaのような構文を使うことで、ジェネリックな実現が可能です。 では、このジェネリックな実現の利点は何でしょうか?

  • ジェネリックの種類を確認できます。
  • MutableListの方が構文がすっきりしているからです。

例えば、Androidの開発中、別のアクティビティを開始する場合、通常の構文は次のようになります。

val intent = Intent(this, MainActivity::class.java)
startActivity(intent)

今すぐリフォームを。

startActivity<MainActivity>(this)
inline fun <reified T> startActivity(context: Context) {
        val intent = Intent(context, T::class.java)
        context.startActivity(intent)
}

コードがよりエレガントになりませんか? そうです、Gson Retrofitはあなたのコードをよりエレガントにするためにできることなのです。

ミュータリスト

ジェネリックスの連結と反転 一般的にはあまり使われませんが、Kotlinでは連結と反転の機能がたくさん使われています。 その一般的な例がリストで、当然連結をサポートしているので、連結を見てみましょう。

引数を受け取る場所が inの 位置 値を返す場所が outの 位置 この2つを覚えておく必要があります 共分散と反転の理解は、この図にかかっています。

open class A
class B : A()
fun main() {
 val b = B()
    get(b)
}
fun get(a: A) {
//    ...
}

ジェネリックを追加してみてください。

open class A
class B : A()
fun main() {
    val b = B()
 val mutableListOf = mutableListOf(b)
    get(mutableListOf)//ここにエラーがある。
}
fun get(a: MutableList<A>) {
//    ...
}

get関数の呼び出しでエラーが見つかりました。最初のコードでは、get関数はBのインスタンスを渡しています。 BはAのサブクラスなので、これはOKです。

Read next

一日一リーツ(文字列の足し算)難易度:易Day20200803

2つの非負整数 num1 と num2 が文字列形式で与えられているとき、それらの和を計算しなさい。 num1 と num2 はどちらも 5100 より小さい。 num1 と num2 はどちらも 0-9 の数字だけを含む。 num1 と num2 はどちらも先頭にゼロを含まない。 組み込みライブラリは使用できず、出力を直接変換することもできません。

Feb 12, 2020 · 2 min read