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

66
ジジジジジジジジジジ ジジジジジジジジジ Twetter : @nagise ジジジ : Nagise ジジ java-ja ジジジジジジジジジジジ 2013 ジジ

description

JJUG CCC 2013 Fall #ccc_h1

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

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

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

Twetter : @nagiseはてな : Nagise

所属  java-ja  北陸エンジニアグループ

2013年版

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

導入

Java 1.4までのコード

ArrayList list = new ArrayList();

list.add("hoge");

String s = (String) list.get(0);

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

ジェネリクスのない世界

Object型からのダウンキャストが必要キャスト失敗は実行時例外 ClassCastException

動かしてみないと間違いに気づかないときに動かしても気づかないドキュメントなどで型を明示

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

導入

Java 1.5からのコード

ArrayList<String> list =

new ArrayList<String>();

list.add("hoge");

String s = list.get(0);

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

ジェネリクス以後の世界

コレクション APIでダウンキャストが不要に

コンパイル時点で型の間違いに気づく IDEによってはコンパイル前に気づく型システムが型を把握してくれる必修事項が増えました

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

今日のキーワード

Generics Hellじぇねりくす へる

日本風に言えば「まったく Javaのジェネリクスは地獄だぜ!」

略して「ヘル -い」などと表現したりする

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

入門編

ジェネリックな APIを利用するのに必要な知識を身に着ける

2種類のスコープ3種の山括弧代入について

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

ジェネリクスの2種類のスコープメソッドの中でのみ有効なジェネリクス

public static <T> void hoge(T t) { … }

インスタンスの中でのみ有効なジェネリク

class Hoge<T> {

T t ;

...

}

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

ジェネリックメソッド

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

数の型の関係を表す

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

ジェネリックメソッドの例

java.util.Collections クラス

リスト内に出現する指定された値をすべてほかの値に置き換えます。

public static <T> boolean replaceAll(List<T> list, T oldVal, T

newVal)

3つの引数が List<T> 型、 T 型、 T型という関係性

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

ジェネリックメソッドの例

java.util.Collections クラス

指定されたオブジェクトだけを格納している不変のセットを返します

public static <T> Set<T> singleton(T o)

引数が T型で、戻り値が Set<T>型という関係性

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

ジェネリックメソッドの例

java.util.Collections クラス

デフォルトの乱数発生の元を使用して、指定されたリストの順序を無作為に入れ替えます。

public static void shuffle(List<?> list)

引数1つだけなので関連性を示す必要がない。無駄に型変数を定義せず、ワイルドカードを利用してList<?>型として宣言するほうが使い勝手が良い

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

ジェネリックメソッドの呼び出し方

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を省略できない

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

インスタンスの I/O

複数のメソッド間の引数・戻り値の型の関係性公開されてい

るフィールド内部クラス

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

ジェネリックなインスタンスの例

java.util.ArrayListの例

public boolean add(E e)public E get(int index)

複数のメソッド間で型の関連性を表現している

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

ジェネリクスと構造化

ジェネリクスメソッド、ジェネリッククラスともにある囲いを作って、その内外のやりとりをする場合のデータ型を型変数で抽象化する。

そのため、ジェネリックな設計をするためには前提としてきれいな構造化をする必要がある。

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

文法のはなし

public class Syntax<T>implements Iterable<String> {

public <X> void hoge(X x) {List<X> list = new ArrayList<X>();list.add(x);

}@Overridepublic Iterator<String> iterator() {

return null;}

}似て非なる<>を色分けしてみました

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

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;

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

型のバインド

ジェネリックメソッドの例

宣言側 仮型引数( type-parameter)

public static <T> boolean replaceAll(List<T> list, T oldVal, T

newVal)

利用側 実型引数( type-argument)

Collections.<String>replaceAll(list, "hoge", "piyo");

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

型のバインド

ジェネリッククラスの例

宣言側 仮型引数( type-parameter)

public class ArrayList<E> {...}

利用側 実型引数( type-argument)

List<String> list = new ArrayList<String>();

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

(参考 ) 仮引数と実引数

メソッドの仮引数と実引数との対比

宣言側 仮引数( parameter)

public void hoge(int index){...}

利用側 実引数( argument)

hoge(123);

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

型推論

ジェネリックメソッド

正書法

Collections.<String>replaceAll(list, "hoge", "piyo");

型推論

Collections.replaceAll(list, "hoge", "piyo");

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

