web-dev-qa-db-ja.com

Java 8:ラムダ式での必須のチェック済み例外処理。なぜ必須であり、オプションではないのか?

Java 8の新しいラムダ機能で遊んでいますが、Java 8が提供するプラクティスが本当に役立つことがわかりました。しかし、私は疑問に思っています。次のシナリオの回避策を作成するgood方法があります。満たすために何らかのファクトリを必要とするオブジェクトプールラッパーがあるとしますたとえば、オブジェクトプール(_Java.lang.functions.Factory_を使用):

_public class JdbcConnectionPool extends ObjectPool<Connection> {

    public ConnectionPool(int maxConnections, String url) {
        super(new Factory<Connection>() {
            @Override
            public Connection make() {
                try {
                    return DriverManager.getConnection(url);
                } catch ( SQLException ex ) {
                    throw new RuntimeException(ex);
                }
            }
        }, maxConnections);
    }

}
_

関数インターフェイスをラムダ式に変換すると、上記のコードは次のようになります。

_public class JdbcConnectionPool extends ObjectPool<Connection> {

    public ConnectionPool(int maxConnections, String url) {
        super(() -> {
            try {
                return DriverManager.getConnection(url);
            } catch ( SQLException ex ) {
                throw new RuntimeException(ex);
            }
        }, maxConnections);
    }

}
_

それほど悪くはありませんが、チェック済み例外_Java.sql.SQLException_には、ラムダ内にtry/catchブロックが必要です。私の会社では、2つのインターフェイスを長い間使用しています。

  • _IOut<T>_は、_Java.lang.functions.Factory_と同等です。
  • 通常、チェック例外の伝播を必要とするケース用の特別なインターフェイス:interface IUnsafeOut<T, E extends Throwable> { T out() throws E; }

_IOut<T>_と_IUnsafeOut<T>_の両方は、Java 8への移行中に削除されることになっていますが、_IUnsafeOut<T, E>_に完全に一致するものはありません。チェックされていないようにチェック例外を処理する場合、上記のコンストラクタで次のように単純に使用することができます。

_super(() -> DriverManager.getConnection(url), maxConnections);
_

それはずっときれいに見えます。 _IUnsafeOut<T>_を受け入れるようにObjectPoolスーパークラスを書き換えることができますが、私が知る限り、Java 8はまだ終了していないので、次のような変更があります。

  • _IUnsafeOut<T, E>_に似たものを実装していますか? (正直なところ、私は汚いと考えています-サブジェクトは受け入れるものを選択する必要があります:Factoryまたは互換性のないメソッドシグネチャを持たない「安全でないファクトリ」)
  • ラムダのチェック例外を単に無視するので、_IUnsafeOut<T, E>_サロゲートは不要ですか? (なぜですか?例えば、私が使用する別の重要な変更:OpenJDK、javacは、匿名クラス[機能インターフェース]でキャプチャされるfinalとして宣言される変数とパラメーターを必要としません。ラムダ式)

質問は一般に次のとおりです。ラムダでチェック例外をバイパスする方法はありますか、またはJava 8が最終的にリリースされるまで将来的に計画されていますか?


アップデート1

Hm-m-m、私たちが現在持っていることを理解している限り、参照されている記事は2010年以降のものですが、現時点では方法がないようです: Brian GoetzはJavaの例外透過性について説明しています 。 Java 8でほとんど何も変わっていない場合、これは答えと見なすことができます。また、ブライアンは_interface ExceptionalCallable<V, E extends Exception>_(コードレガシーから_IUnsafeOut<T, E extends Throwable>_と言ったもの)はほとんど役に立たない、と私は彼に同意します。

私はまだ何か他のものが恋しいですか?

69

あなたの質問に本当に答えているかどうかはわかりませんが、そのようなものを単純に使用することはできませんか?

public final class SupplierUtils {
    private SupplierUtils() {
    }

    public static <T> Supplier<T> wrap(Callable<T> callable) {
        return () -> {
            try {
                return callable.call();
            }
            catch (RuntimeException e) {
                throw e;
            }
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }
}

public class JdbcConnectionPool extends ObjectPool<Connection> {

