Java Language
ジェネリックス
サーチ…
前書き
ジェネリックスは、コンパイル時の型の安全性を提供しながら、型またはメソッドがさまざまな型のオブジェクトを操作できるように、Javaの型システムを拡張する汎用プログラミングの機能です。特に、Javaコレクション・フレームワークは、ジェネリックをサポートして、コレクション・インスタンスに保管されているオブジェクトのタイプを指定します。
構文
- クラスArrayList <E> {} //型パラメータEを持つ汎用クラス
- クラスHashMap <K、V> {} // 2つの型パラメータKとVを持つ汎用クラス
- <E> void print(E element){} //型パラメータEを持つ汎用メソッド
- ArrayList <String>の名前です。 //ジェネリッククラスの宣言
- ArrayList <?>オブジェクト。 //型パラメータが不明な汎用クラスの宣言
- 新しいArrayList <String>()//汎用クラスのインスタンス化
- 新しいArrayList <>()//型推論「diamond」(Java 7以降)を使用したインスタンス化
備考
ジェネリックは、タイプ消去によってJavaで実装されます。つまり、実行時にジェネリッククラスのインスタンス化で指定されたタイプ情報は利用できません。たとえば、 List<String> names = new ArrayList<>();
というステートメントがありList<String> names = new ArrayList<>();
実行時にエレメントタイプString
を復元できないリストオブジェクトを生成します。ただし、リストがList<String>
型のフィールドに格納されている場合、またはこの同じ型のメソッド/コンストラクタパラメータに渡された場合、またはその戻り型のメソッドから返された場合、実行時に完全な型情報を復元できますJava Reflection APIを使用します。
これは、ジェネリック型(たとえば: (List<String>) list
)にキャストするときに、キャストがチェックされていないキャストであることも意味します。パラメータ<String>
が消去されるため、JVMはList<String>
からList<String>
へのキャストが正しいList<?>
確認することはできません。 JVMは実行時にList
to List
キャストしか見ることができません。
汎用クラスの作成
ジェネリックスを使用すると、クラス、インタフェース、およびメソッドは、他のクラスとインタフェースを型パラメータとして使用できます。
この例では、ジェネリッククラスParam
を使用して、角カッコ( <>
)で区切られた単一の型パラメータ T
をとります 。
public class Param<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
このクラスをインスタンス化するには、 T
代わりに型引数を指定します。たとえば、 Integer
:
Param<Integer> integerParam = new Param<Integer>();
type引数は、配列やその他の汎用型を含む任意の参照型になります。
Param<String[]> stringArrayParam;
Param<int[][]> int2dArrayParam;
Param<Param<Object>> objectNestedParam;
Java SE 7以降では、type引数は、 ダイヤモンドと呼ばれる型引数( <>
)の空のセットで置き換えることができます。
Param<Integer> integerParam = new Param<>();
他の識別子とは異なり、型パラメータには命名の制約はありません。しかし、彼らの名前は、大文字の目的の最初の文字です。 (これは正式なJavaDocsでも当てはまります。)
例としては、 "type"の場合はT
、 " element "の場合はE
、 "key" / "value"の場合はK
/ V
があります 。
ジェネリッククラスの拡張
public abstract class AbstractParam<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
AbstractParam
は、型パラメータT
宣言された抽象クラスです。このクラスを拡張するとき、その型パラメータは<>
中に書かれた型引数で置き換えることができます。そうでない場合は、型パラメータは変更されません。以下の第1および第2の例では、 String
とInteger
がtypeパラメータを置き換えます。 3番目の例では、typeパラメーターは変更されません。 4番目の例ではジェネリックはまったく使用しないため、クラスにObject
パラメータがある場合と似ています。コンパイラはAbstractParam
が生の型であることを警告しますが、 ObjectParam
クラスをコンパイルします。 5番目の例は、2つの型パラメータ(下記の「複数型パラメータ」を参照)を持ち、2番目のパラメータをスーパークラスに渡される型パラメータとして選択します。
public class Email extends AbstractParam<String> {
// ...
}
public class Age extends AbstractParam<Integer> {
// ...
}
public class Height<T> extends AbstractParam<T> {
// ...
}
public class ObjectParam extends AbstractParam {
// ...
}
public class MultiParam<T, E> extends AbstractParam<E> {
// ...
}
使用方法は次のとおりです。
Email email = new Email();
email.setValue("[email protected]");
String retrievedEmail = email.getValue();
Age age = new Age();
age.setValue(25);
Integer retrievedAge = age.getValue();
int autounboxedAge = age.getValue();
Height<Integer> heightInInt = new Height<>();
heightInInt.setValue(125);
Height<Float> heightInFloat = new Height<>();
heightInFloat.setValue(120.3f);
MultiParam<String, Double> multiParam = new MultiParam<>();
multiParam.setValue(3.3);
Email
クラスでは、 T getValue()
メソッドはString getValue()
シグネチャを持つかのように動作し、 void setValue(T)
メソッドはvoid setValue(String)
として宣言されているかのように動作しvoid setValue(String)
。
空の中括弧( {}
)で匿名の内部クラスでインスタンス化することもできます。
AbstractParam<Double> height = new AbstractParam<Double>(){};
height.setValue(198.6);
匿名の内部クラスを持つダイヤモンドを使用することはできません。
複数のタイプのパラメータ
Javaでは、ジェネリッククラスまたはインターフェイスで複数の型パラメータを使用できます。複数の型パラメータは、クラスまたはインタフェースで、角括弧の間にカンマで区切ったタイプのリストを配置することによって使用できます。例:
public class MultiGenericParam<T, S> {
private T firstParam;
private S secondParam;
public MultiGenericParam(T firstParam, S secondParam) {
this.firstParam = firstParam;
this.secondParam = secondParam;
}
public T getFirstParam() {
return firstParam;
}
public void setFirstParam(T firstParam) {
this.firstParam = firstParam;
}
public S getSecondParam() {
return secondParam;
}
public void setSecondParam(S secondParam) {
this.secondParam = secondParam;
}
}
使用方法は次のとおりです。
MultiGenericParam<String, String> aParam = new MultiGenericParam<String, String>("value1", "value2");
MultiGenericParam<Integer, Double> dayOfWeekDegrees = new MultiGenericParam<Integer, Double>(1, 2.6);
ジェネリックメソッドの宣言
メソッドにはジェネリック型のパラメータもあります。
public class Example {
// The type parameter T is scoped to the method
// and is independent of type parameters of other methods.
public <T> List<T> makeList(T t1, T t2) {
List<T> result = new ArrayList<T>();
result.add(t1);
result.add(t2);
return result;
}
public void usage() {
List<String> listString = makeList("Jeff", "Atwood");
List<Integer> listInteger = makeList(1, 2);
}
}
実際の型引数を汎用メソッドに渡す必要はないことに注意してください。コンパイラは、ターゲット型(結果を代入する変数など)または実際の引数の型に基づいて、型引数を推論します。一般的には、コールタイプを正しいものにする最も具体的なタイプの引数を推論します。
まれに、明示的な型引数でこの型推論をオーバーライドする必要があることがあります。
void usage() {
consumeObjects(this.<Object>makeList("Jeff", "Atwood").stream());
}
void consumeObjects(Stream<Object> stream) { ... }
この例では、コンパイラは、 stream()
を呼び出した後にObject
がT
に必要であり、そうでなければmakeList
引数に基づいてString
を推論することを「先読み」できないためです。 Java言語は、(メソッドが呼び出されたクラスまたはオブジェクト省略サポートしていないことに注意してくださいthis
型引数が明示的に提供される場合、上記の例では)。
ダイヤモンド
Java 7はDiamond 1を導入して、ジェネリッククラスのインスタンシエーションについていくつかのボイラープレートを削除しました。 Java 7+を使用すると、次のように書くことができます。
List<String> list = new LinkedList<>();
以前のバージョンで書かなければならなかった場所は次のとおりです。
List<String> list = new LinkedList<String>();
1つの制限は匿名クラスであり、インスタンス化で型パラメータを指定する必要があります。
// This will compile:
Comparator<String> caseInsensitiveComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
// But this will not:
Comparator<String> caseInsensitiveComparator = new Comparator<>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
};
Anonymous Inner Classesでのダイヤモンドの使用はJava 7および8ではサポートされていませんが、Java 9の新機能として含まれます 。
脚注:
1 - <>
使用を「ダイヤモンド演算子 」と呼ぶ人もいます。これは間違っています。ダイヤモンドは演算子として動作しません.JLSやオペレータとしての(公式の)Javaチュートリアルのどこにも記述されていません。確かに、 <>
は別個のJavaトークンでもありません。それはむしろ<
トークンの後に>
トークンであり、2つの間に空白やコメントを入れることは合法です(悪いスタイルですが)。 JLSとチュートリアルでは、 <>
は「ダイヤモンド」と一貫して言及しています。したがって、正しい言葉です。
複数の上限を必要とする(「A&Bを拡張」)
複数の上限を拡張するには、ジェネリック型を必要とすることができます。
例:数値のリストをソートしたいが、 Number
はComparable
実装していない。
public <T extends Number & Comparable<T>> void sortNumbers( List<T> n ) {
Collections.sort( n );
}
この例では、 T
はNumber
を拡張し、 Integer
またはBigDecimal
ようなすべての「通常の」組み込みの数値実装に適合しなければならないが、 Striped64
ようなよりエキゾチックなものには適合しないComparable<T>
を実装Comparable<T>
必要があります。
複数の継承は許可されていないため、最大で1つのクラスをバインドとして使用することができ、最初にリストされている必要があります。たとえば、 <T extends Comparable<T> & Number>
は許可されません。これは、Comparableはクラスではなくインターフェースだからです。
バインドされた汎用クラスの作成
汎用クラスで使用されている有効な型は、その型をクラス定義で囲むことで制限できます。次の単純な型階層を考えます。
public abstract class Animal {
public abstract String getSound();
}
public class Cat extends Animal {
public String getSound() {
return "Meow";
}
}
public class Dog extends Animal {
public String getSound() {
return "Woof";
}
}
限定ジェネリックスがなければ、ジェネリックであり 、各要素が動物であることを知っているコンテナクラスを作ることはできません:
public class AnimalContainer<T> {
private Collection<T> col;
public AnimalContainer() {
col = new ArrayList<T>();
}
public void add(T t) {
col.add(t);
}
public void printAllSounds() {
for (T t : col) {
// Illegal, type T doesn't have makeSound()
// it is used as an java.lang.Object here
System.out.println(t.makeSound());
}
}
}
クラス定義でジェネリックバインドを使用すると、これが可能になりました。
public class BoundedAnimalContainer<T extends Animal> { // Note bound here.
private Collection<T> col;
public BoundedAnimalContainer() {
col = new ArrayList<T>();
}
public void add(T t) {
col.add(t);
}
public void printAllSounds() {
for (T t : col) {
// Now works because T is extending Animal
System.out.println(t.makeSound());
}
}
}
これは、ジェネリック型の有効なインスタンス化も制限します。
// Legal
AnimalContainer<Cat> a = new AnimalContainer<Cat>();
// Legal
AnimalContainer<String> a = new AnimalContainer<String>();
// Legal because Cat extends Animal
BoundedAnimalContainer<Cat> b = new BoundedAnimalContainer<Cat>();
// Illegal because String doesn't extends Animal
BoundedAnimalContainer<String> b = new BoundedAnimalContainer<String>();
`T`、`? 「スーパーT」、「? T 'を延長する
Javaジェネリックのための構文は、未知の型を表すワイルドカードをバインドしてい?
次のとおりです。
? extends T
は上限のあるワイルドカードを表します。未知の型は、Tのサブタイプでなければならない型、またはT型を表します。? super T
は下限のワイルドカードを表します。未知の型は、Tのスーパータイプでなければならない型、またはT型の型を表します。
経験則として、あなたは
-
? extends T
"読み取り"アクセス( "入力")だけが必要な場合? extends T
-
? super T
あなたが "書き込み"アクセス( "出力")を必要とする場合、? super T
- あなたが両方を必要とするならば
T
( "modify")
使用するには、 extends
たりsuper
、あなたが以下を参照してくださいますよう、:それは(サブタイプとスーパータイプの使用が可能のように)あなたのコードはより柔軟になりますので、通常より良いです。
class Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {}
class GrannySmith extends Apple {}
public class FruitHelper {
public void eatAll(Collection<? extends Fruit> fruits) {}
public void addApple(Collection<? super Apple> apples) {}
}
コンパイラは不正な使用状況を検出できるようになります:
public class GenericsTest {
public static void main(String[] args){
FruitHelper fruitHelper = new FruitHelper() ;
List<Fruit> fruits = new ArrayList<Fruit>();
fruits.add(new Apple()); // Allowed, as Apple is a Fruit
fruits.add(new Banana()); // Allowed, as Banana is a Fruit
fruitHelper.addApple(fruits); // Allowed, as "Fruit super Apple"
fruitHelper.eatAll(fruits); // Allowed
Collection<Banana> bananas = new ArrayList<>();
bananas.add(new Banana()); // Allowed
//fruitHelper.addApple(bananas); // Compile error: may only contain Bananas!
fruitHelper.eatAll(bananas); // Allowed, as all Bananas are Fruits
Collection<Apple> apples = new ArrayList<>();
fruitHelper.addApple(apples); // Allowed
apples.add(new GrannySmith()); // Allowed, as this is an Apple
fruitHelper.eatAll(apples); // Allowed, as all Apples are Fruits.
Collection<GrannySmith> grannySmithApples = new ArrayList<>();
fruitHelper.addApple(grannySmithApples); //Compile error: Not allowed.
// GrannySmith is not a supertype of Apple
apples.add(new GrannySmith()); //Still allowed, GrannySmith is an Apple
fruitHelper.eatAll(grannySmithApples);//Still allowed, GrannySmith is a Fruit
Collection<Object> objects = new ArrayList<>();
fruitHelper.addApple(objects); // Allowed, as Object super Apple
objects.add(new Shoe()); // Not a fruit
objects.add(new IPhone()); // Not a fruit
//fruitHelper.eatAll(objects); // Compile error: may contain a Shoe, too!
}
右のT
選ぶ? super T
か? extends T
サブタイプでの使用を可能にするためには、 ? extends T
が必要です。コンパイラは型の安全性を確保できます。適切に使用するとキャストする必要はありません(タイプセーフではなく、プログラミングエラーの原因となります)。
理解しにくい場合は、 PECSのルールを覚えておいてください。
P roducerは「 E xtends」を使用し、 C onsumerは「 S uper」を使用します。
(プロデューサには書き込みアクセスのみ、コンシューマには読み取りアクセスのみがあります)
汎用クラスとインタフェースの利点
ジェネリックスを使用するコードは、非ジェネリックコードよりも多くの利点があります。以下は主な利点です
コンパイル時の型チェックの強化
Javaコンパイラは、ジェネリックコードに強力な型チェックを適用し、コードが型の安全性に違反するとエラーを発行します。コンパイル時のエラーを修正することは、実行時のエラーを修正するよりも簡単です。見つけにくい場合があります。
キャストの排除
ジェネリックスを使用しない次のコードスニペットでは、キャストが必要です。
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
ジェネリックを使用するように書き直すと、コードにはキャストは必要ありません。
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // no cast
プログラマーが汎用アルゴリズムを実装できるようにする
ジェネリックを使用することにより、プログラマは、さまざまなタイプのコレクションで動作し、カスタマイズでき、タイプセーフで読みやすくなりやすい汎用アルゴリズムを実装できます。
1つ以上の型にジェネリックパラメータをバインドする
汎用パラメータは、 T extends Type1 & Type2 & ...
構文を使用して、複数の型にバインドすることもできます。
Flushable
とCloseable
両方を実装するGenericタイプのクラスを作成したいとします。
class ExampleClass<T extends Flushable & Closeable> {
}
これで、 ExampleClass
は、 Flushable
と Closeable
両方を実装するタイプを一般的なパラメータとしてのみ受け入れます。
ExampleClass<BufferedWriter> arg1; // Works because BufferedWriter implements both Flushable and Closeable
ExampleClass<Console> arg4; // Does NOT work because Console only implements Flushable
ExampleClass<ZipFile> arg5; // Does NOT work because ZipFile only implements Closeable
ExampleClass<Flushable> arg2; // Does NOT work because Closeable bound is not satisfied.
ExampleClass<Closeable> arg3; // Does NOT work because Flushable bound is not satisfied.
クラスメソッドは、ジェネリック型の引数をCloseable
またはFlushable
として推論することができます。
class ExampleClass<T extends Flushable & Closeable> {
/* Assign it to a valid type as you want. */
public void test (T param) {
Flushable arg1 = param; // Works
Closeable arg2 = param; // Works too.
}
/* You can even invoke the methods of any valid type directly. */
public void test2 (T param) {
param.flush(); // Method of Flushable called on T and works fine.
param.close(); // Method of Closeable called on T and works fine too.
}
}
注意:
OR ( |
)句を使用して、汎用パラメータをいずれかの型にバインドすることはできません。 AND ( &
)句のみがサポートされています。汎用型は、1つのクラスと多くのインタフェースだけを拡張できます。クラスはリストの先頭に置かなければなりません。
ジェネリック型のインスタンス化
タイプの消去のため、以下は動作しません。
public <T> void genericMethod() {
T t = new T(); // Can not instantiate the type T.
}
タイプT
は消去されます。実行時に、JVMは元々のT
が何であったかを知らないので、どのコンストラクタを呼び出すべきか分からない。
回避策
genericMethod
呼び出すときにT
のクラスをgenericMethod
:public <T> void genericMethod(Class<T> cls) { try { T t = cls.newInstance(); } catch (InstantiationException | IllegalAccessException e) { System.err.println("Could not instantiate: " + cls.getName()); } }
genericMethod(String.class);
渡されたクラスにアクセス可能なデフォルトコンストラクタがあるかどうかを知る方法がないため、例外をスローします。
T
のコンストラクタへの参照を渡す:public <T> void genericMethod(Supplier<T> cons) { T t = cons.get(); }
genericMethod(String::new);
それ自身の宣言内で宣言されたジェネリック型を参照する
メソッド宣言の中で継承されたジェネリック型のインスタンスを、宣言されているジェネリック型自体にどのように使っていますか?これはジェネリックスに少し深く掘り下げたときに直面する問題の1つですが、かなり一般的です。
ここでは、タイプT
値を含む汎用データ系列を定義するDataSeries<T>
型(ここではインタフェース)があるとします。 double型の値で多くの操作を実行する場合は、この型で直接作業するのは面倒です。したがって、 DoubleSeries extends DataSeries<Double>
を定義します。元のDataSeries<T>
型には、同じ長さの別の系列を追加して新しい系列を返すadd(values)
というメソッドがあるとします。どのように派生クラスのDataSeries<Double>
ではなく、 values
の型と戻りvalues
の型をDoubleSeries
するか?
この問題は、宣言されている型を参照して拡張するジェネリック型パラメータを追加することで解決できます(ここではインターフェイスに適用されますが、同じものはクラスを表します)。
public interface DataSeries<T, DS extends DataSeries<T, DS>> {
DS add(DS values);
List<T> data();
}
ここで、 T
は系列が保持するデータ型を表し、例えばDouble
やDS
は系列そのものを表す。上記のパラメータを対応する派生型で置き換えることにより、継承された型(1つまたは複数の型)を簡単に実装できるようになり、具体的なDouble
ベースの次の形式の定義が得られます。
public interface DoubleSeries extends DataSeries<Double, DoubleSeries> {
static DoubleSeries instance(Collection<Double> data) {
return new DoubleSeriesImpl(data);
}
}
現時点では、IDEでも正しいタイプの適切なインターフェイスを実装しています。ちょっとしたコンテンツの埋め込み後には、次のようになります。
class DoubleSeriesImpl implements DoubleSeries {
private final List<Double> data;
DoubleSeriesImpl(Collection<Double> data) {
this.data = new ArrayList<>(data);
}
@Override
public DoubleSeries add(DoubleSeries values) {
List<Double> incoming = values != null ? values.data() : null;
if (incoming == null || incoming.size() != data.size()) {
throw new IllegalArgumentException("bad series");
}
List<Double> newdata = new ArrayList<>(data.size());
for (int i = 0; i < data.size(); i++) {
newdata.add(this.data.get(i) + incoming.get(i)); // beware autoboxing
}
return DoubleSeries.instance(newdata);
}
@Override
public List<Double> data() {
return Collections.unmodifiableList(data);
}
}
ご覧のように、 add
メソッドはDoubleSeries add(DoubleSeries values)
として宣言されており、コンパイラは満足しています。
必要に応じてパターンをさらにネストすることができます。
Genericsでのinstanceofの使用
ジェネリックを使用してinstanceofで型を定義する
仮パラメータ<T>
宣言された次の汎用クラスのExample
考えてみましょう。
class Example<T> {
public boolean isTypeAString(String s) {
return s instanceof T; // Compilation error, cannot use T as class type here
}
}
コンパイラがJavaソースをJavaバイトコードにコンパイルするとすぐに、 タイプ消去と呼ばれるプロセスが適用され、すべてのジェネリックコードが非ジェネリックコードに変換され、実行時にTタイプを区別することが不可能になるため、コンパイルエラーが発生します。 instanceof
使用される型は、 reifiableでなければなりません。 つまり 、型に関するすべての情報を実行時に使用できるようにする必要があります。通常、これはジェネリック型ではありません。
次のクラスは、二つの異なるクラスのものを表すExample
、 Example<String>
とExample<Number>
、ジェネリック医薬品は、 型消去により剥離した後、次のようになります。
class Example { // formal parameter is gone
public boolean isTypeAString(String s) {
return s instanceof Object; // Both <String> and <Number> are now Object
}
}
タイプがなくなったので、JVMがどのタイプがT
かを知ることはできません。
前のルールの例外
次のように、 instanceof
内の型を指定するために、 無制限のワイルドカード (?)をいつでも使用できます。
public boolean isAList(Object obj) {
return obj instanceof List<?>;
}
これは、インスタンスobj
がList
かどうかを評価するのに便利です:
System.out.println(isAList("foo")); // prints false
System.out.println(isAList(new ArrayList<String>()); // prints true
System.out.println(isAList(new ArrayList<Float>()); // prints true
実際、無制限のワイルドカードは、改訂可能な型とみなされます。
instanceofで汎用インスタンスを使用する
硬貨の反対側は、インスタンス使用することであるt
のT
有するinstanceof
、次の例に示すように、合法的です。
class Example<T> {
public boolean isTypeAString(T t) {
return t instanceof String; // No compilation error this time
}
}
タイプの消去後にクラスは次のようになります:
class Example { // formal parameter is gone
public boolean isTypeAString(Object t) {
return t instanceof String; // No compilation error this time
}
}
たとえ型の消去が起こっても、JVMは同じ参照型( Object
)を使用していても、次のスニペットに示すように、メモリ内の異なる型を区別できるようになりました。
Object obj1 = new String("foo"); // reference type Object, object type String
Object obj2 = new Integer(11); // reference type Object, object type Integer
System.out.println(obj1 instanceof String); // true
System.out.println(obj2 instanceof String); // false, it's an Integer, not a String
汎用インターフェースを実装するためのさまざまな方法(または汎用クラスの拡張)
次の汎用インターフェースが宣言されているとします。
public interface MyGenericInterface<T> {
public void foo(T t);
}
以下に、その実装方法を示します。
特定の型を持つ非ジェネリッククラスの実装
次の例のように、 MyGenericClass
の仮型パラメータ<T>
を置き換える特定の型を選択して実装します。
public class NonGenericClass implements MyGenericInterface<String> {
public void foo(String t) { } // type T has been replaced by String
}
このクラスはString
のみを扱うので、 MyGenericInterface
をさまざまなパラメータ( Integer
、 Object
など)で使用すると、次のスニペットが示すようにコンパイルされません。
NonGenericClass myClass = new NonGenericClass();
myClass.foo("foo_string"); // OK, legal
myClass.foo(11); // NOT OK, does not compile
myClass.foo(new Object()); // NOT OK, does not compile
汎用クラスの実装
次のように、 MyGenericInterface
を実装する正式型パラメータ<T>
を使用して、別の汎用インタフェースを宣言します。
public class MyGenericSubclass<T> implements MyGenericInterface<T> {
public void foo(T t) { } // type T is still the same
// other methods...
}
次のように、異なる正式型パラメータが使用されている可能性があることに注意してください。
public class MyGenericSubclass<U> implements MyGenericInterface<U> { // equivalent to the previous declaration
public void foo(U t) { }
// other methods...
}
生の型のクラスの実装
MyGenericInteface
ように、 MyGenericInteface
を実装する非ジェネリッククラスを生の型として宣言します(genericをまったく使用しません)。
public class MyGenericSubclass implements MyGenericInterface {
public void foo(Object t) { } // type T has been replaced by Object
// other possible methods
}
この方法は、(サブクラスの) 生の型を (インターフェイスの) ジェネリックと混ぜて混乱させるため、実行時に100%安全ではないため、お勧めできません 。現代のJavaコンパイラはこの種の実装で警告を発しますが、古いJVM(1.4以前)との互換性のためにコードはコンパイルされます。
汎用インターフェースの代わりにスーパークラスとして汎用クラスを使用する場合は、上記のすべての方法を使用することもできます。
ジェネリックスを使用して自動キャスト
ジェネリックでは、呼び出し側が期待するものをすべて返すことができます。
private Map<String, Object> data;
public <T> T get(String key) {
return (T) data.get(key);
}
メソッドは警告とともにコンパイルされます。 Javaランタイムは、使用時にキャストを行うため、コードは実際には安全です。
Bar bar = foo.get("bar");
ジェネリック型を使用すると安全性が低下します。
List<Bar> bars = foo.get("bars");
ここで、返される型がList
いずれかの種類である場合(つまり、 List<String>
を返すことでClassCastException
トリガーされない場合)、キャストは機能しClassCastException
。
この問題を回避するには、型付きキーを使用するAPIを作成します。
public final static Key<List<Bar>> BARS = new Key<>("BARS");
このput()
メソッドと一緒に:
public <T> T put(Key<T> key, T value);
この方法では、間違った型をマップに入れることができないので、結果は常に正しいでしょう(同じ名前で異なる型の2つのキーを誤って作成しない限り)。
関連:
実行時に汎用パラメータを満たすクラスを取得する
静的メソッドで使用されているような多くのバインドされていない汎用パラメータは、実行時にはリカバリできません(「 消失時のその他のスレッド」を参照)。ただし、実行時にクラスの汎用パラメータを満たす型にアクセスするための共通の戦略があります。これにより、すべての呼び出しを通じて型情報をスレッド化することなく 、型へのアクセスに依存する汎用コードが可能になります。
バックグラウンド
クラスの一般的なパラメータ化は、匿名の内部クラスを作成することによって検査できます。このクラスは型情報を取得します。一般に、このメカニズムはスーパータイプのトークンと呼ばれ、 Neal Gafterのブログ記事に詳しく記載されています。
実装
Javaでの3つの一般的な実装は次のとおりです。
使用例
public class DataService<MODEL_TYPE> {
private final DataDao dataDao = new DataDao();
private final Class<MODEL_TYPE> type = (Class<MODEL_TYPE>) new TypeToken<MODEL_TYPE>
(getClass()){}.getRawType();
public List<MODEL_TYPE> getAll() {
return dataDao.getAllOfType(type);
}
}
// the subclass definitively binds the parameterization to User
// for all instances of this class, so that information can be
// recovered at runtime
public class UserService extends DataService<User> {}
public class Main {
public static void main(String[] args) {
UserService service = new UserService();
List<User> users = service.getAll();
}
}