SlideShare a Scribd company logo
ジェネリクスの基礎と
クラス設計への応用 2013 年版

Twetter : @nagise
はてな : Nagise
所属

  java-ja

 北陸エンジニアグループ
導入
Java 1.4 までのコード
ArrayList list = new ArrayList();
list.add("hoge");
String s = (String) list.get(0);
ジェネリクスのない世界
 Object 型からのダウンキャストが必要
 キャスト失敗は実行時例外
ClassCastException
 動かしてみないと間違いに気づかない
 ときに動かしても気づかない
 ドキュメントなどで型を明示
導入
Java 1.5 からのコード
ArrayList<String> list =
new ArrayList<String>();
list.add("hoge");
String s = list.get(0);
ジェネリクス以後の世界
 コレクション API でダウンキャストが不
要に
 コンパイル時点で型の間違いに気づく
 IDE によってはコンパイル前に気づく
 型システムが型を把握してくれる
 必修事項が増えました
今日のキーワード

Generics Hell
じぇねりくす へる
日本風に言えば「まったく Java のジェネリクスは地獄だぜ!」
略して「ヘル - い」などと表現したりする
入門編

 ジェネリックな API を利用するのに必要
な知識を身に着ける
 2種類のスコープ
 3種の山括弧
 代入について
ジェネリクスの2種類のスコー
プ
メソッドの中でのみ有効なジェネリクス
public static <T> void hoge(T t) { … }
インスタンスの中でのみ有効なジェネリク
ス
class Hoge<T> {
Tt;
...
}
ジェネリックメソッド

メソッドの引数と戻
り値の型の関係を表
す


メソッドの複数の引



数の型の関係を表す
ジェネリックメソッドの例
java.util.Collections クラス
リスト内に出現する指定された値を
すべてほかの値に置き換えます。
public static <T> boolean replaceAll
(List<T> list, T oldVal, T newVal)

3つの引数が List<T> 型、 T 型、 T 型という関係性
ジェネリックメソッドの例
java.util.Collections クラス
指定されたオブジェクトだけを格納している
不変のセットを返します
public static <T> Set<T> singleton(T o)
引数が T 型で、戻り値が Set<T> 型という関係性
ジェネリックメソッドの例
java.util.Collections クラス
デフォルトの乱数発生の元を使用して、
指定されたリストの順序を無作為に入れ替えます。
public static void shuffle(List<?> list)
引数1つだけなので関連性を示す必要がない。
無駄に型変数を定義せず、ワイルドカードを利用して
List<?> 型として宣言するほうが使い勝手が良い
ジェネリックメソッドの呼び出
し方
List<String> list = new ArrayList<String>();
list.add("hoge");
Collections.<String>replaceAll(list, "hoge", "piyo");
List<Integer> intList = new ArrayList<Integer>();
intList.add(42);
Collections.<Integer>replaceAll(intList, 42, 41);
static の場合はクラス名 .< バインド型 > メソッド名 (…)
インスタンスメソッドの場合は変数名 .< バインド型 > メソッド名
(…)
this に対するメソッド呼び出し時、明示的にバインドしたい場合は
this を省略できない
インスタンスの I/O
複数のメソッ
ド間の引数・戻
り値の型の関係
性


公開されてい



るフィールド
内部クラス


ジェネリックなインスタンスの
例
java.util.ArrayList の例
public boolean add(E e)
public E get(int index)
複数のメソッド間で型の関連性を表現している
ジェネリクスと構造化

ジェネリクスメソッド、ジェネリッククラスとも
に
ある囲いを作って、その内外のやりとりを
する場合のデータ型を型変数で抽象化する。
そのため、ジェネリックな設計をするためには
前提としてきれいな構造化をする必要がある。
文法のはなし
public class Syntax<T>
implements Iterable<String> {
public <X> void hoge(X x) {
List<X> list = new ArrayList<X>();
list.add(x);
}
@Override
public Iterator<String> iterator() {
return null;
}
}
似て非なる<>を色分けしてみました
3種類の<>
型変数の宣言
class Hoge<T> {}
public <T> void hoge() {}


