メソッドの戻り値のジェネリクス(List<String>
のString
部分)を動的に変えてみましょう。
メリット
どういうメリットがあるかというと、
- 共変戻り値を使っていて、メソッドの戻り値の型が一つに定まらないとき
- 親子関係にあるいろいろな型のオブジェクトが1つのリストに入っていて、そのうち特定の型のオブジェクトだけを抽出して新たなリストを作成して返したいとき(たとえばAndroidアプリの開発で、引数に
Button.class
やらTextView.class
を渡して、それぞれList<Button>
やList<TextView>
が返ってくるメソッドを作りたいとき)
に役立ちます。
この方法を使えば、
戻り値のジェネリクスが違うだけのメソッドをたくさん作らないですんだり、
返ってきたList<Foo>
の要素1つ1つに対して型をチェックしたりキャストしたりする手間がなくなります。
それほど頻繁に書きたいコードではありませんが、たまに書きます。
サンプル
ここでは
- 親クラスである
Super
クラスのオブジェクト -
Super
を継承したSubA
クラスのオブジェクト -
Super
を継承したSubB
クラスのオブジェクト
が混ざっている配列から、引数で渡された型のオブジェクトだけを抽出してリストを作成し、返すメソッドを考えてみました。
次のサンプルのgetList
メソッドです。
package test;
import java.util.ArrayList;
import java.util.List;
public class GenericsTest {
public static void main(String[] args) {
List<Super> superList = getList(Super.class);
System.out.println("superList.size():" + superList.size());
for (Super element : superList) {
System.out.println(element);
}
System.out.println("!!!!!!");
List<SubA> subAList = getList(SubA.class);
System.out.println("subAList.size():" + subAList.size());
for (SubA element : subAList) {
System.out.println(element);
}
System.out.println("!!!!!!");
List<SubB> subBList = getList(SubB.class);
System.out.println("subBList.size():" + subBList.size());
for (SubB element : subBList) {
System.out.println(element);
}
System.out.println("!!!!!!");
List<String> stringList = getList(String.class);
System.out.println("stringList.size():" + stringList.size());
for (String element : stringList) {
System.out.println(element);
}
}
private static <T> List<T> getList(Class<T> clazz) {
List<T> result = new ArrayList<T>();
Super[] array = { new Super(), new SubA(), new SubB() };
for (Super element : array) {
if (clazz.isAssignableFrom(element.getClass())) {
result.add(clazz.cast(element));
}
}
return result;
}
private static class Super {
@Override
public String toString() {
return "Super";
}
}
private static class SubA extends Super {
@Override
public String toString() {
return "SubA";
}
}
private static class SubB extends Super {
@Override
public String toString() {
return "SubB";
}
}
}
このコードを実行すると
superList.size():3
Super
SubA
SubB
!!!!!!
subAList.size():1
SubA
!!!!!!
subBList.size():1
SubB
!!!!!!
stringList.size():0
となり、
Super.class
を引数にすると、Super
とそのサブクラスのオブジェクトがリストで返ってきて、
SubA.class
やSubB.class
を引数にするとそれぞれのオブジェクトだけがリストに入って返ってくることがわかります。
さらにString.class
を引数にすると、String
のオブジェクトはもとの配列に入っていなかったので、返ってくるリストには何も入っていません。
もっと役立てる方法
返ってくるリストにサブクラスのオブジェクトを含めないようにする
上のサンプルの
if (clazz.isAssignableFrom(element.getClass()))
を
if (clazz == element.getClass())
にすれば、返ってくるリストにサブクラスのオブジェクトが入らないようにできます。
具体的には上のコードで言えば、Super.class
を引数に渡して、返ってくるリストにSubA
やSubB
のオブジェクトが入らないようにできます。
AndroidでいえばTextView
のオブジェクトはほしいけれどもButton
のオブジェクトはいらない、というときに役立ちます。
(Button
はTextView
のサブクラスなので)
引数に受け取るクラスをあるクラスのサブクラスに限定する
gakaさんにコメントで教えていただいたので追記します。
gakaさんのこのコメントのとおりに型引数を<T extends Super>
というように指定すれば、引数として受け取るクラスの型を指定できます。
こうすれば、たとえばString.class
を引数として渡そうとすると、String
はSuper
のサブクラスではないのでコンパイルエラーが発生してくれます。