ジェネリクス(Generics)とは
- 任意の型を受け付けるクラス・メソッドに対して特定の型を割り当てて、型専用のクラスを生成する機能
- これでメンバー要素の型をコンパイル時に保証できる
- ジェネリック型を定義する
class クラス名<型パラメータ,...>{クラス}
- ex:型パラメータEを宣言
public class ArrayList<E> extends AbstractList<E>{
-
型パラメータ:特定の型を受け取るための宣言<...>の部分
- インタンスにに任意型を割り当てる
- T(Type)・E(Element)・K(Key)、V(Value)
-
型変数:型パラメータ内で宣言された型のこと
- インスタンスメンバーの引数/戻り値
- ローカル変数の型
- ネストした型
- ジェネリクスではインスタンス化の際に、ArrayListが生成される。クラスメンバーの型としては使えない
- イレイジャ(Erasure):このように実行時に不要な型情報をコンパイル時にObject型に変換すること
-
実型パラメータ(型引数):ジェネリック型をインスタンス化する際に指定する具体的な型のこと(Integerなど)
new ArrayList**<Integer>**:
-
パラメータ化された型:型引数で特定の型が割り当てられたジェネリック型のこと(ArrayListなど)
new **ArrayList<Integer>**:
型パラメータの制約
-
メソッドがある引数をもつ前提にしたい
- 下記の例では、T型の変数x,yが必ずcompareToメソッドを持つとは限らないのでエラーになる
- compareToメソッドを持つ=Comparableインターフェースを実装するString,Integer,Fileなどのクラス
//エラー
public class GenericConstraint <T> {
public int Hoge(T x, T y) {
return x.compareTo(y);
}
}
- →型パラメータで制約(境界型)を付与
<型パラメータ extends 境界型, ..>
- ingBuilder境界型を指定された型パラメータは、コンパイル時にObject型ではなく境界型に変換される
- 以下のようにComparableインターフェースを実装したString型などはGenericConstraintクラスに渡せる
//compareToメソッドを認識できる例
public class GenericConstraint <T extends Comparable<T>> {
public int Hoge(T x, T y) {
return x.compareTo(y);
}し
}
- 境界型はクラス/インターフェース指定可能
- implementsでなくextends
- インターフェースは複数指定可能
- クラスは1つのみ(もしくは0)
- クラス→インターフェースの順で記述
public class MyGeneric <T extends Hoge & Bar $ Foo> {
ジェネリックメソッド・コンストラクタ
- 引数/戻り値・ローカル変数などの型を呼び出し時に決められるメソッド、コンストラクタのこと
- ジェネリックメソッドはジェネリック型とは独立なので非ジェネリック型でも定義可能
[修飾子]<型パラメータ>戻り値型 メソッド名(引数の型 引数名, ...)
[throw句]{
メソッド本体
}
境界ワイルドカード型
-
共変(covariant):配列では派生クラスを基底クラスに代入できる
Parent/Childに継承関係がある場合Child[]はParent[]に代入できるObject[] data = new String[5];
- Object/String には継承関係があるのでObject型入れるにString配列は代入可能
-
data[1] = 10;
のdataの実態はString[]なのでArrayStoreException
-
不変(invariant):ジェネリクスのコレクション
- Parent/Childに継承関係があってもArrayListはArrayListに代入できない
-
ArrayList<Object> data = new ArrayList<String>();
//エラー
//NG例
import java.util.List;
public class GenericBounded {
//List<Parent>型のリストを受け取り出力
public void show(List<Parent> list) {
for (var p : list) {
System.out.println(p.getName());
}
}
}
import java.util.ArrayList;
import java.util.Arrays;
public class GenericBoundedBasic {
public static void main(String[] args) {
var cli = new GenericBounded();
var data1 = new ArrayList<Parent>(Arrays.asList(
new Parent("かえる"), new Parent("にわとり"), new Parent("とんぼ")));
var data2 = new ArrayList<Child>(Arrays.asList(
new Child("おたまじゃくし"), new Child("ひよこ"), new Child("やご")));
cli.show(data1); //OK
//List<Parent>型の引数にList<Child>型の値は渡せない
cli.show(data2); //コンパイルエラー
}
}
- showはリストの中身を参照するメソッドなので値の代入は発生しない
- →境界ワイルドカードでParent型、またはその派生型を認める(共変)
- List型の引数にList型を渡せるようになる
- 要素の追加、更新はできない
- 境界ワイルドカード型では境界型で値を参照できることのみ
import java.util.List;
public class GenericBounded {
//境界ワイルドカード
public void show(List<? extends Parent> list) {
for (var p : list) {
System.out.println(p.getName());
}
}
}
標準ライブラリの例
ArrayList.java
public boolean addAll(Collection<? extends E> c){
Object[] a = c.toArray();
//略
}
-
addAll(Collection<E> c
のように定義されていたら、ArrayList型に対しArrayList,ArrayList型などの値をaddAllすることはできない - 境界ワイルドカードを使うことで引数として渡すリストの要素型はCharSequence/その派生型を許容できる
下限境界ワイルドカード型
- 上限境界ワイルドカード:指定境界型を上限、その下位型も認める(<? extends E>)
- 下限境界ワイルドカード:枷型に対して基底型を渡せる=反変(<? super T>)
- addAllメソッドでは、下限境界ワイルドカード型
Collection<? super T>
で、型Tもしくはその上位型を要素に持つコレクションを渡せる- addAllに下限境界ワイルドカード型がなかったらString型要素の追加には
ArrayList<String>
でないといけなかった
- addAllに下限境界ワイルドカード型がなかったらString型要素の追加には
-
PECS原則:Producer-Extends,Consumer-Super
- 値を取得するだけの引数(生産者)の時は上限境界ワイルドカード
- 値を設定するだけの引数(消費者)の時は下限境界ワイルドカード
java.util.Collections.addAll.java
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
//ArrayList<Object>に対してString型の要素の追加を許容
import java.util.ArrayList;
import java.util.Collections;
public class LowerBoundedBasic {
public static void main(String[] args) {
var list = new ArrayList<Object>();
Collections.addAll(list, "neko", "inu", "tori");
System.out.println(list); //[neko, inu, tori]
}
}
非境界ワイルドカード型
- 境界型を持たない
-
List<?>
ように指定 - 注意:型安全を保証するためには
- Tを引数として受け取るメソッドを呼び出せない
- メソッドの戻り値となるTは全部object型
import java.util.List;
public class UnBounded {
public static void showList(List<?> list) {
for (var item : list) {
System.out.println(item);
}
//list.add("Hoge");//型が決まってないので値の引き渡し不可
//list.add(null); //例外的にnullはOK
// Object obj = list.get(0); //取得した結果はString型ではなくObject型!!
}
}