LoginSignup
34
38

More than 5 years have passed since last update.

メソッドの戻り値のジェネリクス(List<String>の"String"部分)を動的に変える

Last updated at Posted at 2014-06-18

メソッドの戻り値のジェネリクス(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.classSubB.classを引数にするとそれぞれのオブジェクトだけがリストに入って返ってくることがわかります。
さらにString.classを引数にすると、Stringのオブジェクトはもとの配列に入っていなかったので、返ってくるリストには何も入っていません。

もっと役立てる方法

返ってくるリストにサブクラスのオブジェクトを含めないようにする

上のサンプルの

if (clazz.isAssignableFrom(element.getClass()))

if (clazz == element.getClass())

にすれば、返ってくるリストにサブクラスのオブジェクトが入らないようにできます。
具体的には上のコードで言えば、Super.classを引数に渡して、返ってくるリストにSubASubBのオブジェクトが入らないようにできます。
AndroidでいえばTextViewのオブジェクトはほしいけれどもButtonのオブジェクトはいらない、というときに役立ちます。
ButtonTextViewのサブクラスなので)

引数に受け取るクラスをあるクラスのサブクラスに限定する

gakaさんにコメントで教えていただいたので追記します。
gakaさんのこのコメントのとおりに型引数を<T extends Super>というように指定すれば、引数として受け取るクラスの型を指定できます。
こうすれば、たとえばString.classを引数として渡そうとすると、StringSuperのサブクラスではないのでコンパイルエラーが発生してくれます。

34
38
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
38