結論
T型のインスタンスを生成する関数型インターフェースのインスタンスを、引数で受け取る。
// newT()が必要なメソッド
<T> T doSomething(Supplier<T> supplier) {
T instance = supplier.get();
return instance;
}
// doSomethingを呼び出すメソッド
void caller() {
Test instance = this.doSomething(() -> new Test());
}
解説
そもそも new T()
がアンチパターンなのはわかっていても、 new T()
したいときもある。
そのときの解決法としてよく提示されるのが以下のパターン。
ClassインスタンスをもらってnewInstance()する
<T> T doSomething(Class<T> clazz) throws InstantiationException, IllegalAccessException {
T instance = clazz.newIntance();
return instance;
}
void caller() {
Test instance = this.doSomething(Test.class);
}
このコードの問題点は検査例外をスローすること。
検査例外をスローする
上述のコードの通り、 InstantiationException
と IllegalAccessException
をスローする。
Class.newInstance()
側としては妥当な設計だとは思うが、使う側としてはコンストラクタが例外をスローしないことを確信できる場合もあるわけで…。
そうするとわざわざ上記例外を try-catch
したり throws
したりするのは面倒くさい。
さらに検査例外なので、stream
と相性が悪く1、内部でtry-catch
が必要になる
<T extends SuperTest> List<T> doSomething(Class<T> clazz, List<String> valueList) throws InstantiationException, IllegalAccessException {
return valueList.stream()
.map(value -> {
// せっかくstreamでスマートに書きたいのにtry-catch…
try {
T obj = clazz.newInstance();
obj.setValue(value);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
その点、関数型インターフェースは余計な検査例外が発生しないので比較的綺麗にかける(コンストラクタにthrows
がある場合は別)。
余談
コンストラクタが引数をとる場合はFunction<T, R>
のインスタンスを使う。
2つならBiFunction<T, U, R>
。
3つ以上の場合は、引数のためのクラスを作るなり、java.util.Map
を使うなりする。
// newT()が必要なメソッド
<T> T doSomething(Function<String, T> func) {
T instance = func.apply("コンストラクタの引数");
return instance;
}
// doSomethingを呼び出すメソッド
void caller() {
Test instance = this.doSomething((str) -> new Test(str));
}
// newT()が必要なメソッド
<T> T doSomething(BiFunction<String, Integer, T> func) {
T instance = func.apply("コンストラクタの引数", 1);
return instance;
}
// doSomethingを呼び出すメソッド
void caller() {
Test instance = this.doSomething((str, number) -> new Test(str, number));
}
-
正確には関数型インターフェースと相性が悪い。 ↩