なぜJavaの汎用型は「偽汎用型」なのか.

11053 ワード

個人ブログ:https://blog.N0tExpectErr0r.cn
小欄:https://xiaozhuanlan.com/N0tExpectErr0r
Java汎用
Javaの汎用性はJDK 5がもたらす新しい特性であり、次のような利点があります.
  • は、同じコード
  • を実行するために複数のデータ型に適用される.
  • 汎用のタイプ使用時に
  • を指定する.
  • 汎用型は結局「模版」
  • しかし、下向き互換性を実現するために、Javaの汎用型は文法糖にすぎず、C++のような真の汎用型ではない.
    どのように証明しますか?次の例を見てみましょう
    アテステーション
    この例では、Listのセットを定義し、addメソッドを呼び出してIntegerタイプの値を加えることができる.
    次のようにaddメソッドを呼び出してStringを追加すると、当然エラーが発生します.
    public static void main(String[] args) throws Exception {
    	List<Integer> list = new ArrayList<>();
        list.add("str");
    }
    

    エラーメッセージは次のとおりです.
    add (java.lang.Integer) in List cannot be applied to (java.lang.String)
    明らかに、Stringタイプの値を直接加えることはできません.
    しかし、私たちは別の道を切り開くことができます.反射によりadd法を間接的に呼び出し,このListString型の値を加えることを試みた.
    public static void main(String[] args) throws Exception {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        Method add = list.getClass().getMethod("add", Object.class);
        add.invoke(list,"str");
        System.out.println(list);
        System.out.println(list.get(1));
    }
    

    実行中にエラーは発生せず、strの印刷に成功しました!
    [1, str] str
    このことから,汎型とは確かに偽汎型であることがわかる.もともとIntegerのリストにしか読み込めなかったが、Stringタイプの値を読み込むことに成功した.
    タイプ消去
    実際,Javaの汎用はコンパイル期間のみ有効であり,実行期間では消去され,つまりすべての汎用パラメータタイプはコンパイル後に消去される.これがいわゆるタイプ消去です.
    次の例を示します.
    public class Test {  
        public void addList(List<String> stringList){
        }  
        public void addList(List<Integer> intList) {
        }  
    }  
    

    このクラスでは,ListListの2つの異なるタイプのパラメータを入力する2つの方法を定義し,問題はないように見えた.
    しかし、コンパイラは次のエラーを報告します.
    Method addList(List) has the same erasure addList(List) as another method in type Test
    すなわち,この2つの方法の署名は,タイプ消去後ともにaddList(List)であった.
    以下に、リストの種類消去について説明します.
  • ListList消去後のタイプはListである.
  • List[]List[]消去後のタイプはList[]である.
  • List extends E>List super E>消去後のタイプはListである.
  • List消去後のタイプはListです.

  • C++テンプレート
    C++のテンプレートは、我々のいわゆる「真汎用型」であり、以下に、C++がテンプレートを使用するクラスの例を示します.
    #include
    using namespace std;
    
    template<class T> class Test
    {
    	private:
    		T obj;
    	public:
    	  	Test(T x){obj=x;}
    	  	void try(){obj.fun();}
    };
    
    class A
    {
    	public:
    	  	void fun(){cout<<"A::fun()"<<endl;}
    };
    
    int main(int argc, char* argv[]){
    	A a;
    	Test<A> test(a);
    	test.try();
    }
    

    クラスTestにはタイプTのオブジェクトが格納されている.興味深いことに、Test::try()メソッドでobjメソッドを呼び出した.fun()の方法がTに含まれていることをどうして知っていますか.
    Testのコンストラクション関数を呼び出してこのテンプレートをインスタンス化すると,コンパイラが調べたところ,Aクラスにはfunメソッドが含まれていることが分かったので,コンパイルは通過できる.
    Aクラスにfunメソッドが含まれていない場合、コンパイルは通過しません.このようなタイプの安全が保障されています.
    C++テンプレートでは、コンパイラは提供されたタイプのパラメータを使用してテンプレートを拡張するので、fun()のために生成されたC++コードはListのために生成されたコードとは異なり、ListListは実際には2つの異なるクラスである.
    まとめ
    Javaの汎用型は「偽」ですが、消去のタイプがあります.しかし,汎用的な導入がJava言語に与える影響は依然として大きいことは否めない.
    そのため、汎用型を使用する場合は、できるだけタイプ消去という特徴を考慮し、自分のパッケージがタイプ安全かどうかを考えなければならない.