型変数のバインド



new ArrayList<String>();
class Hoge extends ArrayList<String> {}
Collections.<String>replaceAll(
list, "hoge", "piyo");

パラメータ化された型
( parameterized type )
List<String> list;

型のバインド
ジェネリックメソッドの例
宣言側 仮型引数( type-parameter )
public static <T> boolean replaceAll
(List<T> list, T oldVal, T newVal)
利用側 実型引数( type-argument )
Collections.<String>replaceAll(
list, "hoge", "piyo");
型のバインド
ジェネリッククラスの例
宣言側 仮型引数( type-parameter )
public class ArrayList<E> {...}
利用側 実型引数( type-argument )
List<String> list = new ArrayList<String>();
( 参考 ) 仮引数と実引数
メソッドの仮引数と実引数との対比
宣言側 仮引数( parameter )
public void hoge(int index){...}
利用側 実引数( argument )
hoge(123);
型推論
ジェネリックメソッド
正書法
Collections.<String>replaceAll(
list, "hoge", "piyo");

型推論
Collections.replaceAll(
list, "hoge", "piyo");
ダイヤモンド演算子
ジェネリッククラス
正書法
List<String> list = new ArrayList<String>();

ダイヤモンド演算子
List<String> list = new ArrayList<>();
推論器
ジェネリックメソッドの型推論と
ダイヤモンド演算子は同じ機構を利用し
ている
Java7 では推論が弱いが、 Java8 ではラ
ムダのために
型推論が強化された
継承によるバインド
public class StringList
extends ArrayList<String> {}
というクラスを作ると
StringList list = new StringList();
list.add("hoge");
String str = list.get(0);
といったように、型変数のないクラスになる
複雑になる前に

Map<String, Map<Integer, List<Hoge>>>
みたいにジェネリクスが複雑化するようなら
適度なレベルでした継承クラスを作ることで
シンプルでわかりやすくなる
クラスを作るのをサボらない
サンプルのクラス
パラメータ化された型の代入互換
性
B[] arrayB = new B[1];
A[] arrayA = arrayB;
arrayA[0] = new B2();
→ ArrayStoreException が発生
List<B> listB = new ArrayList<B>();
List<A> listA = listB;
listA.add(new B2());
→ コンパイルエラー
異なる代入互換性
B の集合型 ArrayList<B> は
A の集合型 ArrayList<A> の
代理をできない
ワイルドカードの使用
ArrayList<? extends B> の範囲

ArrayList<? super B> の範囲
<? extends ~ > の入力制約
List<? extends B> には add() できない


List<C> 型を代入



B 型を add() とする



List<C> に B 型が add() される ←矛盾



get() は B 型の戻り値を返す
<? super ~ > の出力制約
ArrayList<? super B> には
B 型を add() できる



ただし get() は Object 型としてしか返せな
い

まとめ
型変数でメソッドやインスタンスの I/O の
型の関連性を表す
→ まずは綺麗なオブジェクト指向の設計を
文法をマスターするには
3種類の<>を意識する
代入互換性は訓練あるのみ
中級編
 ジェネリックな API を設計するのに必要
な知識を身に着ける
 型変数の宣言
 再帰的ジェネリクス
 内部クラス
 リフレクション
型変数の境界
class Hoge<T extends B>
型変数の宣言時には境界を指定できる
new Hoge<A>(); ←
new Hoge<B>(); ←
new Hoge<B2>(); ←
new Hoge<C>(); ←

NG
OK
NG
OK
型変数の境界

class Hoge <T extends A & Serializable>
& で繋いでインタフェースを
境界に加えることができる
再帰ジェネリクス

public abstract class
Hoge<T extends Hoge<T>> {
public abstract T getConcreteObject();
}
Hoge 型の型変数 T は Hoge 型を継承していなくてはなら
ない
再帰する型変数へのバインド

new でバインドできない
Hoge<?> hoge = new Hoge<Hoge<...>>();
再帰ジェネリクスは継承で扱う
public class Piyo extends Hoge<Piyo> {...}
再帰ジェネリクスの効能
public class Piyo extends Hoge<Piyo> {
@Override
public Piyo getConcreteObject() {
return this;
}
}
このようにすると
Piyo piyo = new Piyo();
Piyo piyo2 = piyo.getConcreteObject();
再帰ジェネリクスの効能

