(1)人が諦めない限り、全世界があなたを諦めない
(2)私は大いに役立つために生まれる(3)
学ぶことの苦しみに耐えられないのなら、人生の苦しみに耐えなければならない。深い理解。
(4)難しいことをすることで得なければなりません。(
5)精神は本当の刃です。
(6)相手を2回、心の中で初めて征服します。
(7)書くのは本当に簡単ではありません。好きな場合や何か持っている場合好き+フォローまたはお気に入りを忘れないでください〜
Javaジェネリックの原理と消去メカニズム
1. Javaでジェネリックを処理する方法は?
1.1一般的な消去
1.1.1一般的な消去にブリッジ方式を使用する必要があるのはなぜですか?
(1)ブリッジメソッドは、ジェネリック消去後もサブクラスがインターフェイスメソッドの実装を保証できるようにするために使用され、ブリッジメソッドはジェネリック消去後にサブクラスメソッドを呼び出します。
(2)サンプルコードは次のとおりです。
(3)ブリッジ方式の表示は、バイトコード表示プラグインASM ByteCodeViewerを介して表示できます。具体的な使用方法は他のブログで紹介します。
1.2一般的な消去残留物
(1)バイトコードの例
(2)上の写真は、一般的な消去後のバイトコードのスクリーンショットです。バイトコードファイルに一般的な残差Tがある理由です。
(A)理由は、ここに表示されているのは、メソッドの署名と汎用インターフェイスで定義された形式のみであるためです。このように、バイトコードの分析に適しています。
(B)この情報はクラスの定数プールに保存されます
1.3ジェネリックとリフレクション
1.3.1ジェネリックは消去されませんか?なぜそれはまだ反射に関連しているのですか?
(1)理由は消去です。実際、一般的な情報はクラスの定数プールに保持されます。
(2)一般的な情報を取り出した後の用途は何ですか?
(A)リフレクションを通じて特定のタイプを取得し、Retrofitのソースコードを確認します。
1.4 Javaジェネリックの原理は何ですか?一般的な消去メカニズムとは何ですか?
(1)JavaジェネリックはJDK5の新機能です。下位互換性を保つために、仮想マシンはジェネリックをサポートしていないため、Javaは疑似ジェネリックメカニズムを実装しています。つまり、Javaはコンパイル中に消去されます。すべての汎用情報。Javaがバイトコードに新しい型を生成する必要がないため、すべての汎用型は最終的にはプリミティブ型であり、Javaの実行時には汎用情報はありません。
1.5 Javaコンパイラはジェネリックをどのように制御しますか?
(1)ジェネリックタイプを確認し、ターゲットタイプを取得します。
(2)型変数を消去し、限定型に置き換えます。
(a)如果泛型类型的类型变量没有限定(<T>),则用Object作为原始类型。
(b)如果有限定(<T extends XClass>),则用XClass作为原始类型
(c)如果有多个限定(T extends XClass1&XClass2),则使用第一个边界XClass1作为原始类
(3)型の安全性を維持するために、必要に応じて型変換を挿入します。
(4)拡張中に多形性を維持するためのブリッジメソッドを生成します。
2.ジェネリックとジェネリック消去の使用による影響
2.1汎用タイプ変数は基本データタイプを使用できません
(1)比如没有ArrayList<int>,只有ArrayList<Integer>.当类型擦除后,ArrayList的原始类中类的类型变量(T)替换成Object,但Object类型不能存放int值。
2.2instanceof演算子は使用できません
(1)不能使用instanceof运算符判断一个泛型类型是否与另一个有具体类型的泛型类型相同
(2)因为泛型擦除之后,ArrayList<String>只剩下原始类型,泛型信息String不存在了,所以没法使用instanceof。
(3)示例代码如下:
public static void test2(){
ArrayList<String> strings = new ArrayList<>();
//s1.ArrayList<?> 可以使用instanceof,因为ArrayList<?>就是代表的是一种未知的类型,所以可以使用。
if(strings instanceof ArrayList<?>){
}
//s2.因为ArrayList<String>泛型擦除之后,String类型为丢失,所以不能使用instanceof运行符
if(strings instanceof ArrayList<String>){
}
}
2.3静的メソッドと静的クラスのジェネリックの問題
(1)因为泛型类中的泛型参数的实例化是在定义泛型类型对象(比如ArrayList<Integer>)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没创建,如何确定这个泛型参数是什么。所以静态方法与静态类中是不能够使用泛型的。
(2)示例代码如下:
public class Test2<T> {
//s1.报错是因为泛型参数是要创建对象的时候才确定
public static T one;
public static T test(T t){
}
//s2.这里可以,是因为这是泛型方法,此T不是指的Test2<T>中的T
public static <T> T test1(T t){
return t;
}
}
(3)为什么静态的泛型方法可以使用泛型呢?
是因为泛型方法是在调用的过程中来确定类型的,因此静态的泛型方法中是可以使用泛型的。
2.4ジェネリックタイプでのメソッドの競合
(1)因为擦除后两个equals方法变成一样的了
(2)示例代码如下:
//s1.因为泛型擦除之后,类型会变成Object,会和Object的equals()方法一样,相当于方法重复定义
@Override
public boolean equals(T t){
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
2.5汎用インスタンスを作成できない
(1)因为类型不确定,所以就无法创建泛型实例
(2)示例代码:
public class Test2 {
//s1.无法创建一个类型参数的实例。例如:下面代码就会引起编译时错误:
public static <E> void append(List<E> list){
E elem = new E();
list.add(elem);
}
//s2.通过反射创建一个参数化类型的实例
public static <E> void append(List<E> list,Class<E> cls) throws Exception {
E elem = cls.newInstance();
list.add(elem);
}
}
2.6汎用配列には引数がありません
2.6.1Java配列の共分散とは何ですか
(1)AppleはFruitを拡張します
(2)Apple []の親クラスはFruit []であると考えることができます。これは、Java配列の共分散と呼ばれます。
2.6.2Java配列の共分散によって引き起こされる問題
(1)为什么不能有泛型数组
(2)是因为当泛型信息被擦除之后,在运行期虚拟机就不知道它是什么类型了,就没办法满足Java数组的协变原则了,并且也不知道数组是什么类型的,所以就不允许有泛型数组。
(a)List<Apple>[]
(b)List<Fruit>[]
(2)サンプルコード1:
public static <T> void test1(){
Apple[] apples = new Apple[10];
Fruit[] fruits = new Fruit[10];
System.out.println(apples.getClass());
System.out.println(fruits.getClass());
fruits = apples;
//s1.编译通过,运行时会报ArrayStoreException异常
fruits[0] = new Banana();
//s2.下列语句可以为什么?
//(1)因为Plate<?>表示的是不知道类型的任意类型,所以是可以的,相当于Object
Plate<?>[] plates = new Plate<?>[10];
}
(3)サンプルコード2:
public static <T> void test2(){
//s1.Fruit是Apple的父类
//s2.Fruit[]是Apple[]的父类,这就是Java数组的协变.
//s3.如果加入泛型后,由于泛型的擦除机制,运行时将无法知道数组的类型。
//s4.所以下列方式创建泛型数组是不允许的。
Plate<Apple>[] applePlates = new Plate<Apple>[10];
}
le[]的父类,这就是Java数组的协变.
//s3.如果加入泛型后,由于泛型的擦除机制,运行时将无法知道数组的类型。
//s4.所以下列方式创建泛型数组是不允许的。
Plate<Apple>[] applePlates = new Plate<Apple>[10];
}