    public JdbcConnectionPool(int maxConnections, String url) {
        super(SupplierUtils.wrap(() -> DriverManager.getConnection(url)), maxConnections);
    }
}
43
JB Nizet

ラムダメーリングリストでは、これは 徹底的に議論された でした。あなたが見ることができるように、ブライアン・ゲッツはそこで代替案はあなた自身のコンビネーターを書くことであると示唆しました:

または、独自の簡単なコンビネータを作成できます。

static<T> Supplier<T> exceptionWrappingSupplier(Supplier<T> b) {
     return e -> {
         try { b.accept(e); }
         catch (Exception e) { throw new RuntimeException(e); }
     };
}

元の電子メールを書くのにかかった時間よりも短い時間で、一度書くことができます。同様に、使用するSAMの種類ごとに1回。

代わりにこれを「99%満たされたガラス」と見なします。すべての問題がソリューションとして新しい言語機能を必要とするわけではありません。 (言うまでもなく、新しい言語機能は常に新しい問題を引き起こします。)

当時、ConsumerインターフェイスはBlockと呼ばれていました。

これは JB Nizetの答え と一致すると思います。

後でブライアン 理由を説明する これはこのように設計された(問題の理由)

はい、独自の例外的なSAMを提供する必要があります。しかし、ラムダ変換はそれらでうまく動作します。

EGは、この問題に対する追加の言語およびライブラリのサポートについて議論し、最終的にはこれが悪いコスト/利益のトレードオフであると感じました。

ライブラリベースのソリューションは、SAMタイプで2倍の爆発を引き起こします(例外的とそうでない)。これは、プリミティブな特殊化のために既存のコンビナトリアル爆発と相互作用します。

利用可能な言語ベースのソリューションは、複雑さ/価値のトレードオフからの敗者でした。いくつかの代替ソリューションがありますが、私たちは探求し続けますが、明らかに8のためではなく、おそらく9のためでもありません。

それまでの間、あなたはあなたがしたいことをするためのツールを持っています。ラストマイルを提供する方が良いと思います(そして、第二に、あなたのリクエストは本当に「すでにチェック済みの例外をあきらめないのか?」あなたは仕事をやり遂げます。

34
Edwin Dalorzo

2015年9月:

これには [〜#〜] et [〜#〜] を使用できます。 ETは、例外変換/翻訳用の小さなJava 8ライブラリです。

ETを使用すると、次のことができます。

super(() -> et.withReturningTranslation(() -> DriverManager.getConnection(url)), maxConnections);

複数行バージョン:

super(() -> {
  return et.withReturningTranslation(() -> DriverManager.getConnection(url));
}, maxConnections);

前に行う必要があるのは、新しいExceptionTranslatorインスタンスを作成することだけです:

ExceptionTranslator et = ET.newConfiguration().done();

このインスタンスはスレッドセーフであり、複数のコンポーネントで共有できます。より具体的な例外変換ルールを設定できます(例:FooCheckedException -> BarRuntimeException) もし良かったら。他のルールが利用できない場合、チェックされた例外は自動的にRuntimeExceptionに変換されます。

(免責事項:私はETの著者です)

5
micha

RuntimeException(未チェック)ラッパークラスを使用してラムダ式から元の例外を密輸し、ラップされた例外backを元のチェック済み例外にキャストすることを検討しましたか?

class WrappedSqlException extends RuntimeException {
    static final long serialVersionUID = 20130808044800000L;
    public WrappedSqlException(SQLException cause) { super(cause); }
    public SQLException getSqlException() { return (SQLException) getCause(); }
}

public ConnectionPool(int maxConnections, String url) throws SQLException {
    try {
        super(() -> {
            try {
                return DriverManager.getConnection(url);
            } catch ( SQLException ex ) {
                throw new WrappedSqlException(ex);
            }
        }, maxConnections);
    } catch (WrappedSqlException wse) {
        throw wse.getSqlException();
    }
}

独自の一意のクラスを作成することで、例外をキャッチして再スローする前にパイプラインのどこかでシリアル化されている場合でも、ラムダでラップした例外を別の未チェックの例外と間違えないようにする必要があります。

うーん...ここで問題になっているのは、コンストラクタ内でこれを行うことです。super()の呼び出しは、法律でコンストラクタの最初のステートメントでなければなりません。 tryは前のステートメントとしてカウントされますか?私は自分のコードでこれを(コンストラクタなしで)動作させています。

4
GlenPeterson

私たちはこれを支援する社内プロジェクトを開発しました。 2か月前に公開することにしました。

これが私たちが思いついたものです:

@FunctionalInterface
public interface ThrowingFunction<T,R,E extends Throwable> {
R apply(T arg) throws E;

/**
 * @param <T> type
 * @param <E> checked exception
 * @return a function that accepts one argument and returns it as a value.
 */
static <T, E extends Exception> ThrowingFunction<T, T, E> identity() {
    return t -> t;
}

/**
 * @return a Function that returns the result of the given function as an Optional instance.
 * In case of a failure, empty Optional is returned
 */
static <T, R, E extends Exception> Function<T, Optional<R>> lifted(ThrowingFunction<T, R, E> f) {
    Objects.requireNonNull(f);

    return f.lift();
}

static <T, R, E extends Exception> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
    Objects.requireNonNull(f);

    return f.uncheck();
}

default <V> ThrowingFunction<V, R, E> compose(final ThrowingFunction<? super V, ? extends T, E> before) {
    Objects.requireNonNull(before);

    return (V v) -> apply(before.apply(v));
}

default <V> ThrowingFunction<T, V, E> andThen(final ThrowingFunction<? super R, ? extends V, E> after) {
    Objects.requireNonNull(after);

    return (T t) -> after.apply(apply(t));
}

/**
 * @return a Function that returns the result as an Optional instance. In case of a failure, empty Optional is
 * returned
 */
default Function<T, Optional<R>> lift() {
    return t -> {
        try {
            return Optional.of(apply(t));
        } catch (Throwable e) {
            return Optional.empty();
        }
    };
}

/**
 * @return a new Function instance which wraps thrown checked exception instance into a RuntimeException
 */
default Function<T, R> uncheck() {
    return t -> {
        try {
            return apply(t);
        } catch (final Throwable e) {
            throw new WrappedException(e);
        }
    };
}

}

https://github.com/TouK/ThrowingFunction/

3

Paguroは、チェックされた例外をラップする機能インターフェイスを提供します 。あなたが質問をしてから数ヶ月後に私はそれに取り組み始めたので、おそらくあなたはそれのインスピレーションの一部でした!

Paguroには、Java 8に含まれる43個のインターフェイスがあるのに対して、4個の機能インターフェイスしかありません。これは、Paguroがプリミティブよりもジェネリックを好むためです。

Paguroには、その不変コレクション(Clojureからコピー)にシングルパス変換が組み込まれています。これらの変換は、ClojureトランスデューサーまたはJava 8ストリームとほぼ同等ですが、チェック例外をラップする機能インターフェースを受け入れます。参照: PaguroとJava 8ストリーム

1
GlenPeterson

説明した方法で例外をラップしても機能しません。私はそれを試しましたが、実際には仕様に従っているコンパイラエラーが発生します:ラムダ式は、メソッド引数のターゲット型と互換性のない例外をスローします:Callable; call()はそれをスローしないので、ラムダ式をCallableとして渡すことはできません。

したがって、基本的に解決策はありません。定型文を書くことにこだわっています。できることは、これを修正する必要があるという意見を表明することだけです。仕様は、互換性のないスローされた例外に基づいてターゲットタイプを盲目的に破棄するだけではなく、スローされた互換性のない例外が呼び出しスコープでスローとしてキャッチまたは宣言されているかどうかを確認する必要があります。そして、インライン化されていないラムダ式については、それらをサイレントにチェック例外をスローするようにマークできることを提案します(コンパイラーはチェックしないが、ランタイムはキャッチするという意味でサイレント)。これを議論サイトではないことを知っていますが、これはディスカッションサイトではありませんが、このISが唯一の解決策なので、聞いてこの仕様を変更しましょう!

canラムダから投げることができます。ラムダは「あなた自身の方法で」宣言する必要があります(残念ながら、それらは標準で再利用できませんJDKコード、でもね、できることはやります)。

@FunctionalInterface
public interface SupplierIOException {
   MyClass get() throws IOException;
}

または、より一般化されたバージョン:

public interface ThrowingSupplier<T, E extends Exception> {
  T get() throws E;
}

ref ここ 。また、「sneakyThrow」を使用して、チェック済み例外を宣言せずにスローすることについての言及もあります。それは私の頭を少し傷つけますが、オプションかもしれません。

0
rogerdpack