ホームページ  >  記事  >  Java  >  Javaジェネリックスの詳細な説明

Javaジェネリックスの詳細な説明

高洛峰
高洛峰オリジナル
2016-12-19 14:58:461217ブラウズ
  1. 概要
    ジェネリックが導入される前は、Java 型はプリミティブ型と複合型に分割され、配列とクラスに分割されていました。ジェネリックスの導入後は、複合型をさらに多くの型に分割できるようになります。
    たとえば、元の型 List は List、List などの型に細分化されています。
    List と List は 2 つの異なる型であることに注意してください。
    String が Object を継承しても、それらの間には継承関係はありません。次のコードは不正です
    List ls = new ArrayList();
    List lo = ls;
    この設計の理由は、lo の宣言に従って、コンパイラが追加を許可しているためです。 to lo 任意のオブジェクト (Integer など)、ただしこのオブジェクトは
    List であるため、データ型の整合性が失われます。
    ジェネリックの導入前に、クラス内のメソッドが複数のデータ型をサポートしている場合、メソッドをオーバーロードする必要があります。ジェネリックの導入後、この問題は解決され、
    (ポリモーフィズム)、さらに複数のパラメーターを定義できるようになります。戻り値間の関係。
    例:
    public void write(Integer i, Integer[] ia);
    public void write(Double d, Double[] da);
    汎用バージョンは
    public [ ] ta);

    2. 定義と使用
    型パラメーターの命名スタイルは次のとおりです:
    仮型パラメーターの名前として簡潔な名前 (可能であれば 1 文字) を使用することをお勧めします。他の通常の
    仮パラメータと区別しやすくするため、小文字は避けることをお勧めします。
    区別するための具体的なタイプがない場合は、タイプに T を使用します。これはジェネリック メソッドでよく見られます。複数の型パラメータがある場合は、S など、アルファベットの T に近い文字を使用することがあります。
    ジェネリック関数がジェネリック クラスに出現する場合、混乱を避けるために、メソッドの型パラメータとクラスの型パラメータで同じ名前を使用しないことが最善です
    。内部クラスについても同様です。

    2.1 型パラメーターを使用してクラスを定義する
    型パラメーターを使用してクラスを定義する場合、クラス名の直後の <> に 1 つ以上の型パラメーターの名前を指定します。また、型パラメーターを指定することもできます。値の範囲は制限されており、複数の型パラメータは、記号で区切られます。
    型パラメーターを定義した後は、通常の型を使用するのと同じように、クラス内の定義位置以降のほぼどこでも (静的ブロック、静的プロパティ、静的メソッドを除く) 型パラメーターを使用できます。
    親クラスで定義された型パラメータはサブクラスに継承できないことに注意してください。
    public class TestClassDefine < ;>、1 つ以上の型パラメーターの名前を指定します。
    型パラメーターの値の範囲を制限したり、複数の型パラメーターを記号で区切ることもできます。
    型パラメータを定義した後は、通常の型を使用する場合と同様に、メソッド内の定義位置以降の任意の場所で型パラメータを使用できます。
    例:
    public T testGenericMethodDefine(T t, S s){
    ...
    }
    注: 型パラメーターを使用してメソッドを定義する主な目的は、複数のパラメーターを表現して戻り値を返すことです。価値観間の関係。たとえば、この例の T と S の間の継承関係では、戻り値の型は最初の型パラメーターの値と同じです。
    ポリモーフィズムを実現したいだけの場合は、最初にワイルドカードを使用してください。ワイルドカードの内容については、次のセクションを参照してください。
    public void testGenericMethodDefine2(List s){

    }

    public void testGenericMethodDefine2(List s){

    }

    3.クラスまたはメソッドの型パラメータに値を割り当てる場合は、すべての型パラメータを割り当てる必要があります。そうしないと、コンパイル エラーが発生します。

    3.1 型パラメータを持つクラスに型パラメータを割り当てる
    型パラメータを持つクラスに型パラメータを割り当てる方法は 2 つあります
    まず、クラス変数を宣言するかインスタンス化します。たとえば、
    List list;
    list = new ArrayList;
    2 番目の継承クラスまたはインターフェイスを実装する場合。たとえば、
    public class MyList extends ArrayListimplements List

    3.2 型パラメータを持つメソッドへの値の割り当て
    ジェネリック メソッドを呼び出すと、コンパイラは自動的に値を割り当てます。型パラメータへの代入が成功しない場合、コンパイル エラーが発生します。たとえば
    public T testGenericMethodDefine3(T t, List list){
    ...
    }
    public T testGenericMethodDefine4(List list list2){
    ...
    }

    Number n = null;
    Integer i = null;
    testGenericMethodDefine(n, i);//このとき、T は Number で、S は Integer です
    testGenericMethodDefine(o, i);/ / T はオブジェクト、S は整数です

    List list1 = null;
    testGenericMethodDefine3(i, list1)//T はこの時点では Number です

    List list2 = null;
    testGenericMethodDefine4(list1, list2) // コンパイルエラー
    3.3 ワイルドカード
    上記 2 つのセクションでは、型パラメータに特定の値が割り当てられていますが、さらに、未定義の値を型パラメータに割り当てることもできます。例:
    ListunknownList;
    List? super Integer>
    注: Java コレクション フレームワークでは、パラメーター値が不明なクラスの場合、読み取りのみ可能です 要素のうち、要素を追加することはできません
    その型が不明であるため、コンパイラは追加された要素の型がコンテナの型と互換性があるかどうかを識別できません

    唯一の例外です。 List listString;
    List<>unknownList2 = listString;
    unknownList2;
    listString =unknownList;// 配列パラダイム
    を宣言するために、クラスを使用できます。配列ですが、配列を作成することはできません
    List [] iListArray;
    new ArrayList[10];// 実装原理

    5.1.実行時には一般的な情報は含まれません。クラスのインスタンスのみが含まれます。型パラメーターの定義情報が含まれます。
    ジェネリックは、Java コンパイラーによる消去と呼ばれるフロントエンド プロセスを通じて実装されます。これは (基本的に) 汎用バージョンを非汎用バージョンに変換する、ソースからソースへの変換と考えることができます。
    基本的に、消去によりすべてのジェネリック型情報が削除されます。山かっこ内のすべての型情報は破棄されるため、たとえば、
    List 型は List に変換されます。型変数へのすべての参照は、型変数 (通常は Object) の上限に置き換えられます。また、
    結果のコードの型が正しくない場合は、適切な型への変換が挿入されます。 ️ 。これは、時間やスペースのオーバーヘッドが追加されないことを意味し、これは素晴らしいことです。残念ながら、これは型変換に依存できないことも意味します。

    5.2. ジェネリック クラスはそのすべての呼び出しで共有されます
    次のコードによって出力される結果は何ですか?
    List l1 = 新しい ArrayList();
    List l2 = new ArrayList();
    System.out.println(l1.getClass() == l2.getClass()) ;
    もしかしたら嘘だと言うかも知れませんが、それは間違っています。 true と出力されます。ジェネリック クラスのすべてのインスタンスは、実際の型パラメーターに関係なく、実行時に同じランタイム クラスを持つためです。
    実際、ジェネリックがジェネリックと呼ばれる理由は、考えられるすべての型パラメータに対して同じ動作をするためであり、同じクラスが多くの異なる
    型と見なすことができるからです。その結果、クラスの静的変数とメソッドもすべてのインスタンス間で共有されます。このため、静的メソッドまたは静的初期化コード内で、または静的変数を宣言および初期化するときに、型パラメーター (型パラメーターは特定のインスタンスに属します) を使用することは違法です。

    5.3. キャストとinstanceof
    ジェネリック クラスがそのすべてのインスタンスによって共有されるもう 1 つの意味は、インスタンスが特定のタイプのジェネリック クラスであるかどうかを確認するのは無意味であるということです。 I Collection CS = New ArrayList & LT; String & GT;
    If (CS Instanceof Collection & LT; String & GT;) // 同様に不正です
    String & gt; = (Collection & LT; String & GT; ) cs;
    ランタイム環境ではそのようなチェックが行われないため、未チェックの警告が表示されます。

    6. クラスのジェネリック処理
    Java 5以降、クラスはジェネリックになります。
    JDK1.5 の変更点の 1 つは、クラス java.lang.Class がジェネリックになったことです。これは、ジェネリックスをコンテナー クラスを超えて拡張する興味深い例です。
    さて、Class には型パラメータ T があります。T は何を表しているのかと疑問に思うかもしれません。 Class オブジェクトによって表される型を表します。たとえば、
    String.class 型は Class を表し、Serializable.class は Class を表します。
    これを使用して、リフレクション コードのタイプ セーフティを向上させることができます。
    特に、Class の newInstance() メソッドが T を返すようになったので、リフレクションを使用してオブジェクトを作成するときに、より正確な型を取得できるようになりました。
    たとえば、SQL ステートメントを指定してデータベース クエリを実行し、クエリ条件を満たすデータベース内のオブジェクトのコレクションを返すツール メソッドを作成するとします。 1 つの方法は、次のコードのように、ファクトリ オブジェクトを明示的に渡すことです。 Factory, String ステートメント) {
    Collection & LT; Result = New ArrayList & LT & ++) { /* jdbc の結果を反復処理します。 item = Factory.make(); T item =
    }
    次のように呼び出すことができます:
    select(new Factory(){
    public EmpInfo make() { return new EmpIn fo(); }俳優になる
    class EmpInfoFactoryimplements Factory { ...
    public EmpInfo make() { return new EmpInfo();}
    }
    次に呼び出します:
    select(getMyEmpInfoFactory(), "selection string");
    この解決策の欠点それは、呼び出しサイトで長い匿名ファクトリ クラスを使用するか、使用する型ごとにファクトリ クラスを宣言し、そのオブジェクトを呼び出しサイトに渡すことの 2 つのうちの 1 つが必要であるという点です。これは不自然です。
    クラス型パラメータ値の使用は非常に自然であり、リフレクションによって使用できます。ジェネリックのないコードは次のようになります:
    Collection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”); ...
    public static Collection select(Class c, String sqlStatement) {
    Collection result = new ArrayList () ;
    /* jdbc を使用して SQL クエリを実行します */
    for ( /* jdbc の結果を反復処理します */ ) {
    Object item = c.newInstance();
    /* リフレクションを使用し、SQL の結果から項目のフィールドをすべて設定します */
    result.add(item);
    誰誰誰誰誰の result.add(item); Class がジェネリックになったので、次のように記述できます。
    Collection emps=sqlUtility.select(EmpInfo.class, ''select * from emps''); ;T>c, String sqlStatement) {
    Collection result = new ArrayList();
    /* jdbc を使用して SQL クエリを実行します */
    for ( /* jdbc の結果を反復処理します */ ) {
    T item = c.newInstance(); 必要なコレクション。
    このテクニックは非常に便利なトリックで、アノテーションを処理する新しい API で広く使用される慣用句になっています。

    7. 新旧コードの互換性

    7.1. コードの互換性を確保するために、次のコード コンパイラ (javac) が独自に型安全性を保証します
    List l = new ArrayList();
    List< ; String> l = new ArrayList();

    7.2. クラス ライブラリを汎用バージョンにアップグレードする場合は、共変の戻り値を使用するように注意してください。
    たとえば、コードを変更します
    public class Foo {
    public Foo create(){
    return new Foo();
    }
    }

    public class Bar extends Foo {
    public Foo create(){
    """"""" ;
    }
    }
    ライブラリのクライアントには注意してください。




    Java ジェネリックスの詳細な説明に関連するその他の記事については、PHP 中国語 Web サイトに注目してください。


    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。