Java 8ストリーム/ラムダの内部からCHECKED例外をスローする方法を教えてください。
言い換えれば、私はこのようなコードをコンパイルしたいです。
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.map(className -> Class.forName(className))
.collect(Collectors.toList());
return classes;
}
上記のClass.forName()
メソッドは、チェックされるClassNotFoundException
をスローするため、このコードはコンパイルされません。
私は、実行時例外の中でチェック済み例外をラップしたくはなく、ラップされた未チェック例外を投げたくないことに注意してください。 チェックされた例外自体をスローし、ストリームに醜いtry
/catches
を追加しないでください。
このLambdaExceptionUtil
ヘルパークラスを使用すると、Javaストリームで次のようにチェック済みの例外を使用できます。
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
注Class::forName
はClassNotFoundException
をスローします。これはチェック済みです。ストリーム自体もClassNotFoundException
をスローしますが、これはラップされた未チェックの例外ではありません。
public final class LambdaExceptionUtil {
@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
void accept(T t) throws E;
}
@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
void accept(T t, U u) throws E;
}
@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
R apply(T t) throws E;
}
@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
T get() throws E;
}
@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
void run() throws E;
}
/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
return t -> {
try { consumer.accept(t); }
catch (Exception exception) { throwAsUnchecked(exception); }
};
}
public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
return (t, u) -> {
try { biConsumer.accept(t, u); }
catch (Exception exception) { throwAsUnchecked(exception); }
};
}
/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
return t -> {
try { return function.apply(t); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
};
}
/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
return () -> {
try { return function.get(); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
};
}
/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
{
try { t.run(); }
catch (Exception exception) { throwAsUnchecked(exception); }
}
/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
{
try { return supplier.get(); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
}
/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
try { return function.apply(t); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
}
@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }
}
それを使用する方法に関する他の多くの例(静的にLambdaExceptionUtil
をインポートした後):
@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.forEach(rethrowConsumer(System.out::println));
}
@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
List<Class> classes1
= Stream.of("Object", "Integer", "String")
.map(rethrowFunction(className -> Class.forName("Java.lang." + className)))
.collect(Collectors.toList());
List<Class> classes2
= Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
}
@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
Collector.of(
rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
}
@Test
public void test_uncheck_exception_thrown_by_method() {
Class clazz1 = uncheck(() -> Class.forName("Java.lang.String"));
Class clazz2 = uncheck(Class::forName, "Java.lang.String");
}
@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
Class clazz3 = uncheck(Class::forName, "INVALID");
}
注1:上記のrethrow
クラスのLambdaExceptionUtil
メソッドは、恐れることなく使用することができます。どんな状況でも使用できます。最後の問題の解決を手助けしてくれたユーザー@PaoloCに感謝します。今度はコンパイラがあなたにthrow節を追加するように頼むでしょう。
注2:上記のuncheck
クラスのLambdaExceptionUtil
メソッドはボーナスメソッドで、使用したくない場合はクラスから安全に削除することができます。これらを使用した場合は、次の使用例、長所/短所、および制限を理解する前に、慎重に行ってください。
•文字通り、宣言した例外をスローすることのないメソッドを呼び出す場合は、uncheck
メソッドを使用できます。例:new String(byteArr、 "UTF-8")はUnsupportedEncodingExceptionをスローしますが、UTF-8は常に存在することがJava仕様によって保証されています。ここでは、throws宣言は厄介なものであり、最小限の定型句でそれを黙らせるための解決策は大歓迎です。String text = uncheck(() -> new String(byteArr, "UTF-8"));
•throws宣言を追加するオプションがなく、例外をスローすることが完全に適切な場合に厳密なインタフェースを実装している場合は、uncheck
メソッドを使用できます。単に例外をスローするという特権を得るために例外をラップすると、誤った例外を含むスタックトレースが発生し、実際に問題が発生したことに関する情報は得られません。良い例はRunnable.run()で、これはチェックされた例外を投げません。
•いずれにせよ、uncheck
メソッドを使うことにした場合、throws節なしでCHECKED例外を投げることによる2つの結果に注意してください:1)呼び出しコードは名前でそれを捕らえることができないでしょう(あなたが試みるなら、コンパイラは言うでしょう:例外は対応するtry文の本体で決して投げられません)。それはバブルして、おそらく "catch Exception"や "catch Throwable"によってメインプログラムのループに捕らえられるでしょう。 2)それは、驚くべきことではないという原則に違反します。すべての可能性のある例外の捕捉を保証できるようにするためには、もはやRuntimeException
を捕捉するのに十分ではないでしょう。このため、フレームワークコードではなく、あなたが完全に管理しているビジネスコードでのみこれを行うべきです。
あなたの質問に対する簡単な答えは、あなたができることではありません、少なくとも直接的にはできません。そしてそれはあなたのせいではありません。 オラクルはそれをめちゃめちゃにしました。彼らはチェック例外の概念にしがみついていましたが、機能インターフェース、ストリーム、ラムダなどを設計する際にチェック例外を処理することを一貫して忘れていました。 Robert C. Martinのように、チェック済み例外を実験失敗と呼んだ専門家の工場。
私の意見では、これはAPIの中の巨大なバグであり、言語仕様の中のマイナーバグです。 .
APIのバグは、これが実際に関数型プログラミングにとってひどい意味をなすようなチェック済みの例外を転送する機能を提供していないことです。以下に説明するように、そのような機能は簡単に可能でした。
言語仕様のバグは、型のリストが許される状況でのみ型パラメータが使われる限り、型パラメータが単一の型の代わりに型のリストを推論することを許さないということです(throws
節)。
Javaプログラマーとしての私たちの期待は、次のコードがコンパイルされるべきだということです。
import Java.util.ArrayList;
import Java.util.List;
import Java.util.stream.Stream;
public class CheckedStream {
// List variant to demonstrate what we actually had before refactoring.
public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
final List<Class> classes = new ArrayList<>();
for (final String name : names)
classes.add(Class.forName(name));
return classes;
}
// The Stream function which we want to compile.
public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
return names.map(Class::forName);
}
}
しかし、それは与えます:
cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.Java
CheckedStream.Java:13: error: incompatible thrown types ClassNotFoundException in method reference
return names.map(Class::forName);
^
1 error
機能インターフェースが定義されている方法は、現在コンパイラが例外を転送するのを防ぎます - もしStream.map()
であればFunction.apply() throws E
であることをStream.map() throws E
に伝える宣言はありません。
欠けているのは、チェック済みの例外を通過させるための型パラメータの宣言です。次のコードは、そのようなパススルー型パラメータが実際に現在の構文でどのように宣言されている可能性があるかを示しています。マークされた行の特別な場合(これは以下で説明する制限です)を除いて、このコードはコンパイルされて期待どおりに動作します。
import Java.io.IOException;
interface Function<T, R, E extends Throwable> {
// Declare you throw E, whatever that is.
R apply(T t) throws E;
}
interface Stream<T> {
// Pass through E, whatever mapper defined for E.
<R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}
class Main {
public static void main(final String... args) throws ClassNotFoundException {
final Stream<String> s = null;
// Works: E is ClassNotFoundException.
s.map(Class::forName);
// Works: E is RuntimeException (probably).
s.map(Main::convertClass);
// Works: E is ClassNotFoundException.
s.map(Main::throwSome);
// Doesn't work: E is Exception.
s.map(Main::throwSomeMore); // error: unreported exception Exception; must be caught or declared to be thrown
}
public static Class convertClass(final String s) {
return Main.class;
}
static class FooException extends ClassNotFoundException {}
static class BarException extends ClassNotFoundException {}
public static Class throwSome(final String s) throws FooException, BarException {
throw new FooException();
}
public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException {
throw new FooException();
}
}
throwSomeMore
の場合、IOException
が見逃されているのを見たいのですが、実際にはException
が見逃されています。
型推論は例外の場合でも単一の型を探しているように見えるのでこれは完璧ではありません。型推論は単一の型を必要とするので、E
はsuper
とClassNotFoundException
の共通のIOException
、すなわちException
に解決する必要があります。
型推論の定義を微調整する必要があるのは、型のリストが許されるところでtypeパラメータが使用されている場合にコンパイラが複数の型を探すためです(throws
節)。その場合、コンパイラーによって報告される例外タイプは、単一キャッチオールスーパータイプではなく、参照されているメソッドのチェック済み例外の元のthrows
宣言と同程度に固有のものになります。
悪いニュースは、これはOracleがそれをめちゃめちゃにしたことを意味するということです。確かにそれらはユーザランドコードを壊すことはありませんが、既存の機能インタフェースに例外型パラメータを導入することはこれらのインタフェースを明示的に使用するすべてのユーザランドコードのコンパイルを壊すでしょう。彼らはこれを修正するためにいくつかの新しい構文糖を発明しなければならないでしょう。
さらに悪いことに、このトピックは2010年にBrian Goetzによってすでに議論されています https://blogs.Oracle.com/briangoetz/entry/exception_transparency_in_Java (新しいリンク: http://mail.openjdk.Java.net/pipermail/lambda-dev/2010-June/001484.html )しかし私は私は、この調査は最終的には成功しなかったこと、そしてチェックされた例外とラムダの間の相互作用を軽減するために私が知っているオラクルでの現在の作業はないことを知らせました。
あなたはこれを安全にすることはできません。あなたはカンニングすることができます、しかしあなたのプログラムは壊れていて、これは必然的に誰かに噛み付くために戻ってくるでしょう(それはあなたでなければなりません、しかししばしば私たちの不正は他の誰かに爆破します)
これをやや安全に実行する方法は次のとおりです(ただし、これはお勧めしません)。
class WrappedException extends RuntimeException {
Throwable cause;
WrappedException(Throwable cause) { this.cause = cause; }
}
static WrappedException throwWrapped(Throwable t) {
throw new WrappedException(t);
}
try
source.stream()
.filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
...
}
catch (WrappedException w) {
throw (IOException) w.cause;
}
ここでは、ラムダで例外をキャッチし、計算が例外的に失敗したことを示すシグナルをストリームパイプラインからスローし、シグナルをキャッチし、そのシグナルに基づいて基礎となる例外をスローします。重要なのは、例外がスローされることを宣言せずにチェック済みの例外をリークさせるのではなく、常に合成例外を捕捉することです。
あなたはできる!
@marcgのUtilException
を拡張し、必要に応じてthrow E
を追加します。このように、コンパイラはthrow句を追加するように要求します。 Java 8のストリームでは例外がネイティブに行われます。
説明:IDEにLambdaExceptionUtil
をコピー/貼り付けてから、以下のLambdaExceptionUtilTest
に示すように使用します。
public final class LambdaExceptionUtil {
@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
void accept(T t) throws E;
}
@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
R apply(T t) throws E;
}
/**
* .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
*/
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
return t -> {
try {
consumer.accept(t);
} catch (Exception exception) {
throwActualException(exception);
}
};
}
/**
* .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
*/
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
return t -> {
try {
return function.apply(t);
} catch (Exception exception) {
throwActualException(exception);
return null;
}
};
}
@SuppressWarnings("unchecked")
private static <E extends Exception> void throwActualException(Exception exception) throws E {
throw (E) exception;
}
}
使用法と動作を示すためのいくつかのテスト:
public class LambdaExceptionUtilTest {
@Test(expected = MyTestException.class)
public void testConsumer() throws MyTestException {
Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
}
private void checkValue(String value) throws MyTestException {
if(value==null) {
throw new MyTestException();
}
}
private class MyTestException extends Exception { }
@Test
public void testConsumerRaisingExceptionInTheMiddle() {
MyLongAccumulator accumulator = new MyLongAccumulator();
try {
Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
fail();
} catch (MyTestException e) {
assertEquals(9L, accumulator.acc);
}
}
private class MyLongAccumulator {
private long acc = 0;
public void add(Long value) throws MyTestException {
if(value==null) {
throw new MyTestException();
}
acc += value;
}
}
@Test
public void testFunction() throws MyTestException {
List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
assertEquals(2, sizes.size());
assertEquals(4, sizes.get(0).intValue());
assertEquals(5, sizes.get(1).intValue());
}
private Integer transform(String value) throws MyTestException {
if(value==null) {
throw new MyTestException();
}
return value.length();
}
@Test(expected = MyTestException.class)
public void testFunctionRaisingException() throws MyTestException {
Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
}
}
NoException (私のプロジェクト)、 jOOλのチェックなし 、 throwing-lambdas 、 スロー可能なインタフェース 、または フェイクパス 。
// NoException
stream.map(Exceptions.sneak().function(Class::forName));
// jOOλ
stream.map(Unchecked.function(Class::forName));
// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());
// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));
// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
ライブラリ を作成しました。これにより、Stream APIを拡張して、チェック済み例外をスローできるようになりました。 Brian Goetzのトリックを使っています。
あなたのコードは
public List<Class> getClasses() throws ClassNotFoundException {
Stream<String> classNames =
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String");
return ThrowingStream.of(classNames, ClassNotFoundException.class)
.map(Class::forName)
.collect(Collectors.toList());
}
この答えは17と似ていますが、ラッパー例外の定義は避けています。
List test = new ArrayList();
try {
test.forEach(obj -> {
//let say some functionality throws an exception
try {
throw new IOException("test");
}
catch(Exception e) {
throw new RuntimeException(e);
}
});
}
catch (RuntimeException re) {
if(re.getCause() instanceof IOException) {
//do your logic for catching checked
}
else
throw re; // it might be that there is real runtime exception
}
それはいけません。
しかしながら、あなたは 私のプロジェクト の1つを見たいと思うかもしれません。
あなたのケースでは、あなたはそれを行うことができるでしょう:
import static com.github.fge.lambdas.functions.Functions.wrap;
final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);
List<Class> classes =
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.map(f.orThrow(MyException.class))
.collect(Collectors.toList());
そしてMyException
をキャッチします。
それは一例です。別の例は、あなたが何らかのデフォルト値を.orReturn()
することができるということです。
これはまだ進行中の作業であることに注意してください。より良い名前、より多くの機能など.
高度な解決策の上にコメントをまとめることは回復、再投げそして抑制を提供するAPIのようなビルダーでチェックされていない機能のための特別なラッパーを使うことです。
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.map(Try.<String, Class<?>>safe(Class::forName)
.handle(System.out::println)
.unsafe())
.collect(toList());
以下のコードは、Consumer、Supplier、およびFunctionの各インターフェースについて説明しています。簡単に拡張できます。この例では、一部の公開キーワードが削除されました。
ClassTryはクライアントコードのエンドポイントです。安全なメソッドは、各関数型に対して一意の名前を持つことがあります。 CheckedConsumer、CheckedSupplier、およびCheckedFunction は、Tryとは無関係に使用できるlib関数の類似物です。
CheckedBuilderは、いくつかのチェック済み関数で例外を処理するためのインタフェースです。 orTryは失敗した場合に同じタイプの関数を実行することを可能にします。 handleは、例外タイプのフィルタリングを含む例外処理を提供します。ハンドラの順番は重要です。メソッドを減らす安全でないおよびrethrowは、実行チェーン内の最後の例外を再スローします。メソッドを減らすorElseおよびorElseGetすべての関数の場合は省略可能なもののような代替値を返します失敗しました。また、メソッドsuppressがあります。 CheckedWrapperはCheckedBuilderの一般的な実装です。
final class Try {
public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T>
safe(CheckedSupplier<T> supplier) {
return new CheckedWrapper<>(supplier,
(current, next, handler, orResult) -> () -> {
try { return current.get(); } catch (Exception ex) {
handler.accept(ex);
return next.isPresent() ? next.get().get() : orResult.apply(ex);
}
});
}
public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
return supplier;
}
public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void>
safe(CheckedConsumer<T> consumer) {
return new CheckedWrapper<>(consumer,
(current, next, handler, orResult) -> t -> {
try { current.accept(t); } catch (Exception ex) {
handler.accept(ex);
if (next.isPresent()) {
next.get().accept(t);
} else {
orResult.apply(ex);
}
}
});
}
public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
return consumer;
}
public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R>
safe(CheckedFunction<T, R> function) {
return new CheckedWrapper<>(function,
(current, next, handler, orResult) -> t -> {
try { return current.applyUnsafe(t); } catch (Exception ex) {
handler.accept(ex);
return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
}
});
}
public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
return function;
}
@SuppressWarnings ("unchecked")
static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E {
throw (E) exception;
}
}
@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
void acceptUnsafe(T t) throws Exception;
@Override default void accept(T t) {
try { acceptUnsafe(t); } catch (Exception ex) {
Try.throwAsUnchecked(ex);
}
}
}
@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
R applyUnsafe(T t) throws Exception;
@Override default R apply(T t) {
try { return applyUnsafe(t); } catch (Exception ex) {
return Try.throwAsUnchecked(ex);
}
}
}
@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
T getUnsafe() throws Exception;
@Override default T get() {
try { return getUnsafe(); } catch (Exception ex) {
return Try.throwAsUnchecked(ex);
}
}
}
interface ReduceFunction<TSafe, TUnsafe, R> {
TSafe wrap(TUnsafe current, Optional<TSafe> next,
Consumer<Throwable> handler, Function<Throwable, R> orResult);
}
interface CheckedBuilder<TSafe, TUnsafe, R> {
CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);
CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);
<E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
Class<E> exceptionType, Consumer<E> handler);
CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);
<E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
Class<E> exceptionType, Consumer<? super E> handler);
TSafe unsafe();
TSafe rethrow(Function<Throwable, Exception> transformer);
TSafe suppress();
TSafe orElse(R value);
TSafe orElseGet(Supplier<R> valueProvider);
}
final class CheckedWrapper<TSafe, TUnsafe, R>
implements CheckedBuilder<TSafe, TUnsafe, R> {
private final TUnsafe function;
private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;
private final CheckedWrapper<TSafe, TUnsafe, R> root;
private CheckedWrapper<TSafe, TUnsafe, R> next;
private Consumer<Throwable> handlers = ex -> { };
private Consumer<Throwable> lastHandlers = ex -> { };
CheckedWrapper(TUnsafe function,
ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
this.function = function;
this.reduceFunction = reduceFunction;
this.root = this;
}
private CheckedWrapper(TUnsafe function,
CheckedWrapper<TSafe, TUnsafe, R> prev) {
this.function = function;
this.reduceFunction = prev.reduceFunction;
this.root = prev.root;
prev.next = this;
}
@Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
return new CheckedWrapper<>(next, this);
}
@Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
Consumer<Throwable> handler) {
handlers = handlers.andThen(handler);
return this;
}
@Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R>
handle(Class<E> exceptionType, Consumer<E> handler) {
handlers = handlers.andThen(ex -> {
if (exceptionType.isInstance(ex)) {
handler.accept(exceptionType.cast(ex));
}
});
return this;
}
@Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
Consumer<Throwable> handler) {
lastHandlers = lastHandlers.andThen(handler);
return this;
}
@Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R>
handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
lastHandlers = lastHandlers.andThen(ex -> {
if (exceptionType.isInstance(ex)) {
handler.accept(exceptionType.cast(ex));
}
});
return this;
}
@Override public TSafe unsafe() {
return root.reduce(ex -> Try.throwAsUnchecked(ex));
}
@Override
public TSafe rethrow(Function<Throwable, Exception> transformer) {
return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
}
@Override public TSafe suppress() {
return root.reduce(ex -> null);
}
@Override public TSafe orElse(R value) {
return root.reduce(ex -> value);
}
@Override public TSafe orElseGet(Supplier<R> valueProvider) {
Objects.requireNonNull(valueProvider);
return root.reduce(ex -> valueProvider.get());
}
private TSafe reduce(Function<Throwable, R> orResult) {
return reduceFunction.wrap(function,
Optional.ofNullable(next).map(p -> p.reduce(orResult)),
this::handle, orResult);
}
private void handle(Throwable ex) {
for (CheckedWrapper<TSafe, TUnsafe, R> current = this;
current != null;
current = current.next) {
current.handlers.accept(ex);
}
lastHandlers.accept(ex);
}
}
TL; DR Lombokの@SneakyThrows
を使うだけです。
Christian Hujerは、ストリームからチェック済み例外をスローすることが厳密にはJavaの制限のため不可能である理由をすでに詳しく説明しました。
他の回答の中には、言語の制限を回避するためのトリックを説明していますが、それでも「チェック済み例外自体をスローし、ストリームに醜いtry/catchを追加することなく」という要件を満たすことができるものがあります。、そのうちのいくつかは定型句の何十もの追加行を必要とします。
私がこれを行うためのもう1つの選択肢を強調します。私見は他のすべてのものよりはるかにきれいです:Lombokの@SneakyThrows
。それは他の答えを通り過ぎることで言及されました、しかし多くの不必要な詳細の下で少し埋められました。
結果のコードは以下のように単純です。
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String")
.map(className -> getClass(className))
.collect(Collectors.toList());
return classes;
}
@SneakyThrows // <= this is the only new code
private Class<?> getClass(String className) {
return Class.forName(className);
}
IDEによって行われる1つのExtract Method
リファクタリングと、@SneakyThrows
のone追加の行が必要です。アノテーションはすべての定型句を追加して、RuntimeException
にラップしなくても明示的に宣言する必要なく、チェック済み例外を確実にスローできるようにします。
おそらく、より良く、より機能的な方法は、例外をラップしてそれらをストリーム内でさらに伝搬させることです。例えば、 Try タイプの Vavr を見てください。
例:
interface CheckedFunction<I, O> {
O apply(I i) throws Exception; }
static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
return i -> {
try {
return f.apply(i);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
} }
fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))
OR
@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
throw (E) e;
}
static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
return arg -> {
try {
return f.apply(arg);
} catch(Exception ex) {
return throwUnchecked(ex);
}
};
}
2番目の実装では、RuntimeException
で例外をラップしないようにします。 throwUnchecked
は、ほとんどすべての一般的な例外がJavaでは未チェックとして扱われるため、機能します。
上記のコメントに同意します。Stream.mapを使用する際には、例外をスローしないFunctionの実装に限定されます。
ただし、以下のようにスローされる独自のFunctionalInterfaceを作成することもできます。
@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
void accept(T instance) throws X;
}
次に、以下に示すように、ラムダまたは参照を使用してそれを実装します。
import Java.io.FileWriter;
import Java.io.IOException;
//lambda expressions and the execute around method (EAM) pattern to
//manage resources
public class FileWriterEAM {
private final FileWriter writer;
private FileWriterEAM(final String fileName) throws IOException {
writer = new FileWriter(fileName);
}
private void close() throws IOException {
System.out.println("close called automatically...");
writer.close();
}
public void writeStuff(final String message) throws IOException {
writer.write(message);
}
//...
public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {
final FileWriterEAM writerEAM = new FileWriterEAM(fileName);
try {
block.accept(writerEAM);
} finally {
writerEAM.close();
}
}
public static void main(final String[] args) throws IOException {
FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));
FileWriterEAM.use("eam2.txt", writerEAM -> {
writerEAM.writeStuff("how");
writerEAM.writeStuff("sweet");
});
FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);
}
void writeIt() throws IOException{
this.writeStuff("How ");
this.writeStuff("sweet ");
this.writeStuff("it is");
}
}
私はこのアプローチが正しいと思います。
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes;
try {
classes = Stream.of("Java.lang.Object", "Java.lang.Integer", "Java.lang.String").map(className -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new UndeclaredThrowableException(e);
}
}).collect(Collectors.toList());
} catch (UndeclaredThrowableException e) {
if (e.getCause() instanceof ClassNotFoundException) {
throw (ClassNotFoundException) e.getCause();
} else {
// this should never happen
throw new IllegalStateException(e.getMessage(), e);
}
}
return classes;
}
チェックされた例外をCallable
内のUndeclaredThrowableException
内にラップし(これがこの例外のユースケースです)、外側にラップ解除します。
ええ、私はそれが醜いと思います、そしてこの場合はラムダを使用しないことをお勧めします。パラレルストリームと並列処理で作業しているのでなければコードの判読不能性を正当化する客観的な利益をもたらさない限り。
他の多くの人が指摘したように、この状況に対する解決策があります、そしてそれらのうちの1つが将来のバージョンのJavaになることを願っています。
チェックされていない例外をラップするためのラッパーメソッドを書くこともできますし、(同じ戻り型を持つ)別の機能インターフェイスを表す追加パラメータでラッパーを拡張することもできます R)この場合、例外の場合に実行され返される関数を渡すことができます。下記の例を参照してください。
private void run() {
List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
System.out.println(list.toString());
}
private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function,
Function<T, R> onException) {
return i -> {
try {
return function.apply(i);
} catch (ArithmeticException e) {
System.out.println("Exception: " + i);
return onException.apply(i);
} catch (Exception e) {
System.out.println("Other: " + i);
return onException.apply(i);
}
};
}
@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
R apply(T t) throws E;
}
map
操作によってスローされる可能性があるチェック済み例外を処理する唯一の組み込みの方法は、それらを CompletableFuture
の中にカプセル化することです。 (例外を保存する必要がない場合は、( Optional
を使用するのがより簡単な方法です。)これらのクラスを使用して条件付き操作を表現できます。機能的な方法です。
いくつかの簡単ではないヘルパーメソッドが必要ですが、ストリームの結果がmap
操作が正常に完了したことを条件としていることを明らかにしながら、比較的簡潔なコードに到達できます。これは次のようなものです。
CompletableFuture<List<Class<?>>> classes =
Stream.of("Java.lang.String", "Java.lang.Integer", "Java.lang.Double")
.map(MonadUtils.applyOrDie(Class::forName))
.map(cfc -> cfc.thenApply(Class::getSuperclass))
.collect(MonadUtils.cfCollector(ArrayList::new,
List::add,
(List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
x -> x));
classes.thenAccept(System.out::println)
.exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });
これにより、次のような出力が生成されます。
[class Java.lang.Object, class Java.lang.Number, class Java.lang.Number]
applyOrDie
メソッドは、例外をスローするFunction
を受け取り、それを既に完了したFunction
を返すCompletableFuture
に変換します。関数の結果、またはスローされた例外で例外的に完了しました。
2番目のmap
操作は、単なるStream<CompletableFuture<T>>
ではなくStream<T>
を取得したことを示しています。 CompletableFuture
は、アップストリーム操作が成功した場合にのみ、この操作を実行します。 APIはこれを明確にしますが、比較的簡単です。
collect
フェーズに到達するまで、つまりです。これが、非常に重要なヘルパーメソッドが必要なところです。 CompletableFuture
- toList()
の内側にある通常のコレクション操作(この場合はcfCollector()
)を「持ち上げる」ことで、supplier
を使用することができます。 accumulator
、combiner
、およびfinisher
は、CompletableFuture
について何も知っておく必要はありません。
ヘルパーメソッドはGitHubのmy MonadUtils
クラスにありますが、これはまだ進行中の作業です。
私はこの種のラッピング例外を使用します。
public class CheckedExceptionWrapper extends RuntimeException {
...
public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
throw (T) getCause();
}
}
これらの例外を静的に処理する必要があります。
void method() throws IOException, ServletException {
try {
list.stream().forEach(object -> {
...
throw new CheckedExceptionWrapper(e);
...
});
} catch (CheckedExceptionWrapper e){
e.<IOException>rethrow();
e.<ServletExcepion>rethrow();
}
}
とにかく最初のrethrow()
呼び出しの間に例外が再スローされます(ああ、Javaジェネリック...)、この方法は可能な例外の厳密な静的な定義を取得することを可能にします(throws
でそれらを宣言する必要があります)。そしてinstanceof
や何かは必要ありません。
これが元の問題に対する別の見解または解決策です。ここでは、例外がスローされたときにケースを検出して処理するオプションを持つ値の有効なサブセットのみを処理するコードを書くオプションがあることを示します。
@Test
public void getClasses() {
String[] classNames = {"Java.lang.Object", "Java.lang.Integer", "Java.lang.Foo"};
List<Class> classes =
Stream.of(classNames)
.map(className -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
// log the error
return null;
}
})
.filter(c -> c != null)
.collect(Collectors.toList());
if (classes.size() != classNames.length) {
// add your error handling here if needed or process only the resulting list
System.out.println("Did not process all class names");
}
classes.forEach(System.out::println);
}