読者です 読者をやめる 読者になる 読者になる

うさがにっき

読書感想文とプログラムのこと書いてきます

Genericsについて

概要

Genericsについて全然よくわかってなかったのでまとめる

詳細

ジェネリックス定義の基本

型パラメータを宣言する

ジェネリック型では特定の型を受け取るための型パラメータを宣言しなければいけない

public class HashMap<K, V>

型パラメータとは<...>で囲まれた型のこと
構文規則はないが、E(Element), T(Type), K(Key), V(Value)などのように表すのが一般的

型変数を利用できる場所

型パラメータの中で宣言された型のことを型変数という
型変数は、ジェネリック型配下のメンバー定義の中で型を表すために利用できる

境界ワイルドカード

ジェネリックスでは、標準的な

List<Integer>

の他に

List<? extends Number>

のような型表現を提供している、このような型のことを境界ワイルドカード型という
境界ワイルドカード型を使うことにより配列の時問題となっていた共変問題が解決する

共変と不変

配列は共変という性質を持つ、これは継承の関係にあるSuper, SubにおいてSuper配列, Sub配列も継承関係にあるということ
よって以下のコードが許可される

Object[] list = new Integer[10];
list[0] = "abcde";

このようなコードがコンパイル時にエラーとならずに通ってしまう、もちろん実行時エラーとなる

一方ジェネリックスを利用したコレクションは不変という性質を持つ
Super, Subという型が継承関係にあたってもSuperのListとSubのListは継承関係とならない

list<Number> list = new ArrayList<Integer>(10);

これはコンパイルエラーとなる

境界ワイルドカード

ただし、特定の型だけでなくNumber派生クラスを広く受け入れたい場合、ジェネリック型のこの性質は厳密すぎて不便になることもある
そこで使われるのが境界ワイルドカード
境界ワイルドカード型を使うことによって上のコードはこのように書き直すことができる

list<? extends Number> list = new ArrayList<Integer>(10);

によってNumberクラスの派生型を受け入れるという意味になる

境界ワイルドカード型は、もっぱらメソッドの引数型として使われる

public boolean addAll(Collection<? extends E> c{
...
}

もしこのメソッド

public boolean addAll(Collection<E> c{
...
}

だったら、パラメータ化された型ArrayListに対して、ジェネリック型は不変のため、list.addAll(intList)は不可となる
だが、境界ワイルドカード型を利用することによりNumber or Numberの派生クラスであれば良いことになるのでList,List両方を引き渡すことができる
境界ワイルドカード型は不変の仕組みを持つジェネリック型に、型の安全性を維持しながら一定の柔軟性を与えていると考えられる

境界型パラメータ

以下のコードはコンパイルエラーとなる

public class Bound<E> {
    private E e;

    public char getChar(int index) {
        return e.charAt(index);
    }
}

型パラメータは内部的にはObjectとして扱われているためObject#charAtは存在しないため
このように特定の振る舞いを要求する場合には、型パラメータの定義でも型にcharAtが存在することを保証する必要がある、このような役割を提供するのが境界型パラメータ

public class Bound<E extends CharSequence> {

これで型引数にはCharSequenceの実装クラスしか指定できなくなる
よって、これでcharAtが存在することが保証される

下限境界ワイルドカード

extendsキーワードで表す境界ワイルドカード型は、いわゆる指定された境界型を上限にその下位型を認めることから上限境界ワイルドカード型と呼ばれる
一方、逆に指定された境界型を下限にその上位型を認める下限境界ワイルドカードがある

class MyArrayList<E> extends ArrayList<E> {
    public void export(Collection<? super E> c {...}
}

exportはコレクションcにリストの内容をまとめて出力するメソッド
exportメソッドの引数型「Collection」が下限境界ワイルドカード
型E、もしくはその上位型を要素に持つコレクションを意味する

MyArrayList<String> src = new MyArrayList<String>();
List<CharSequence> dest = new MyArrayList<CharSequence>();
src.export(dest);

もしも下限境界ワイルドカード型がなければ、変数destはArrayListである必要が有る
だが、下限境界ワイルドカードによってCharSequenceなどの上位型を渡すことが可能になる

ジェネリックメソッド

メソッドの引数や伴うローカル変数の型を、メソッド呼び出し時に決められるメソッドのこと
修飾子の直後に<...>の形式で型パラメータを指定しているのが特徴

public static <T> boolean addAll(Collection<? super T> c, T... elements) {
	boolean result = false;
	for(T element : elements) {
		result |= c.add(element);
	}
	return result;
}

ジェネリックメソッドは普通のメソッドと同じく以下の要領で呼び出せる

List<String> list = new ArrayList<String>();
Collections.addAll(list, "A", "B", "C");

つまり引数の型から暗黙的に型パラメータの型を判定している

感想

Generics難しい...半分も理解できてなかったし、まだまだ使いこなせてない

参考

AndroidエンジニアのためのモダンJava

AndroidエンジニアのためのモダンJava

広告を非表示にする