Java資格取得(OCJP Gold)のためのジェネリクスまとめ
Javaの資格(OCJP Gold)取得シリーズ
mrdshinse.hatenablog.com
Javaの資格(OCJP Gold)取得のための関数型プログラミング(
前編・
後編
)、に続き、
今回はジェネリクス編です。
型パラメータ
以下、目次です。
ジェネリクスって何が嬉しいの?
ジェネリクスとは、処理をするクラスと、処理されるクラスをソースの上で分離できる機能です。
処理(メソッド)とか情報(フィールド)が抽象化できます。
エンプラ系システムを作ってると、DaoクラスとかEntityクラスとかいると思うんですけど、
Dao(DB処理をするクラス)で共通の処理を書きたいけど、
処理する対象のEntity(処理される情報を持つクラス)に依存しないクラスが欲しい
ってこと、あると思います。こんな時に使うのが、
class HogeDao extends BaseDao<HogeEntity>{}
とか、そんなクラス、あると思います。
この時、BaseDaoは型パラメータとしてHogeEntityを受け取っています。
型パラメータの定義
型パラメータとはジェネリクスを利用する上で外せない記法です。
クラスやメソッド単位でどんな型を扱うかをパラメータ(引数)として受け取ります。
型パラメータの宣言はクラス名の後ろに<>をつけて宣言します。
class クラス名<型パラメータ名>{}
です。
以下は型パラメータ名をTとして
- フィールド
- コンストラクタ
- 戻り値
- 引数
で型パラメータを利用するサンプルです。
public class DataHolder<T> { private T data; public DataHolder(T data){ this.data = data; } public T getData(){ return data; } public void setData(T data){ this.data = data; } }
上記のクラスを使う場合は、
クラス名<パラメータとして渡す型>
で処理されるクラスの型を書きます。
DataHolder<String> strHolder = new DataHolder<String>("hoge"); String strData = strHolder.getData(); DataHolder<Integer> intHolder = new DataHolder<>(123);//代入するインスタンス側はダイヤモンド演算子で省略もできます。(Java7+) Integer intData = intHolder.getData();
処理をするクラス(DataHolder)には、処理されるクラス(StringとかInteger)の型がでてこないのに、戻り値が変化していることがわかります。
また、型パラメータで渡した型と異なる型の操作はコンパイル時にエラーとなります。
intHolder.setData("123");//コンパイルエラー。
テスト対策で知っておきたいこと(型パラメータ)
型パラメータは
抽象クラスでも利用できますし、インターフェースでも同様に
interface インターフェース名<型パラメータ名>{}
で型パラメータが定義できます。
また、メソッド単位で型引数を定義することもできて、この場合は戻り値の前に型パラメータを定義します。
(static)(abstract) <型パラメータ名> 戻り値 メソッド名(引数);
静的メソッド、抽象メソッドでもOKですし、
ただのメソッドでも定義できます。(ここ結構忘れがち)
ワイルドカード型と上限型、下限型
Listは型パラメータをもつので、ジェネリクスで格納する値を指定できます。
List<String> strList = new ArrayList<String>();
代入をする場合は、宣言している型と同じであることを<>で表現できます。<>がダイヤモンドみたいなんでダイヤモンド演算子と呼ぶらしいですw
List<Integer> intList = new ArrayList<>();//new ArrayList<Integer>();と同じ。 strList = new ArrayList<>();//strListは上でList<String>で宣言されているので、new ArrayList<String>();と同じ。
型パラメータの指定をすると、指定した型以外の代入やaddはできなくなります。
strList = new ArrayList<Integer>();//コンパイルエラー strList.add(1);//コンパイルエラー
?はワイルドカード型と呼ばれ、型パラメータとして指定することでどんな型でも代入できるようになります。
List<?> list; list = new ArrayList<String>(); list = new ArrayList<Integer>();
しかし、ワイルドカード型を利用するとaddはできなくなります。
list.add("1");//コンパイルエラー list.add(1);//コンパイルエラー list.add(new Object());//コンパイルエラー
また、ワイルドカード型を利用するとgetの戻り値はObjectになります。
Object value = list.get(0); Integer i = list.get(0);//コンパイルエラー
ワイルドカード型には上限型・下限型を指定できます。
上限型を指定する場合は、? extends クラス名と書きます。
こんなクラスがいたとします。
class Parent {} class Child extends Parent {} class GrandChild extends Child {}
以下は?はParentが先祖ですよという意味。
List<? extends Parent> up2ParentList; up2ParentList = new ArrayList<Parent>(); up2ParentList = new ArrayList<Child>(); up2ParentList = new ArrayList<>();//ダイヤモンド演算子も指定できます。 up2ParentList = new ArrayList<String>();//コンパイルエラー。Stringは先祖にParentがいないので。
しかし、ワイルドカード型の指定なのでaddは相変わらずできません。
up2ParentList.add(new Child());//コンパイルエラー。ChildはParentを継承していますがaddできません。 up2ParentList.add(new Parent());//コンパイルエラー。Parent自身もaddできません。
?のみ指定の場合と異なるのは、戻り値の型がObjectではなく上限型に指定した値となることです。
Parent p = up2ParentList.get(0);
下限型を指定する場合は、? super クラス名と書きます。
以下は、?はChildの先祖ですよという意味。
List<? super Child> down2ChildList; down2ChildList = new ArrayList<Parent>(); down2ChildList = new ArrayList<Child>(); down2ChildList = new ArrayList<GrandChild>();//コンパイルエラー。GrandChildはChildに継承されていないので。 down2ChildList = new ArrayList<>();//ダイヤモンド演算子も指定できます。 down2ChildList = new ArrayList<String>();//コンパイルエラー。StringはChildの先祖でないので。
上限型と異なる点はaddができる点です。ただし、下限型としてaddされているのか、下限型の親クラスは代入できないようです。。。
down2ChildList.add(new Parent());//ArrayList<Parent>()を代入できるのに、add(new Parent())できないのはわかりづらい。 down2ChildList.add(new Child()); down2ChildList.add(new GrandChild());
しかし、上限型では上限型で戻り値が取得できましたが、下限型では?のみと同様にObjectしか返せません。
Object o = down2ChildList.get(0); Child c = down2ChildList.get(0);//コンパイルエラー。
ワイルドカードややこしや。
参考
Java8 Goldの試験対策本(日本語)はこちら。
英語だけどとても丁寧なテキストはこちら