29

私は最近、javac コンパイラーを介してチェック済み例外をこっそりとスローして、スローしてはならない場所にスローすることが可能であるという事実を発見し、ブログに書きました。これは Java 6 および 7 でコンパイルおよび実行され、with or 句がスローさSQLExceptionthrowsますcatch

public class Test {

    // No throws clause here
    public static void main(String[] args) {
        doThrow(new SQLException());
    }

    static void doThrow(Exception e) {
        Test.<RuntimeException> doThrow0(e);
    }

    static <E extends Exception> void doThrow0(Exception e) throws E {
        throw (E) e;
    }
}

生成されたバイトコードは、JVM がチェック済み/未チェックの例外をあまり気にしていないことを示しています。

// Method descriptor #22 (Ljava/lang/Exception;)V
// Stack: 1, Locals: 1
static void doThrow(java.lang.Exception e);
  0  aload_0 [e]
  1  invokestatic Test.doThrow0(java.lang.Exception) : void [25]
  4  return
    Line numbers:
      [pc: 0, line: 11]
      [pc: 4, line: 12]
    Local variable table:
      [pc: 0, pc: 5] local: e index: 0 type: java.lang.Exception

// Method descriptor #22 (Ljava/lang/Exception;)V
// Signature: <E:Ljava/lang/Exception;>(Ljava/lang/Exception;)V^TE;
// Stack: 1, Locals: 1
static void doThrow0(java.lang.Exception e) throws java.lang.Exception;
  0  aload_0 [e]
  1  athrow
    Line numbers:
      [pc: 0, line: 16]
    Local variable table:
      [pc: 0, pc: 2] local: e index: 0 type: java.lang.Exception

これを受け入れるJVMは1つのことです。しかし、Java-the-languageがそうあるべきかどうか、私には疑問があります。JLS のどの部分がこの動作を正当化しますか? バグですか?それとも、Java 言語の隠れた「機能」ですか?

私の気持ちは次のとおりです。

  • doThrow0()はin に<E>バインドされています。したがって、JLS §11.2の行に沿った節は では必要ありません。RuntimeExceptiondoThrow()throwsdoThrow()
  • RuntimeExceptionは との代入互換性があるため、コンパイラによってExceptionキャスト (結果として) が生成されません。ClassCastException
4

5 に答える 5

9

これはすべて、ジェネリック型へのチェックされていないキャストがコンパイラエラーではないという抜け穴を利用することになります。そのような式が含まれている場合、コードは明示的にタイプセーフになります。また、チェックされた例外のチェックは厳密にコンパイル時の手順であるため、実行時に何も中断されません。

Genericsの作者からの答えは、おそらく「チェックされていないキャストを使用している場合、それはあなたの問題です」という線に沿っているでしょう。

あなたの発見には非常に前向きなものがあります。チェックされた例外の拠点からの脱却です。残念ながら、これでは、既存の例外がチェックされたAPIをより使いやすいものに変えることはできません。

これがどのように役立つか

私の典型的な階層型アプリケーションプロジェクトでは、次のような定型文がたくさんあります。

try {
  ... business logic stuff ...
} 
catch (RuntimeException e) { throw e; } 
catch (Exception e) { throw new RuntimeException(e); }

なぜ私はそれをするのですか?シンプル:キャッチするビジネス価値の例外はありません。例外は、ランタイムエラーの症状です。例外は、グローバル例外バリアに向けてコールスタックを伝播する必要があります。Lukasのすばらしい貢献により、私は今書くことができます

try {
  ... business logic stuff ... 
} catch (Exception e) { throwUnchecked(e); }

これはそれほど多くはないように思われるかもしれませんが、プロジェクト全体で100回繰り返すと、メリットが蓄積されます。

免責事項

私のプロジェクトでは、例外に関して高い規律があるので、これはそれらにぴったりです。この種のトリックは、一般的なコーディングの原則として採用するものではありません。多くの場合、例外をラップすることが依然として唯一の安全なオプションです。

于 2012-09-25T10:34:47.673 に答える
2

これは、チェックされていないかのようにチェック例外を発生させる複数の方法の 1 つです。Class.newInstance()別の、Thread.stop(Trowable)廃止されたものです。

JLS がこの動作を受け入れない唯一の方法は、ランタイム (JVM) が強制する場合です。

指定されているかどうか: 指定されていません。チェックされた例外とチェックされていない例外は同じように動作します。チェックされた例外には、単に catch ブロックまたは throws 句が必要です。

編集:コメントの議論に基づいて、根本的な原因を公開するリストベースの例:消去

public class Main {
    public static void main(String[] args) {
        List<Exception> myCheckedExceptions = new ArrayList<Exception>();
        myCheckedExceptions.add(new IOException());

        // here we're tricking the compiler
        @SuppressWarnings("unchecked")
        List<RuntimeException> myUncheckedExceptions = (List<RuntimeException>) (Object) myCheckedExceptions;

        // here we aren't any more, at least not beyond the fact that type arguments are erased
        throw throwAny(myUncheckedExceptions);
    }

    public static <T extends Throwable> T throwAny(Collection<T> throwables) throws T {
        // here the compiler will insert an implicit cast to T (just like your explicit one)
        // however, since T is a type variable, it gets erased to the erasure of its bound
        // and that happens to be Throwable, so a useless cast...
        throw throwables.iterator().next();
    }
}
于 2012-09-25T10:11:40.150 に答える
1

JLS 11.2:

考えられる結果であるチェックされた例外ごとに、メソッド(§8.4.6)またはコンストラクター(§8.8.5)のthrows節は、その例外のクラスまたはその例外のクラスのスーパークラスの1つ(§ 11.2.3)。

これは、doThrowのthrows句に例外が必要であることを明確に示しています。または、ダウンキャスト(RuntimeExceptionへの例外)が関係しているため、例外がRuntimeExceptionであるかどうかを確認する必要があります。これは、キャストされる例外がSQLExceptionであるため、たとえば失敗するはずです。したがって、ClassCastExceptionは実行時にスローされる必要があります。

実用面では、このJavaバグにより、次のような標準の例外処理コードに対して安全でないハックを作成できます。

try {
 doCall();
} catch (RuntimeException e) {
 handle();
}

例外は処理されずに発生します。

于 2012-09-25T10:50:00.587 に答える
1

JLSがそのコードを許可していることに異議を唱えることはできないと思います。問題は、そのコードが合法であるべきかどうかではありません。コンパイラには、エラーを発行するのに十分な情報がありません。

ジェネリック スロー メソッドを呼び出したときに出力されるコードの問題です。コンパイラは現在、戻り値がすぐに参照に割り当てられるジェネリックを返すメソッドを呼び出した後に型キャストを挿入します。

コンパイラが必要に応じて、try-catch-rethrow フラグメントですべてのジェネリック スロー メソッド呼び出しを静かに囲むことで、問題を防ぐことができます。例外がキャッチされてローカル変数に割り当てられるため、型キャストが必須になります。したがってClassCastException、 のインスタンスでない場合は が得られますRuntimeException

しかし、仕様では、ジェネリック スロー メソッドについて特別なことは何も義務付けられていません。仕様のバグだと思います。それについてバグを報告して、修正するか、少なくとも文書化できるようにしてください。

ちなみに、ClassCastException元の例外が抑制されるため、見つけにくいバグが発生する可能性があります。しかし、その問題にはJDK 7以降の簡単な解決策があります。

于 2012-09-25T14:40:15.270 に答える