web-dev-qa-db-ja.com

Throwable initCauseが1回だけ呼び出されるように設計されているのはなぜですか?

JavaのinitCauseクラスのThrowableメソッドが1回しか呼び出せない、またはまったく呼び出せない(Throwableを受け入れるコンストラクターが使用されている場合)のは非常に奇妙です。このため、例外の連鎖は、私が思っているほど簡単ではありません。

_private Throwable cause = this;

public Throwable(String message, Throwable cause) {
    ...
    this.cause = cause;
}

public Throwable getCause() {
    return (cause==this ? null : cause);
}

public synchronized Throwable initCause(Throwable cause) {
    if (this.cause != this)
        throw new IllegalStateException("Can't overwrite cause");
    if (cause == this)
        throw new IllegalArgumentException("Self-causation not permitted");
    this.cause = cause;
    return this;
}
_

これは、メソッドmayがすでに呼び出されているEdgeケースを処理する必要がある醜い定型コードにつながりますが、必要なコードの部分それをもう一度呼び出すために、あなたはわからない。

さらに悪いことに、getCauseメソッドは、initCauseメソッドが呼び出されたかどうかを正しく通知しません。これは、他の誰かがinitCause(null)を呼び出した可能性があり、getCausenullを返すと同時にinitCauseIllegalStateException(_null != this_がtrueであるため)。

結果は、次のようなボイラープレートコードです。

_try {
    exp1.initCause(exp2);
} catch (IllegalStateException ise) {
    // do something or ignore
} catch (IllegalArgumentException iae) {
    // do something or ignore
}
_

一方、同じ例外であるか、メソッドがすでに呼び出されているかを気にせずに、単純にexp1.initCause(exp2);を呼び出すことができるはずです(そして、条件のいずれかがtrueの場合、メソッドが何をすべきかをメソッドに任せます;は単に新しい原因を無視します)。

そのような奇妙なデザインの背後にある理由は何ですか?

編集:

原因が不変であることに問題がないことを明確にしたいと思います。私の問題は、そのような不変性が違反されないようにすることは、実装によって自動的に処理されるのではなく、プログラマーにとって負担になることです。

原因が次のように実装されていれば、私にはもっと理にかなっているでしょう。

_private Throwable cause; // null means no cause

public Throwable(String message, Throwable cause) {
    ...
    this.cause = cause;
}

public Throwable getCause() {
    return cause;
}

public synchronized Throwable initCause(Throwable cause) {
    if (this.cause == null)
        this.cause = cause;
    return this;
}
_

または、たとえoriginalの実装が維持されていても、便利なメソッドが追加されている可能性があります。

_public boolean hasCause() {
    return (this.cause != this);
}
_

これで、ボイラープレートコードを単純に次のように減らすことができます。

_if (!exp1.hasCause()) exp1.initCause(exp2);
_

また、これはどのJava使用するバージョンであるか、またはJavaサポートのサポート終了に達しました。Javaの古いバージョンが "lazy to upgrade"または "セキュリティについて気にしない"。_:)_

3
ADTC

このような奇妙なデザインの背後にある理由は何ですか?

奇妙なデザインではありません。原因で遊んでいるユーザーコードは、常に良いよりも多くの害を引き起こし、原因の意味論を壊すので、原因は不変でなければなりません。

initCauseメソッドの存在についての説明は、Throwable javadocにあります。

initCauseメソッドはpublicであるため、例外をスローすることができる「レガシースローアブル」であっても、例外連鎖メカニズムをThrowableに追加する前にその原因を関連付けることができます。

確かに、例外チェーンはJava 1.4でのみ追加されています。それ以前は、例外は原因を持っていませんでした。

互換性の理由から、このメソッドを追加する必要がありました。コンストラクタでcauseパラメータを指定せずに古い例外を使用しても、後で原因を設定できます。

ユースケースに関しては、使用したいものは 抑制された例外 です。これらはJava 7に追加され、正確なユースケースを処理します(たとえば、try-with-catchのドキュメントを参照)。

廃止されたバージョンのJavaを使用している場合は、同様のメカニズムを独自の例外に追加できます(または廃止されていないバージョンにアップグレードします)。

本当に例外チェーンのセマンティクスを壊したい場合は、常に複合/ハッキングされた原因で新しい例外を作成してスローすることができます。

TL; DR:原因は不変である必要があるため、このチェックはinitCauseで行われます。原因をハッキングすることは、抑制された例外を処理する良い方法ではなく、通常多くの問題を引き起こします。これは、Javaバージョン7よりも古いバージョンの主な欠陥でした。連鎖例外の欠如は、Java 1.4。

5