ジェネリックス - ジェネリックスの概要

高洛峰
リリース: 2016-12-19 16:02:19
オリジナル
5151 人が閲覧しました

ジェネリックの概要

ジェネリックとは何かを説明するために例を挙げてみましょう。
次の 2 つのクラスがあり、2 つのクラスのオブジェクトを構築し、それぞれのメンバー x を出力する必要があります。
public class StringFoo {
private String x;
public String getX() {
return x; }

public void setX(String x) {

public class DoubleFoo {
private Double x;
パブリックダブルgetX() {
return x;
}

public void setX(Double x) {
this. ong、Date など。型操作に対応するクラスを書くのは本当に退屈です。
したがって、上記の 2 つのクラスを 1 つのクラスにリファクタリングし、次の点を考慮してください:
上記のクラスでは、メンバーとメソッドのロジックは同じですが、型が異なります。 Object はすべてのクラスの親クラスであるため、ユニバーサルにするために、メンバーの型として Object を使用することを検討できます。
public class ObjectFoo {
private Object x;
public Object getX() {
return x; }

public void setX(Object x) {
this.x = x;

以下のように:
public class ObjectFooDemo {
public static void main(String args[]) {
ObjectFoo strFoo = new ObjectFoo();
strFoo.setX("Hello Generics!");
ObjectFoo douFoo = new ObjectFoo() ;
douFoo .setX(new Double("33")) ;
ObjectFoo objFoo = new ObjectFoo();

String str = (String)strFoo.getX(); d = (Double)douFoo.getX();
オブジェクト obj = objFoo.getX()
System.out.println("strFoo.getX=" + str); =" + d);
System.out.println("strFoo.getX=" + obj);
}
}
上記は、型宣言に最上位の基本クラス Object を使用して、ジェネリックスを使用せずに記述したコードです。値を渡し、取り出すときに強制的に型変換を実行します。
JDK は、このような問題をエレガントに解決するために、1.5 以降ジェネリックの概念を導入しました。汎用テクノロジーを使用して作成されたコードは次のとおりです。
public class GenericsFoo {
public T getX() {
return x }

public void setX(T x) {
this; . x = x; }
}

呼び出しコードは次のとおりです:
public static void main(String args[]){
GenericsFoo();
setX("Hello Generics!");
GenericsFoo new GenericsFooObject obj = objfoo.getx (); intln ("strFoo.getX=" + obj);
}
}

いくつかの明らかな変更があることに注意してください。オブジェクトを作成するときは、GenericsFoo など、型を明示的に指定する必要があります。
2. getX メソッドを通じてオブジェクトを取得する場合、型変換は必要ありません。
3.各メソッドを呼び出すときに、パラメータの型がメソッドの作成時に指定した型と一致しない場合、コンパイラはエラーを報告します。
では、なぜジェネリック医薬品が必要なのでしょうか? 2 つの利点があります:
1. 保存されたデータがコンパイル時に正しいかどうかを確認できます。私たちの開発では、できるだけ早い段階で、できればジェネリックがこの条件を満たしているときにエラーを見つける傾向があります。
2. 強制変換の削減、String str = (String)strList.get(0); この操作は、List に格納されているオブジェクトが String に適していない場合、比較的危険な操作です。投げられた異常。
JDK1.5 では、java.util パッケージ内のさまざまなデータ型ツール クラスがジェネリックスをサポートしており、プログラミングで広く使用されているため、習得する必要があります。
ジェネリックの最も一般的なアプリケーションは、以下で紹介するクラス、インターフェイス、メソッドです。
3.4.2 ジェネリックはインターフェイスに適用されます:
public interface ValuePair
public String toString();
ここでは A と B の両方が代表的なタイプ。 cusp <> では、1 つのタイプまたは複数のタイプを使用できます。
3.4.3 ジェネリックはクラスに適用されます。
public class ValuePairImpl {
public Final A first;
public ValuePairImpl(A a, Second = b) ; }
public A getA() { return first; }
public B getB() { return "(" + first + ", " + next + ")";
このクラスがジェネリック インターフェイスを実装する場合、対応する記述は次のようになります:
public class ValuePairImpl
implements ValuePair...
}
3.4.4 ジェネリックは上記のメソッドに適用されます:
ジェネリックは個々のメソッドに適用することもできます。例は次のとおりです:
public class GenericMethod {
public void printValue(T v) {
String str = v.getClass().getName() + “ = " + v.toString();
System.out.println(str);
}
}
構文に注意してください: public 修飾子が <> の後に、関数名、関数パラメーターが続きます。 。もちろん、戻り値はジェネリック型にすることもできます。
3.4.5 使用可能なジェネリックスの型の制限
上記で紹介した 3 つのジェネリック アプリケーションは、インターフェイス、クラス、メソッドに適用される場合の一般的なアプローチです。ジェネリックスに渡すことができる型には制限がありません。ただし、シナリオによっては、使用可能な型を制限したい場合があります。たとえば、受信型は特定のクラスから継承する必要があります (つまり、この場合、特定のクラスのサブクラス、孫などである必要があります)。この場合、一般的な制限構文が使用されます。
extends: ジェネリック型を、この型を含む特定のクラスの子孫となるように制限します。
構文: ここで、T はジェネリック型であり、extends キーワードはジェネリック型をparentClassの子孫に制限します。 parentClass は親クラスのタイプを指定します。親クラスはインターフェイスにすることもできます。
Java 言語では、クラスは 1 回のみ継承でき、インターフェイスは複数回継承できます。指定した型を特定のクラスから継承するように制限し、複数のインターフェイスを実装する場合、構文は次のとおりです。

クラスはインターフェイスの前になければならないことに注意してください。
例は次のとおりです:
public class BaseClass {
       int 値; 

public BaseClass(int value) {
this.value = 値; 
}

public int getValue() {
戻り値; 
}

public void setValue(int value) {
this.value = 値; 
}

}

public class SubClass extends BaseClass{
public SubClass(int value) {
super(value*2); 
}
}

public class GenericBound {

public long sum(List tList) {
long iValue = 0; 
for (BaseClass Base : tList) {
iValue +=base.getValue(); 
}

iValue を返します。 
}

public static void main(String[] args) {
GenericBound obj = new
GenericBound(); 

リスト<サブクラス> list = 新しい LinkedList(); 
list.add(new SubClass(5)); 
list.add(new SubClass(6)); 

System.out.println(obj.sum(list)); 
}
}
运行、输出结果は22です。 
次に、プローブをさらに詳しく説明します。 上の例を次のように記述します。
public class GenericBound {

public long sum(List tList) {
long iValue = 0; 
for (BaseClass Base : tList) {
iValue +=base.getValue(); 
}

iValue を返します。 
}

public static void main(String[] args) {
// 注意! >、無法通过编译
GenericBound obj = new
GenericBound(); 

List list = new LinkedList(); System.out.println(obj.sum( list));
ステートメント GenericBound();基本的な理由は、GenericBound クラス宣言の がこのクラスの構築を制限しているためです。インスタンス化するとき、T は特定の型であり、この型は BaseClass の子孫です。ただし、BaseClass の子孫は SubClass3 や SubClass4 など多数あり、それぞれに固有のサブクラスの型を記述する必要がある場合は、Object を使用して汎用化する方が良いでしょう。通常のクラスと同様に、親クラスの型を使用してさまざまなサブクラスのインスタンスを導入できますか?答えは「はい」です。ジェネリックは、この状況に対するより良い解決策を提供します。それは、以下で詳しく説明する「ワイルドカード ジェネリック」です。
3.4.6 ワイルドカードジェネリック
Java のジェネリック型は、java.lang.String や java.io.File などの通常の Java 型です。たとえば、次の 2 つの変数の型は異なります。
Box boxObj = new Box();
Box();オブジェクトのサブクラスですが、Box と Box の間に関係はありません。Box は Box のサブクラスまたはサブタイプではないため、次の代入ステートメントは無効です。 / コンパイルできません
そこで、ジェネリクスを使用する際に、親クラスの型を利用して、通常のクラスと同様にさまざまなサブクラスのインスタンスを導入し、プログラム開発を簡素化できることを期待しています。 Java のジェネリックスは、この要件を満たすために ? ワイルドカード文字を提供します。
コード例は次のとおりです。
public class WildcardGeneric {
using using through through ’ s ’ through through off ‐ ‐ ‐‐ ‐ ‐ および
( lst.get(i));
strList.add( "1"); strList. () );ただし、この場合、WildcardGeneric.print メソッドのパラメーターが受け入れることができる型は、プログラマーの設計意図に対して少し広すぎる可能性があります。 print に List を受け入れさせたいだけかもしれませんが、この List 内の要素は Number の子孫でなければならないからです。したがって、この場合、ワイルドカードに制限を課す必要があります。この要件を満たすために、境界付きワイルドカード形式を使用できます。 Print メソッドを再度変更しましょう:
Public Void Print (list & lt ;? Extends number & gt; lst) {
for (int i = 0; I & lst.size (); i ++) {
sysem.println (lstrintln .get (i));
}}}
このようにして、list & lt; integer & gt;、list & lt; short & gt; を系譜に渡すことができます。 List の型変数 (list & lt (list & lt、;String> など) など) を print メソッドに渡すことはできません。
例外は?上限付きワイルドカード (上限付きワイルドカード) を拡張するだけでなく、List< super ViewWindow> などの下限付きワイルドカード (下限付きワイルドカード) も使用できます。
最後に、ワイルドカードを使用したジェネリック型の 3 つの形式をまとめます。
GenericType
GenericTypeまずコードを見てみましょう:
public class GenericsFoo {
private T x;
public T getX() {
return x; }

public void setX(T x) {
this.x = x
}

public static void main(String[] args) {
out through アウト ‐ ‐ ‐‐ ‐‐‐‐ および � to GenericsFoo = gf;
gf2.setX("世界"); ! !
文字列 str = gf2.getX(); ! !
gf2.setX(gf2.getX()); ! !
}
}
main メソッドの最後の 3 行は不正なため、コンパイルできないことに注意してください。元々は のジェネリック型です。 を通じて参照された後、setX() は String を渡すときにエラーを報告し、getX() の戻り値の型は String ではありません。さらに奇妙なのは、ステートメント gf2.setX(gf2.getX()); が値を取り出して変更せずに戻しても機能しないことです。これはどうなっているでしょうか?
これらの問題を完全に理解するには、JDK のジェネリックスの内部実装原則を理解する必要があります。まず 2 つの例を見てみましょう:
public class GenericClassTest {
using using through through through through through out through out through out through out through out off ‐ ‐for ‐‐‐‐ ‐ to () .getClass();
System.out.println(c1 == c2); c3);
}
}
実行後の出力結果は次のようになります。この例は、ジェネリック ArrayList、ArrayList、およびジェネリックを持たない ArrayList が実際には同じクラスであることを示しています。ジェネリック医薬品を使用していないようなものです。
2 番目の例を見てみましょう:
class 要素{}
クラス Box {}
クラス ペア list = new ArrayList(); =新しいペア&lt; string&gt;();ドキュメントに記載されているように、Class.getTypeParameters() メソッドは、TypeVariable オブジェクトの配列を返します。配列内の各 TypeVariable オブジェクトは、ジェネリックで宣言された型を記述します。これは、ジェネリックが TypeVariable オブジェクトからインスタンス化されるときに実際の型を知ることができることを意味しているようです。ただし、プログラムの実行結果から、 Class.getTypeParameters() によって返される一連の TypeVariable オブジェクトは、ジェネリック宣言中のパラメーター化された型のプレースホルダーのみを表し、実際のインスタンス化中の型は破棄されていることがわかります。したがって、Java ジェネリックに関する真実は次のとおりです。
ジェネリック コードには、パラメーター化された型に関する情報がまったくありません。
この事実の理由は、JDK がジェネリックの内部実装に消去を使用するためです。具体的な消去方法は次のとおりです。
1) ArrayList、ArrayList、ArrayList? はすべて ArrayList に消去されます。 2) ArrayList、ArrayList< extends BaseClass> はすべて ArrayList に消去されます
消去の実装メカニズムを理解した後、前の例に戻って分析してみましょう。コンパイルできない理由を確認してください:
public class GenericsFoo {
public T getX() {
gf = new GenericsFoogf2.setX("世界"); ! !
String str = gf2.getX(); // エラー! ! !
gf2.setX(gf2.getX()); ! !
}
}
消去メカニズムにより、型が失われた後、GenericsFoo はすべて GenericsFoo に消去されます。
public Object getX();
public void setX(null x);
したがって、値を取り出すジェネリック クラスの get メソッドの戻り値は Object になり呼び出すことができますが、値、パラメーターを設定するジェネリック クラスの set メソッドは戻り値を型変換する必要があります。 type すべて null となり、どの型も null 型に変換できないため、すべての set メソッドを呼び出すことができません。
これは興味深い現象を形成します。 のジェネリック型の場合、取得のみが可能であり、設定はできません。
上記は、一部の有用な型情報が失われる Java ジェネリックスの消去メカニズムを説明しています。ただし、いくつかのトリックを使用して、コンパイラに型情報を再構築させ、set メソッドを正常に呼び出すことができます。次のコードを参照してください。
public void setGeneric(GenericsFoo foo) { ; foo.setX(foo.getX ());
}
setGenericHelper() は追加のジェネリック メソッドです。型パラメータ (戻り値の型の前に山括弧内にあります) これらのパラメータは、メソッド間のパラメータや戻り値の型制約を表すために使用されます。 setGenericHelper () この宣言メソッドにより、コンパイラーは (型インターフェースを介して) GenericsFoo ジェネリックの型パラメーターに名前を付けることができます。しかし、型には親クラスや祖父母クラスを持つことができ、複数のインターフェイスを実装することもできます。では、コンパイラはどの型に変換するのでしょうか。
次のコードを使用して検証します。
public class GenericClassTest3 {
public static String getType(T>) usingスルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルー スルーthrough through''s'' back through‐‐to‐‐‐‐‐‐‐
Public static void main( String [] args) {
integer I = New Integer (5);
System.out.println (GenericClastest3. gettype (i));
したがって、コンパイラーは T が整数、数値、シリアル化可能、またはオブジェクトであると推論できますが、制約を満たす最も具体的な型として整数を選択します。
さらに、ジェネリックの消去メカニズムにより、次のようなジェネリック型に対して new 演算子を直接使用することはできません。
public class GenericNew // コンパイルに失敗しました。 ! !
obj を返します。
ただし、場合によっては、依然としてジェネリック型の動的インスタンス化が必要です。単一のオブジェクトを作成し、配列を作成する場合のコード例は次のとおりです。
public class GenericNew cls, len); }
}
; create メソッドはジェネリック型のインスタンスを動的に作成します。 createArray メソッドは動的に配列を作成します。ジェネリック型のインスタンス。




ジェネリックに関するその他の記事 - ジェネリックの概要については、PHP 中国語 Web サイトに注目してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート