久々に Java を触ることがあって、Java 8 だったので Stream API を使ってみたところ・・・
色々言いたいことはあるけど、一番の問題は
チェック例外(検査例外)だと感じた。
チェック例外とは、簡単に言うと catch または throws してないとコンパイラに怒られる例外のこと。
Java 以外の言語ではほとんど採用されてないレア機能。
で、何が問題かというと、下記のようなケースでコンパイルできない。
/**
* source にある valueType 型を返すメソッドを実行して、結果をリストで返す。
*/
@SuppressWarnings("unchecked")
public static <T> List<T> getValues(Object source, final Class<T> valueType){
return Arrays.stream(source.getClass().getDeclaredMethods())
.filter(m -> m.getReturnType().equals(valueType))
.map(m -> (T)m.invoke(source)) // この行でコンパイルエラー
.collect(Collectors.toList());
}
理由は、
Stream.map がチェック例外を投げるラムダ式を引数に取れないため。
※ チェック例外を考慮した FunctionalInterface が標準 API に用意されてないこと(2016/1/12 時点)を考慮すると、ラムダ式にチェック例外を混ぜてほしくないのだろう。しかし、標準 API の中でチェック例外を投げるメソッド(上記の
Method.invoke 等)は結構あるため、使う側としては混ぜざるを得ない。
検査例外を実行時例外にラップ
ラムダ式内でチェック例外をキャッチ、実行時例外にラップする。
@SuppressWarnings("unchecked")
public static <T> List<T> getValuesWrap(Object source, final Class<T> valueType){
return Arrays.stream(source.getClass().getDeclaredMethods())
.filter(m -> m.getReturnType().equals(valueType))
.map(m -> {
try{
return (T)m.invoke(source);
} catch( IllegalAccessException | InvocationTargetException e ){
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
この方法だとチェック例外を殺してしまうが、標準 API のみだとどうしようもない。
また、ラムダ式に try-catch が入り込んでソースが汚くなっている。
チェック例外を投げるラムダ式から標準 API のラムダ式に変換
lambda - How can I throw CHECKED exceptions from inside Java 8 streams? - Stack Overflow にある LambdaExceptionUtil を使う。(もしくは自作)
@SuppressWarnings("unchecked")
public static <T> List<T> getValuesRethrow(Object source, final Class<T> valueType){
return Arrays.stream(source.getClass().getDeclaredMethods())
.filter(m -> m.getReturnType().equals(valueType))
.map(rethrowFunction(m -> (T)m.invoke(source)))
.collect(Collectors.toList());
}
rethrowFunction で、チェック例外を考慮した Function_WithExceptions から 標準 API の
Function に変換している。
チェック例外を殺すことには変わりないが、ソースの見た目は最初の方法よりましになっている。
チェック例外は不要、という場合はこの方法が無難。
Stream API だけでなく、他のチェック例外無しラムダ式を引数にするメソッドにも利用できる。
後、面白いのが下記のような方法で実行時例外にラップせずにチェック例外を黙らせていること
Java の割と知られているハックらしい。
@SuppressWarnings("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
throw (E)exception;
}
チェック例外を投げるラムダ式を引数に取れる Stream API を使う
JeffreyFalgout/ThrowingStream · GitHub を使う。(もしくは自作)
@SuppressWarnings("unchecked")
public static <T> List<T> getValuesThrowing(Object source, final Class<T> valueType) throws Exception {
return ThrowingStream.of(Arrays.stream(source.getClass().getDeclaredMethods()), Exception.class)
.filter(m -> m.getReturnType().equals(valueType))
.map(m -> (T)m.invoke(source))
.collect(Collectors.toList());
}
ThrowingStream.of でチェック例外を考慮したメソッドを備える Strem を作成している。
現時点(2016/1/12)で、ThrowingStream.of に指定できる例外は 1 つだけなので、複数のチェック例外がある場合は継承元を指定するしかない。
指定した例外を ThrowingStream.collect で投げてくれるので、一応チェック例外は殺してない。
愚痴
この問題については、Java 開発元がどうにかすべきだと思う。
上記のようなメソッド・クラスを用意するか、いっそコンパイルオプションにチェック例外無効を用意するとか・・・
いずれ解決方法が提供されることを祈りつつ、可能ならベター Java を使っていきたい・・・