public abstract class
Hoge<T extends Hoge<T>> {
public abstract T getConcreteObject();
}
親クラスで定義されたメソッドなのに
子クラスの具象型を扱うことができる…!
再帰ジェネリクスの使用例
java.lang.Enum クラスの例
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable
{
public final int compareTo(E o) {…}
}
再帰ジェネリクスの使用例
enum SampleEnum {
ONE,
TWO,
}
に対して
SampleEnum.ONE.compareTo(SampleEnum.TWO);
は安全に compareTo できる。
他の enum と比較しようとするとコンパイルエラー
内部クラスのジェネリクス

内部クラスは外部クラスの型変数を利用できる
public class Outer<T> {
public class Inner {
T genericField;
}
}
内部クラスの new の仕方
内部クラスの new の仕方知ってますか?
Outer<String> outer = new Outer<String>();
Outer<String>.Inner inner = outer.new Inner();
内部クラスは外部クラスのインスタンスに紐づく。
ジェネリクスは「インスタンスの I/O で型の関連性を示
す」
の I/O には内部クラスも含まれます
内部クラスの利用例
public class SampleList<T> extends ArrayList<T> {
@Override
public Iterator<T> iterator() {
return super.iterator();
}
public class SampleIterator
implements Iterator<T> {
// 略
}
}
そもそも内部クラスの使いどころが難しいですが。
リフレクション
• 型変数に格納されるインスタンスの型情
報は欠落する(イレイジャ方式)
• メソッド引数やフィールド宣言に用いら
れるパラメタライズド・タイプの型情報
は欠落しない
http://d.hatena.ne.jp/Nagise/20121226/1356531878
Java のジェネリクスとリフレクション
リフレクション
java.lang.reflect.Type を利用
実装クラス / サブ interface
•Class – いつもの
•GenericArrayType – ジェネリックな配列。きも
い
•ParameterizedType – パラメタライズドタイプ
•TypeVariable<D> - 型変数
•WildcardType – ワイルドカード
ダウンキャストして使う
リフレクション
java.lang.reflect.Type を返すメソッドを利用する
•Class # getGenericSuperclass() : Type
•Class # getGenericInterfaces() : Type[]
•Method # getGenericParameterTypes() : Type[]
•Method # getGenericReturnType() : Type
•Field # getGenericType() : Type
などなど
まとめ
ジェネリクスが複雑になりすぎないように
継承でのバインドの利用を検討する
再帰ジェネリクスを応用すれば
サブクラスで具象型を扱える
内部クラスもスコープ範囲内
リフレクションでフィールドやメソッド引
数・戻り値のパラメタライズドタイプの型
をとれる
上級編

 ジェネリックな API の設計例
 利用側のコードとともによい設計を考え
る
new T() したい
動機
  DB のマッパーや Web システムの
Controller のようなフレームワークを作っ
た場合、
取り扱うオブジェクトの型をジェネリクス
で定義し、インスタンスを生成してデータ
をマッピングして渡したい
コンストラクタ
Java のオブジェクト指向の一般論おさらい
interface や親クラスとして
振る舞うことが要求される
具象型の特有の情報を
押し込める場所はコンストラクタ
コンストラクタ

