ジェネリクスの基礎と応用




 Twetter : @nagise
 はてな : Nagise

 所属
 
    (株) Abby
 
     java-ja
 
    北陸エンジニアグループ
ジェネリクスのスコープ
メソッドの中でのみ有効なジェネリクス
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<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);
インスタンスの 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);
型変数の境界


  class Hoge<T extends B>

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

    new Hoge<A>(); ←   NG
    new Hoge<B>(); ←   OK
   new Hoge<B2>(); ←    NG
    new Hoge<C>(); ←   OK
型変数の境界



class Hoge <T extends A & Serializable>

      & で繋いでインタフェースを
       境界に加えることができる
中級編
パラメータ化された型の代入互換
       性
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 型としてしか返せな



い
継承によるバインド
public class StringList
  extends ArrayList<String> {}

というクラスを作ると

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

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


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

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

クラスを作るのをサボらない
上級編
再帰ジェネリクス


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> {
     // 略
    }
}

そもそも内部クラスの使いどころが難しいですが。
new T() したい



「 Java のジェネリクスは
 new T() ってできないのがクソだよね」

「お前の設計がクソなんじゃないの?」
コンストラクタ


 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 型にデフォルトコンストラクタがあることという制約
ダサいけど妥当な妥協点か
変態編
再帰での相互参照

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

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();
  }
}
やりすぎです
まとめ
型変数でメソッドやインスタンスの I/O の
 型の関連性を表す
→ まずは綺麗なオブジェクト指向の設計を

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

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

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

ジェネリクスの基礎と応用 JJUG CCC 2012 Fall