24

誰かがこのコードを説明できますか?

public class SneakyThrow {


  public static void sneakyThrow(Throwable ex) {
    SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
  }

  private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
    throw (T) ex;
  }



  public static void main(String[] args) {
    SneakyThrow.sneakyThrow(new Exception());
  }


}

奇妙に思えるかもしれませんが、これはキャスト例外を生成せず、署名で宣言することなくチェック例外をスローしたり、チェックされていない例外にラップしたりすることを許可します。

どちらもsneakyThrow(...)メインもチェック例外を宣言していませんが、出力は次のようになります。

Exception in thread "main" java.lang.Exception
    at com.xxx.SneakyThrow.main(SneakyThrow.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

このハックは Lombok で使用され、注釈 @SneakyThrow を使用して、チェック済みの例外を宣言せずにスローできるようにします。


型消去と関係があることは知っていますが、ハックのすべての部分を理解しているかどうかはわかりません。


編集: を挿入できることIntegerList<String>およびチェックされた/チェックされていない例外の区別がコンパイル時の機能であることを知っています。

コンパイラのListような非ジェネリック型からジェネリック型にキャストすると、警告が生成されます。ただし、上記のコードのList<XXX>ようにジェネリック型に直接キャストすることはあまり一般的ではありません。(T) ex

必要に応じて、私にとって奇妙に思える部分は、JVM 内で aList<Dog>List<Cat>同じように見えることを理解していることですが、上記のコードは、最終的に型 Cat の値を型 Dog または型の変数に割り当てることもできることを意味しているようですそんな感じ。

4

4 に答える 4

19

でコンパイルすると-Xlint、警告が表示されます。

c:\Users\Jon\Test>javac -Xlint SneakyThrow.java
SneakyThrow.java:9: warning: [unchecked] unchecked cast
    throw (T) ex;
              ^
  required: T
  found:    Throwable
  where T is a type-variable:
    T extends Throwable declared in method <T>sneakyThrowInner(Throwable)
1 warning

これは基本的に、「このキャストは実行時に実際にはチェックされていませ」(型消去のため) と言っているのです。したがって、コンパイラは、実際にはチェックされないことを知って、あなたが正しいことをしているとしぶしぶ想定します。

現在、チェックされた例外とチェックされていない例外を気にするのはコンパイラだけであり、JVM の一部ではありません。したがって、コンパイラを通過したら、家から解放されます。

ただし、これを避けることを強くお勧めします。

多くの場合、ジェネリックを使用しているときに「実際の」チェックがあります。これは、何かが目的の型を使用しているためです。ただし、常にそうであるとは限りません。例えば:

List<String> strings = new ArrayList<String>();
List raw = strings;
raw.add(new Object()); // Haha! I've put a non-String in a List<String>!
Object x = strings.get(0); // This doesn't need a cast, so no exception...
于 2012-12-26T09:45:40.333 に答える
9

上記のコードは、最終的に Cat 型の値を Dog 型などの変数に代入できることを意味しているようです。

クラスがどのように構成されているかという観点から考える必要があります。T extends ThrowableそしてあなたはExceptionそれに行きます。これは、 を に代入DogするようなAnimalものDogですCat

コンパイラには、継承に基づいて、どの Throwable がチェックされ、どれがチェックされないかに関するルールがあります。これらはコンパイル時に適用され、コンパイラを混乱させてチェック例外をスローさせる可能性があります。実行時には、これは影響しません。


チェック例外はコンパイル時の機能です (ジェネリックと同様)

BTW Throwable もチェック例外です。サブクラス化すると、Error または RuntimeException のサブクラスでない限りチェックされます。

コンパイラがこれを行っていることを認識せずに、チェック例外をスローする他の 2 つの方法。

Thread.currentThread().stop(throwable);

Unsafe.getUnsafe().throwException(throwable);

唯一の違いは、どちらもネイティブ コードを使用することです。

于 2012-12-26T10:07:44.690 に答える
0

以下のコードを見てみましょう。

public class SneakyThrow {


  public static void sneakyThrow(Throwable ex) {
    SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
  }

  private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
    throw (T) ex;
  }



  public static void main(String[] args) {
    SneakyThrow.sneakyThrow(new Exception());
  }


}

なぜこのような振る舞いをするのか見てみましょう。

まず、コンパイラによって無視される型パラメーターへのキャスト。JVM はそれを直接キャストします。そのthrow (T) ex;ため、コンパイラによるタイプ セーフのチェックは行われません。しかし、コードが JVM に到達するまでに、型の消去が行われます。したがって、コードは次のようになります: [注: 実際のコードはバイトコードになります。以下は、型消去が何をするかを説明するためのものです。]

public class SneakyThrow {


    public static void sneakyThrow(Throwable ex) {
        SneakyThrow.sneakyThrowInner(ex); // Note : Throwable is checked exception but we don't mention anything here for it
    }

    private static  Throwable sneakyThrowInner(Throwable ex) throws Throwable {
        throw (Throwable) ex;
    }


    public static void main(String[] args) {
        SneakyThrow.sneakyThrow(new Exception());

    }


}

最初に注意すべきことは、すべてがスムーズに実行され、ClassCastException がスローされないことです。これは、JVM が に簡単に型キャストできるexためThrowableです。

ここで 2 番目に注意すべきことは、Compiler は常に、受信したチェック済み例外をキャッチまたは渡すことを強制していたことです。そのようなチェックは JVM によって行われません。ここで、キャストされた後、理想的にSneakyThrow.sneakyThrowInner(ex);は強制的にThrowable例外を処理する必要がありますが、このバージョンのバイト コードは JVM に到達します。したがって、何らかの方法でコンパイラをだましました。

このようにしてください:

public class SneakyThrow {


    public static void sneakyThrow(Throwable ex) {
        SneakyThrow.<RuntimeException>sneakyThrowInner(ex);
    }

    private static <T extends Throwable> T sneakyThrowInner(Throwable ex) throws T {
        throw (T) ex;
    }


    public static void main(String[] args) {
        try {
            SneakyThrow.sneakyThrow(new Exception());
        }catch (Throwable ex){
            System.out.println("Done Succesfully"); // Added to show everything runs fine
        }
    }


}

出力: 正常に完了

于 2018-07-22T14:06:30.467 に答える