1. Java ジェネリックの概要
ジェネリックは Java 1.5 の新機能です。ジェネリックの本質はパラメータ化された型であり、操作されるデータ型がパラメータとして指定されることを意味します。このパラメータ タイプは、それぞれジェネリック クラス、ジェネリック インターフェイス、ジェネリック メソッドと呼ばれるクラス、インターフェイス、およびメソッドの作成に使用できます。
導入される Java ジェネリックの利点は、安全性とシンプルさです。
Java SE 1.5 より前では、ジェネリックスが存在しないため、「任意」パラメーターは型オブジェクトへの参照を通じて実現されていました。「任意」の欠点は、明示的な強制型変換が必要であり、この変換について開発者が知っておく必要があることでした。実際のパラメータの型は事前に設定してください。強制的な型変換エラーの場合、コンパイラはエラーを表示しない可能性があり、実行時に例外が発生します。これはセキュリティ上のリスクです。
ジェネリックの利点は、コンパイル中に型安全性がチェックされ、すべてのキャストが自動かつ暗黙的であるため、コードの再利用が向上することです。
ジェネリックスの使用にはいくつかの規則と制限があります:
1. ジェネリックスの型パラメーターは、単純型ではなく、クラス型 (カスタム クラスを含む) のみにすることができます。
2. 同じジェネリック型は複数のバージョンに対応できます (パラメーターの型が不確かであるため)。ジェネリック クラス インスタンスの異なるバージョンには互換性がありません。
3. ジェネリックには複数の型パラメーターを含めることができます。
4. 汎用パラメータ型では、たとえば extends ステートメントを使用できます。通常は「制限付きタイプ」になります。
5. 汎用パラメーターの型はワイルドカード型にすることもできます。
Class<?> classType = Class.forName(java.lang.String);
ジェネリックにはインターフェイスやメソッドなども含まれており、内容が多く、それを理解して上手に適用するには多大な労力がかかります。
2. Java ジェネリックスの実装原則: 型消去
Java のジェネリックスは擬似ジェネリックスです。コンパイル中に、すべての一般情報は消去されます。ジェネリックスの概念を正しく理解するための最初の前提条件は、型消去を理解することです。
Java のジェネリックは基本的にコンパイラ レベルで実装されます。ジェネリックスの型情報は、生成される Java バイトコードには含まれません。ジェネリックスの使用時に追加された型パラメーターは、コンパイル中にコンパイラーによって削除されます。このプロセスは型の消去と呼ばれます。
コード内で定義された List273238ce9338fbb04bee6997e5552b95 や Listf7e83be87db5cd2d9a8a0b8117b38cd4 などの型は、コンパイル後に List にプログラムされます。 JVM が認識できるのはリストだけであり、ジェネリックによって付加された型情報は JVM には見えません。 Java コンパイラは、コンパイル時に考えられるエラーを見つけるために最善を尽くしますが、実行時に型変換例外を回避することはできません。型消去は、Java の汎用実装方法と C++ テンプレート メカニズムの実装方法 (後述) の重要な違いでもあります。
3. 型消去後に保持される元の型
元の型 (生の型) は、汎用情報が消去された後のバイトコード内の型変数の実型です。ジェネリック型が定義されると、対応するプリミティブ型が自動的に提供されます。型変数は消去 (消去) され、修飾された型に置き換えられます (修飾されていない変数は Object です)。
class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
Pair8742468051c85b06f0a0af9e3e506b5c の元の型は次のとおりです:
class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
これは、Pair8742468051c85b06f0a0af9e3e506b5c では T が未定義の型変数であるため、Object に置き換えられます。結果は、Java が言語に追加される前にジェネリックスが実装されていたのと同じように、通常のクラスになります。プログラムには、Pairf7e83be87db5cd2d9a8a0b8117b38cd4やPairc0f559cc8d56b43654fcbe4aa9df7b4aなど、さまざまなタイプのPairを含めることができます。ただし、タイプを消去すると、それらは元のPairタイプになり、元のタイプはすべてObjectになります。
型変数が制限されている場合、元の型は最初の制限された型変数で置き換えられます。
たとえば、Pair が次のように宣言した場合:
public class Pair<T extends Comparable& Serializable> {
その場合、元の型は Comparable
になります
注意:
このように、Pair が public classampir072f492c1c614d437f856ffc9d1253f7 を宣言すると、元の型は次のように置き換えられます。シリアル化可能であり、Comparable にキャストを挿入するときにコンパイラが必要に応じて置き換えます。効率を高めるために、タグ付きインターフェイス (つまり、メソッドのないインターフェイス) を境界修飾リストの最後に配置する必要があります。
プリミティブ型とジェネリック変数型を区別するには
ジェネリック メソッドを呼び出すとき、ジェネリック型を指定するかどうかを指定できます。
ジェネリックを指定しない場合、ジェネリック変数の型は、メソッド内の複数の型の同じ親クラスの最下位レベル (Object まで) になります。
ジェネリックを指定する場合、メソッド内のいくつかの型はジェネリック インスタンス タイプまたはそのサブクラスである必要があります。
public class Test{ public static void main(String[] args) { /**不指定泛型的时候*/ int i=Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型 Number f=Test.add(1, 1.2);//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number Object o=Test.add(1, "asd");//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Object /**指定泛型的时候*/ int a=Test.<Integer>add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类 int b=Test.<Integer>add(1, 2.2);//编译错误,指定了Integer,不能为Float Number c=Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float } //这是一个简单的泛型方法 public static <T> T add(T x,T y){ return y; } }
実はジェネリッククラスでもジェネリックスを指定しない場合はほぼ同じですが、この時のジェネリック型がObjectである点以外はArrayListと同様、ジェネリックスを指定しなければどの型でも構いません。この ArrayList オブジェクトに配置されます。
4. C++ テンプレートの実装
C++ はわかりませんが、インターネットで C++ の実装も探しました。
C++ でテンプレートのインスタンス化ごとに異なる型が生成される現象は、「テンプレート コードの肥大化」と呼ばれます。
例えば、vector
Java ジェネリックスの実装原則に関連するその他の記事については、PHP 中国語 Web サイトに注目してください。