【解惑】JVMがJava汎用クラスをどのように理解するか
//
public class Pair<T>{
private T first=null;
private T second=null;
public Pair(T fir,T sec){
this.first=fir;
this.second=sec;
}
public T getFirst(){
return this.first;
}
public T getSecond(){
return this.second;
}
public void setFirst(T fir){
this.first=fir;
}
}
上は典型的な汎用(generic)コードです.Tはタイプ変数であり、任意の参照タイプであってもよい.
1.Generic classオブジェクトの作成 Pair
2、JVMがどのように汎用概念を理解するか——タイプ消去 実際、JVMは汎用性を知らず、すべての汎用性はコンパイル段階で一般的なクラスと方法に処理されています. 処理方法は簡単で,タイプ変数Tの消去(erased)と呼ぶ. 汎用タイプをどのように定義しても、元のタイプが自動的に提供されます.元のタイプの名前は、消去タイプパラメータの汎用タイプの名前です. 汎用型の型変数が限定されていない場合(
// :
public class Pair{
private Object first=null;
private Object second=null;
public Pair(Object fir,Object sec){
this.first=fir;
this.second=sec;
}
public Object getFirst(){
return this.first;
}
public void setFirst(Object fir){
this.first=fir;
}
}
3、汎用制約と限界——タイプ消去によるトラブル
(1) 汎用タイプのマルチステート面倒を継承します.(——子に親を上書きする方法はありません)
次のクラスのSonPairを見てみましょう
class SonPair extends Pair<String>{
public void setFirst(String fir){....}
}
明らかに,プログラマーの本意はSonPairクラスで親クラスPair
これはマルチステートにとって確かに大きなトラブルであり、コンパイラがこの問題をどのように解決しているかを見てみましょう.
コンパイラは、SonPairでブリッジメソッド(bridge method)を自動的に生成します. public void setFirst(Object fir){ setFirst((String) fir) } これにより、SonPairのブリッジメソッドは確かに汎用親のsetFirst(Object)を上書きすることができます.また、ブリッジメソッドの内部では、実はサブクラスバイトsetFirst(String)メソッドが呼び出されます.マルチステートには問題ありません.
問題はまだ終わっていないので、多態の中の方法でカバーすればいいのですが、橋の方法には疑問があります.
今、SonPairでgetFirst()メソッドを上書きしたいとしたら?
class SonPair extends Pair<String>{
public String getFirst(){....}
}
親クラスのgetFirstを上書きするブリッジメソッドが必要なため、コンパイラは自動的にSonPairでpublic Object getFirst()ブリッジメソッドを生成します. しかし、疑問が出てくると、SonPairには2つのメソッドが署名されているように見えます(ただし、戻りタイプが異なるだけです):
①String getFirst() //自分で定義した方法
②Object getFirst() // コンパイラ生成ブリッジメソッド まさか、コンパイラはメソッド署名が同じ複数のメソッドが1つのクラスに存在することを許可しますか?
実際には皆さんが知らない知識があるかもしれません. ①メソッド署名は確かにメソッド名+パラメータリストのみです.これは間違いありません! ②メソッド署名のような複数のメソッドを絶対に作成することはできません.このようにプログラムを書くと、コンパイラは見逃されません.これも間違いありません! ③最も重要な点は、JVMがパラメータタイプと戻りタイプで1つのメソッドを決定することです.コンパイラがメソッド署名のような2つのメソッドを何らかの方法で自分でコンパイルすると(コンパイラ自身がこのような奇跡を創造するしかありませんが、私たちのプログラマーはこのコードを人為的に書くことはできません).JVMは、戻りタイプが異なることを前提として、これらの方法を明確にすることができます.
(2)汎用型におけるメソッド競合
コードを見てみましょう
// equals
public class Pair<T>{
public boolean equals(T value){
return (first.equals(value));
}
}
このように見ると、問題のないコードはコンパイラにも合格できないようです.
【Error】 Name clash: The method equals(T) of type Pair
コンパイラはあなたの方法がObjectの方法と衝突したと言っています.これはなぜですか.
最初はこの問題もよく分かりませんでしたが、コンパイラがequals(T)のような方法でObjectのequals(Object)をカバーしてくれたような気がします.皆さんの議論を経て、この問題をこのように説明すべきだと思いますか?
まず、サブクラスメソッドを上書きするには、親メソッドと同じメソッド署名(メソッド名+パラメータリスト)が必要であることを知っています.また、サブクラスのアクセス権>=親メソッドのアクセス権を保証する必要があります.これはよく知られている事実です.
そして、上記のコードでは、コンパイラがPair
次に、コンパイラが汎用コードのTをObjectに置き換え(消去).消去後にequals(T)がequals(Object)になってしまったことに突然気づき、しまった.この方法はObjectクラスのequalsと同じである.上書きされていないと判断し始めたことに基づいて、コンパイラは完全に狂ってしまった(精神分裂)それから2つの結論を出しました:①元の思想を堅持します:カバーしていません.しかし今同じように方法の衝突をもたらしました. ②このプログラムを書いたプログラマーは気が狂った(ハハ).
それに、Pair
(3)汎用配列なし
Pair
Pair
この書き方コンパイラは、Cannot create a generic array of Pair
汎用消去後、Pair
汎用配列が存在すると仮定すると
Object[0]=stringPairs[0]; Ok
Object[1]=intPairs[0]; Ok
これは面倒で、理論的にはObject[]はすべてのPairオブジェクトを格納することができるが、これらのPairオブジェクトは汎用オブジェクトであり、彼らのタイプ変数は異なるので、各Object[]配列要素を呼び出すオブジェクト方法は異なる記憶を得ることができるかもしれない.文字列かもしれないし、整形かもしれない.これはJVMには予想できない.
配列はその要素タイプ、つまりすべての要素オブジェクトが同じでなければならないことを覚えておいてください.汎用タイプではできません.Pair
まとめ:汎用コードとJVM ①仮想マシンには汎用型はなく、普通のクラスと方法しかありません. ②コンパイル段階では、すべての汎用クラスのタイプパラメータがObjectまたはそれらの限定境界に置き換えられます.(タイプ消去) 3汎用型を継承する場合,ブリッジ法の合成は型変数消去による多態災害を回避するためである.