ダイヤモンド演算子

ジェネリッククラス

正書法

List<String> list = new ArrayList<String>();

ダイヤモンド演算子

List<String> list = new ArrayList<>();

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

推論器

ジェネリックメソッドの型推論とダイヤモンド演算子は同じ機構を利用している

Java7では推論が弱いが、 Java8ではラムダのために型推論が強化された

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

継承によるバインド

public class StringList extends ArrayList<String> {}

というクラスを作ると

StringList list = new StringList();list.add("hoge");String str = list.get(0);

といったように、型変数のないクラスになる

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

複雑になる前に

Map<String, Map<Integer, List<Hoge>>>

みたいにジェネリクスが複雑化するようなら適度なレベルでした継承クラスを作ることでシンプルでわかりやすくなる

クラスを作るのをサボらない

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

サンプルのクラス

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

パラメータ化された型の代入互換性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());

→ コンパイルエラー

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

異なる代入互換性

B  の集合型 ArrayList<B>は

A  の集合型 ArrayList<A>の

代理をできない

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

ワイルドカードの使用

ArrayList<? extends B> の範囲

ArrayList<? super B> の範囲

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

<? extends ~ >の入力制約List<? extends B>には add()できない List<C>型を代入 B型を add()とする List<C>に B型が add()   ←される 矛盾

get()は B型の戻り値を返す

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

<? super ~ >の出力制約

ArrayList<? super B> にはB型を add()できる

ただし get()は Object型としてしか返せない

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

まとめ

型変数でメソッドやインスタンスの I/Oの型の関連性を表す→ まずは綺麗なオブジェクト指向の設計を

文法をマスターするには3種類の<>を意識する

代入互換性は訓練あるのみ

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

中級編

ジェネリックな APIを設計するのに必要な知識を身に着ける

型変数の宣言再帰的ジェネリクス内部クラスリフレクション

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

型変数の境界

class Hoge<T extends B>

型変数の宣言時には境界を指定できる

new Hoge<A>(); ← NGnew Hoge<B>(); ← OKnew Hoge<B2>(); ← NGnew Hoge<C>(); ← OK

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

型変数の境界

class Hoge <T extends A & Serializable>

& で繋いでインタフェースを境界に加えることができる

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

再帰ジェネリクス

public abstract class Hoge<T extends Hoge<T>> {

public abstract T getConcreteObject();}

Hoge型の型変数 Tは Hoge型を継承していなくてはならない

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

再帰する型変数へのバインド

new でバインドできない

Hoge<?> hoge = new Hoge<Hoge<...>>();

再帰ジェネリクスは継承で扱う

public class Piyo extends Hoge<Piyo> {...}

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

再帰ジェネリクスの効能

public class Piyo extends Hoge<Piyo> {@Overridepublic Piyo getConcreteObject() {

return this;}

}

このようにすると

Piyo piyo = new Piyo();Piyo piyo2 = piyo.getConcreteObject();

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

再帰ジェネリクスの効能

public abstract class Hoge<T extends Hoge<T>> {

public abstract T getConcreteObject();}

親クラスで定義されたメソッドなのに…子クラスの具象型を扱うことができる !

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

再帰ジェネリクスの使用例

java.lang.Enum クラスの例

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

public final int compareTo(E o) {…}}

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

再帰ジェネリクスの使用例

enum SampleEnum {ONE,TWO,

}

に対して

SampleEnum.ONE.compareTo(SampleEnum.TWO);

は安全に compareToできる。他の enumと比較しようとするとコンパイルエラー

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

内部クラスのジェネリクス

内部クラスは外部クラスの型変数を利用できる

public class Outer<T> {public class Inner {

T genericField;}

}

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

内部クラスの newの仕方

内部クラスの newの仕方知ってますか?

Outer<String> outer = new Outer<String>();Outer<String>.Inner inner = outer.new Inner();

内部クラスは外部クラスのインスタンスに紐づく。ジェネリクスは「インスタンスの I/Oで型の関連性を示す」の I/Oには内部クラスも含まれます

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

内部クラスの利用例

public class SampleList<T> extends ArrayList<T> {

@Overridepublic Iterator<T> iterator() {

return super.iterator();}

public class SampleIteratorimplements Iterator<T> {// 略

}}

そもそも内部クラスの使いどころが難しいですが。

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

リフレクション

• 型変数に格納されるインスタンスの型情報は欠落する(イレイジャ方式)

