汎用-消去実装Java汎用
6527 ワード
Javaの汎用は言語の内在的なメカニズムではなく、後に追加された特性であり、非汎用コードと汎用コードの互換性という問題をもたらす.汎用型はJDK 1である.5 Javaに追加されたばかりで、以前のコードはすべて非汎用で、JDK 1でどのように実行されていましたか.5以降のVM上?このような互換性を実現するために、Java汎用型は狭い場所に限られ、同時に理解しにくくなり、Java言語の中で最も理解しにくい文法とも言える.
消去
非汎用コードとの互換性を実現するために、Java言語の汎用は消去(Erasure)で実現される.すなわち、汎用は基本的にコンパイラによって実現され、コンパイラによってタイプチェックとタイプ推定が実行され、バイトコードを生成する前に消去され、仮想マシンは汎用の存在を知らない.これにより,汎用と非汎用のコードが混在して実行できるようになり,もちろん,かなり混乱しているように見える.
汎用型を使用すると、対応するタイプがオリジナルタイプ(raw type)と呼ばれ、汎用型はオリジナルタイプに消去されます.例えば、GenericはGenericに、ListはListに、消去のため、仮想マシンではどのタイプの情報も得られず、仮想マシンはオリジナルタイプしか知りません.次のコードは、Java汎用の真実-消去を示します.
Javapを使用してclassファイルを逆コンパイルし、次のコードを得ます.
逆コンパイルされたバイトコードから,汎用ErasureはErasureに消去され,その内部のフィールドTはObjectに消去され,getとsetメソッドではtがObjectとして用いられていることがわかる.最も注目すべきは,逆コンパイルコードの最後から3行目であり,Javaコードに対応するのがString value=erasである.get();コンパイラはタイプ変換を実行します.これがJava汎用の本質です.
転送された値を追加のコンパイル期間チェックし、転送された値の変換を挿入します.
.このような汎用型は本当に汎用型ですか?
Javaの汎用型は確かに本当の汎用型ではないと言っても、Javaのタイプを安全に一歩前進させ、プログラマーが明示的に制御するタイプ変換が必要だったが、コンパイラによって実現され、汎用型の規範に従ってコードを書くだけで、安全な保障を受けることができる.ここでは、Javaの汎用性を理解する問題を考えなければなりません.では、その核心的な目的は何ですか.個人的には、Java汎用の核心的な目的は安全性にあり、特に汎用ワイルドカードを理解する際、すべての奇妙なルールは、結局安全な目的にあると思います.
タイプ情報の消失
消去のため、汎用コードの内部では、汎用パラメータタイプに関する情報は取得できません.実行時に仮想マシンは正確なタイプの情報を取得できず、instanceof操作やnew式などの正確なタイプの情報の作業はすべて完了しません.
汎用クラスの配列
配列はJava言語の組み込み特性であり,汎用性と配列を組み合わせると理解しにくい問題がある.まずJavaの配列はコヒーレントであり、IntegerはNumberのサブクラスであるため、Integer[]もNumber[]のサブクラスであり、Number[]を使用する場合、Integer[]を使用して代用することができるが、汎用型はコヒーレントではない.例えば、ListがList
消去
非汎用コードとの互換性を実現するために、Java言語の汎用は消去(Erasure)で実現される.すなわち、汎用は基本的にコンパイラによって実現され、コンパイラによってタイプチェックとタイプ推定が実行され、バイトコードを生成する前に消去され、仮想マシンは汎用の存在を知らない.これにより,汎用と非汎用のコードが混在して実行できるようになり,もちろん,かなり混乱しているように見える.
汎用型を使用すると、対応するタイプがオリジナルタイプ(raw type)と呼ばれ、汎用型はオリジナルタイプに消去されます.例えば、Generic
class Erasure<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
Erasure<String> eras = new Erasure<String>();
eras.set("not real class type");
String value = eras.get();
}
}
Javapを使用してclassファイルを逆コンパイルし、次のコードを得ます.
class com.think.generics.Erasure<T> {
com.think.generics.Erasure();
Code:
0: aload_0
1: invokespecial #12 // Method java/lang/Object."<init>":()V
4: return
public void set(T);
Code:
0: aload_0
1: aload_1
2: putfield #23 // Field t:Ljava/lang/Object;
5: return
public T get();
Code:
0: aload_0
1: getfield #23 // Field t:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #1 // class com/think/generics/Erasure
3: dup
4: invokespecial #30 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #31 // String not real class type
11: invokevirtual #33 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #35 // Method get:()Ljava/lang/Object;
18: checkcast #37 // class java/lang/String
21: astore_2
22: return
}
逆コンパイルされたバイトコードから,汎用Erasure
転送された値を追加のコンパイル期間チェックし、転送された値の変換を挿入します.
.このような汎用型は本当に汎用型ですか?
Javaの汎用型は確かに本当の汎用型ではないと言っても、Javaのタイプを安全に一歩前進させ、プログラマーが明示的に制御するタイプ変換が必要だったが、コンパイラによって実現され、汎用型の規範に従ってコードを書くだけで、安全な保障を受けることができる.ここでは、Javaの汎用性を理解する問題を考えなければなりません.では、その核心的な目的は何ですか.個人的には、Java汎用の核心的な目的は安全性にあり、特に汎用ワイルドカードを理解する際、すべての奇妙なルールは、結局安全な目的にあると思います.
タイプ情報の消失
消去のため、汎用コードの内部では、汎用パラメータタイプに関する情報は取得できません.実行時に仮想マシンは正確なタイプの情報を取得できず、instanceof操作やnew式などの正確なタイプの情報の作業はすべて完了しません.
class Erasure<T> {
public void f() {
if(arg instanceof T) //Error
T ins = new T();//Error
T[] array = new T[10];//error
}
}
では、特定のタイプ情報が必要な場合、クラスオブジェクトを覚えて実装します.実行時にタイプ情報が必要な場合は、クラスオブジェクトを使用して操作します.たとえば、次のようにします.class Erasure<T> {
private Class<T> clazz;
Erasure(Class<T> kind) {
clazz = kind;
}
public void f() {
if(clazz.isInstance(arg)) {}
T t = clazz.newInstance();//
}
}
汎用クラスの配列
配列はJava言語の組み込み特性であり,汎用性と配列を組み合わせると理解しにくい問題がある.まずJavaの配列はコヒーレントであり、IntegerはNumberのサブクラスであるため、Integer[]もNumber[]のサブクラスであり、Number[]を使用する場合、Integer[]を使用して代用することができるが、汎用型はコヒーレントではない.例えば、List