コンストラクタの引数の形は継承で
制約できません
やりたければ Factory クラス作れ
一般にインスタンス生成は抽象化しにくい
Factory の実装例
interface HogeFactory<T extends A> {
/** デフォルトコンストラクタ的な */
T newInstance();
}
インスタンスの生成に必要なデータを Factory で制約
public class HogeTemplate {
public <T> T template(HogeFactory<T> factory)
{
return factory.newInstance();
}
}
こうすればインスタンスの生成は可能になる、が面倒
妥協例
public <T extends A> T template(T obj) {
try {
return (T)obj.getClass().newInstance();
} catch (InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public <T extends A> T template(Class<T> clazz) {
try {
return (T)clazz.newInstance();
} catch (InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
デフォルトコンストラクタがあることが前提
C# の例

class MyGenericClass<T> where T : new() {}
T 型にデフォルトコンストラクタがあることという制約。
デフォルトコンストラクタがあることを保証させることで
インスタンス生成を可能とする。
この制約はダサい制約ではあるが妥当な妥協点か。
Java でもリフレクションで生成する場合に
デフォルトコンストラクタがあることという制限をつけがち。
継承によるバインドの場合
Class # getGenericSuperclass() という福音
継承によるバインドであれば、型情報から型変数に何がバ
インドされたかを知ることができる
Type を ParameterizedType にキャスト
getActualTypeArguments() でバインドされた実体
実体の Type を Class にキャストして newInstance()
ただし、デフォルトコンストラクタがあるものとする
http://d.hatena.ne.jp/Nagise/20130815
ジェネリックなデザインパター
ン
Template Method パターン
虫食いのテンプレートを作る。
左図の処理 ABC は abstract メソッド。
サブクラスではオーバーライドして実装を書く。
ここに先の継承によるバインドと
バインドされた型のリフレクションでの
newInstance() を複合すると便利

処理 A
処理 B
処理 C
まとめ

New T() したくなるシチュエーションには
継承によるバインド+リフレクション
を使えないか検討する
Template nethod パターンと複合させると
フレームワークに応用できるかも
変態編

 何かに使えるかもしれない
再帰での相互参照
二つの型の具象型が、相互に相手の具象型を知っている
class Hoge<H extends Hoge<H, P>,
P extends Piyo<H, P>>
class Piyo<H extends Hoge<H, P>,
P extends Piyo<H, P>>
実装は
class HogeImpl extends Hoge<HogeImpl, PiyoImpl>
class PiyoImpl extends Piyo<HogeImpl, PiyoImpl>
相互再帰+1
汎用型変数 T を追加してみる
class Hoge<T, H extends Hoge<T, H, P>,
P extends Piyo<T, H, P>>
class Piyo<T, H extends Hoge<T, H, P>,
P extends Piyo<T, H, P>>
実装クラス
class HogeImpl<T> extends
Hoge<T, HogeImpl<T>, PiyoImpl<T>>
class PiyoImpl<T> extends
Piyo<T, HogeImpl<T>, PiyoImpl<T>>
やりすぎです
内部クラスでグルーピング
二つのクラスを囲うクラスを作って
Hoge と Piyo を内部クラスにすれば…!
public abstract class Outer
<H extends Outer<H, P>.Hoge,
P extends Outer<H, P>.Piyo> {
public abstract class Hoge {
public abstract P getConcretePiyo();
}
public abstract class Piyo {
public abstract H getConcreteHoge();
}
}
やりすぎです
型変数の部分適用
複数の型変数がある型を2段階に分けてバインド

public class Outer<K> {
public class Inner<V> extends HashMap<K, V> {}
}
Outer<String> o = new Outer<String> ();
HashMap<String, Integer> inner =
o. new Inner<Integer>();
応用例

型変数の部分適用

http://d.hatena.ne.jp/Nagise/20110124/1295874192
Java による高階型変数の実装
public class Hoge extends KeyGroup<Hoge> {
private static final Hoge singleton = new Hoge();
public static final Hoge.Key<String>
HOGE_STRING = singleton.new Key<String>();
}
public class KeyValue<KG extends KeyGroup<KG>> {
public <T> void put(KeyGroup<KG>.Key<T> key, T value){}
}
KeyValue<Hoge> tm = new KeyValue<Hoge>();
tm.put(Hoge.HOGE_STRING, "hoge");
より深く知りたい場合の資料
当セッションは主に blog に記述した事項を再編集
してまとめたものである。
Blog プログラマーの脳みそ
カテゴリー Generics を参照
http://d.hatena.ne.jp/Nagise/searchdiary?word=
%2A%5BGenerics%5D
Generics Hell に遭遇したら Twitter で
@nagise にご一報ください。

More Related Content

ジェネリクスの基礎とクラス設計への応用