• メソッド引数やフィールド宣言に用いられるパラメタライズド・タイプの型情報は欠落しない

http://d.hatena.ne.jp/Nagise/20121226/1356531878Javaのジェネリクスとリフレクション

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

リフレクション

java.lang.reflect.Type を利用

実装クラス /サブ interface•Class – いつもの•GenericArrayType – ジェネリックな配列。きもい•ParameterizedType – パラメタライズドタイプ•TypeVariable<D> - 型変数•WildcardType – ワイルドカード

ダウンキャストして使う

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

リフレクション

java.lang.reflect.Type を返すメソッドを利用する

•Class # getGenericSuperclass() : Type•Class # getGenericInterfaces() : Type[]•Method # getGenericParameterTypes() : Type[]•Method # getGenericReturnType() : Type•Field # getGenericType() : Type

などなど

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

まとめ

ジェネリクスが複雑になりすぎないように継承でのバインドの利用を検討する

再帰ジェネリクスを応用すればサブクラスで具象型を扱える

内部クラスもスコープ範囲内

リフレクションでフィールドやメソッド引数・戻り値のパラメタライズドタイプの型をとれる

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

上級編

ジェネリックな APIの設計例利用側のコードとともによい設計を考える

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

new T()したい

動機

  DBのマッパーやWebシステムのControllerのようなフレームワークを作った場合、取り扱うオブジェクトの型をジェネリクスで定義し、インスタンスを生成してデータをマッピングして渡したい

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

コンストラクタ

Javaのオブジェクト指向の一般論おさらい

interfaceや親クラスとして振る舞うことが要求される

具象型の特有の情報を押し込める場所はコンストラクタ

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

コンストラクタ

コンストラクタの引数の形は継承で制約できません

やりたければ Factoryクラス作れ

一般にインスタンス生成は抽象化しにくい

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

Factoryの実装例

interface HogeFactory<T extends A> {/** デフォルトコンストラクタ的な */T newInstance();

}

インスタンスの生成に必要なデータを Factoryで制約

public class HogeTemplate {public <T> T template(HogeFactory<T>

factory) {return factory.newInstance();

}}

こうすればインスタンスの生成は可能になる、が面倒

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

妥協例

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);}

}デフォルトコンストラクタがあることが前提

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

C#の例

class MyGenericClass<T> where T : new() {}

T型にデフォルトコンストラクタがあることという制約。デフォルトコンストラクタがあることを保証させることでインスタンス生成を可能とする。

この制約はダサい制約ではあるが妥当な妥協点か。Javaでもリフレクションで生成する場合にデフォルトコンストラクタがあることという制限をつけがち。

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

継承によるバインドの場合Class # getGenericSuperclass() という福音

継承によるバインドであれば、型情報から型変数に何がバインドされたかを知ることができる

Type を ParameterizedType にキャストgetActualTypeArguments() でバインドされた実体

実体の Typeを Classにキャストして newInstance() ただし、デフォルトコンストラクタがあるものとする

http://d.hatena.ne.jp/Nagise/20130815

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

ジェネリックなデザインパターン

Template Methodパターン

虫食いのテンプレートを作る。左図の処理 ABCは abstractメソッド。サブクラスではオーバーライドして実装を書く。

ここに先の継承によるバインドとバインドされた型のリフレクションでのnewInstance()を複合すると便利

処理 A

処理 B

処理 C

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

まとめ

New T()したくなるシチュエーションには継承によるバインド+リフレクションを使えないか検討する

Template nethodパターンと複合させるとフレームワークに応用できるかも

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

変態編

何かに使えるかもしれない

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

再帰での相互参照

二つの型の具象型が、相互に相手の具象型を知っている

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>

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

相互再帰+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>>

やりすぎです

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

内部クラスでグルーピング

二つのクラスを囲うクラスを作って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();}

}やりすぎです

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

型変数の部分適用

複数の型変数がある型を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>();

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

型変数の部分適用応用例

http://d.hatena.ne.jp/Nagise/20110124/1295874192Javaによる高階型変数の実装

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");

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

当セッションは主に blogに記述した事項を再編集してまとめたものである。

Blog プログラマーの脳みそ

カテゴリー Generics を参照

http://d.hatena.ne.jp/Nagise/searchdiary?word=%2A%5BGenerics%5D

Generics Hell に遭遇したら Twitterで

@nagiseにご一報ください。

より深く知りたい場合の資料