Java:汎用(深く解析し、一文で読む)

13430 ワード

基本概念と原理
汎用型を使用する理由:
汎用型を使用しない前に、Javaコレクションにオブジェクトを「捨てる」と、コレクションはオブジェクトのタイプを忘れ、すべてのオブジェクトをObjectタイプとして処理します.プログラムがコレクションからオブジェクトを取り出すと、コードが肥大化しているだけでなく、ClassCastException異常を引き起こしやすい強制タイプ変換が必要になります.
タイトルの基本概念:
汎用とは、クラス、インタフェース、メソッドを定義するときにタイプパラメータを使用することを許可します.このタイプパラメータ(または汎用)は、変数を宣言したり、オブジェクトを作成したり、メソッドを呼び出したりするときに動的に指定されます(つまり、実際のタイプパラメータを入力したり、タイプ実パラメータとも呼ばれます).
汎用性のメリット:
  • より良い安全性汎用型を使用することで、開発環境とコンパイラは間違いのないタイプを確保し、プログラムに安全防護網を複数設置することができます.
  • より良い可読性汎用型を使用すると、煩雑な強制型変換を省くことができ、明確なタイプ情報を加えることで、コード可読性も向上します.

  • 汎用的な使用:
    public class Test<T> {
        T first;
        T second;
        public Test(T first, T second) {
            this.first = first;
            this.second = second;
        }
        public T getFirst() {
            return first;
        }
        public T getSecond() {
            return second;
        }
    }
    

    Testは汎用クラスで、一般クラスとの違いは次のとおりです.
  • 類名の後に1つ増えた.
  • firstとsecondのタイプはすべてTです.

  • Tはタイプパラメータを表し,汎用型はタイプパラメータ化であり,処理するデータ型は固定ではなくパラメータとして伝達できる.
    汎用の原理:
    JavaにはJavaコンパイラとJava仮想マシンがあり、コンパイラはJavaソースコードを.classファイルに変換し、仮想マシンは.classファイルをロードして実行します.汎用クラスの場合、Javaコンパイラは汎用コードを通常の非汎用コードに変換し、上の通常Testクラスコードとその使用コードのように、タイプパラメータTを消去し、Objectを置き換え、必要な強制タイプ変換を挿入します.Java仮想マシンが実際に実行されると、汎用性というものを知らず、普通のクラスやコードしか知らない.
    汎用消去:
    Java汎用型は実装額を消去することによって実現され、クラス定義のタイプパラメータはTのようにObjectに置き換えられ、プログラムの実行中に汎用型の実際のタイプパラメータを知らない.例えばTest、実行中にTestしか知らず、Integerを知らない.
    深く汎用
    //              ,     E
    public interface Test<E> {
        
        //      ,E       
        //        E      
        void add(E x);
        Iterable<E> iterator();
        
    	//    ,E          
        E next();
    }
    public interface Map<K, V> {
    
        //    K、V          
        Set<K> keySet();
        V put(K key, V value);
    }
    

    解釈:インタフェース、クラスを定義するときに汎用パラメータを宣言することを許可し、汎用パラメータはインタフェース全体、クラス内でタイプとして使用することができ、ほとんどの一般的なメソッドタイプを使用できる場所でこの汎用パラメータを使用することができます.
    注意:汎用宣言のカスタムクラスを作成し、そのクラスのコンストラクタを定義する場合、コンストラクタ名は元のクラス名になります.汎用宣言を追加しないでください.たとえば、Testクラスのコンストラクタを定義します.そのコンストラクタ名は依然としてTestであり、Testではありません.このコンストラクタを呼び出すときはTestの形式を使用することができ、もちろんTパラメータに実際のタイプパラメータを入力する必要があります.Java 7では、<>のタイプの実パラメータを省略できる「菱形」構文が提供されています.
    汎用系から子類を派遣する:
    メソッドのパラメータは変数、定数、式などのデータを表し、本稿ではそれらを直接パラメータ、またはデータパラメータと呼ぶ.メソッドを定義するときにデータパラメータを宣言することができ、メソッド(メソッドを使用)を呼び出すときにこれらのデータパラメータに実際のデータを入力する必要があります.これと同様に、クラス、インタフェース、メソッドを定義するときに宣言汎用パラメータを使用し、クラス、インタフェース、メソッドを使用するときに汎用パラメータが実際のタイプに入力される必要があります.
    //   A  Apple ,Apple        
    public class A extends Apple<T> {}   //  
    
    //  Apple   T    String  
    public class A extends Apple<String> //  
    

    メソッドを呼び出すときは、すべてのデータパラメータ値を入力する必要があります.呼び出しメソッドとは異なり、クラス、インタフェースを使用するときに、汎用パラメータではなく実際のタイプパラメータを入力することもできます.つまり、次のコードも正しいです.
    public class A extends Apple  //  
    

    このようにAppleクラスを使用する場合に汎用型を省略する形式を元のタイプ(raw type)と呼ぶ.Appleクラスを使用している間に実際のタイプ(つまり元のタイプを使用)が入力されていない場合、Javaコンパイラは、チェックされていない操作や安全ではない操作が使用されていることを警告する可能性があります.つまり、汎用チェックの警告です.
    汎用クラスは存在しません:
    次のコードを参照してください.
    List<String> list = new ArrayList<>();
    List<Integer> list2 = new ArrayList<>();
    //  getClass()     list list2      
    System.out.println(list.getClass() == list2.getClass());
    

    上のコードクリップを実行すると、falseを出力すべきだと思っている読者がいるかもしれませんが、実際にtrueを出力します.汎用型の実際のタイプパラメータが何であるかにかかわらず、実行時に同じクラス(Class)が常に存在するためです.汎用パラメータにどのタイプの実パラメータが入力されるかにかかわらず、Javaでは同じクラスとして処理され、メモリにもメモリ領域が1つしか消費されないため、静的メソッド、静的初期化ブロック、または静的変数(いずれもクラスに関連する)の宣言および初期化では汎用パラメータの使用は許可されません.
    public class Test<T> {
    
        //      ,                
        static T info;
        
        //  
        T age;
        public void foo(T msg) { }
    
        //      ,                  
        public static void bar(T msg) {}
    }
    

    システムでは実際に汎用クラスが生成されないため、instanceof演算子の後に汎用クラスを使用することはできません.
    java.util.Collection<String> cs = new java.util.ArrayList<String>();
    //           :instanceof          
    if(cs instanceof java.util.ArrayList<String>) {}
    

    タイプワイルドカードの使用:
    様々な汎用リストの親を表すために、タイプワイルドカードを使用し、タイプワイルドカードは疑問符(?)であり、1つの疑問符をタイプ実パラメータとしてリストセットに渡し、リスト>(要素タイプ未知のリストを意味する)と書くことができる.この疑問符(?)はワイルドカードと呼ばれ、要素タイプは任意のタイプに一致します.次のコードを参照してください.
    public void test(List<?> c) { }
    

    タイプワイルドカードの上限を設定(コヒーレント):
    ワイルドカードの上限を指定するコレクションは、アセンブリからのみエレメント(取り出したエレメントが常に上限のタイプまたはそのサブクラス)を取得し、アセンブリにエレメントを追加することはできません(コンパイラは、アセンブリエレメントが実際にどのシードタイプであるかを決定できません).
    設定タイプワイルドカードの下限(逆):
    ワイルドカードの上限を指定できるほか、Javaではワイルドカードの下限を指定できます.ワイルドカードの下限はsuperタイプ>の方式クラスで指定され、ワイルドカードの下限の役割はワイルドカードの上限の役割とは正反対です.FooはBarのサブクラスであり,プログラムがA super Foo>変数を必要とする場合,プログラムはA,AをA super Foo>タイプの変数に割り当てることができ,これをインバータと呼ぶ.インバータの汎用型では,コンパイラは集合要素が下限の親タイプであることしか知らないが,具体的にどの親タイプであるかは不明である.したがって、このような逆変換の汎用集合は、実際に付与された集合要素が常に逆変換宣言された親であるため、その中に要素を追加することができ、集合から要素を取り出すときはObjectタイプとしてしか扱われない(コンパイラは、どの親が取り出されたオブジェクトなのかを特定できない).