Złapanie wyjątku i ponowne jego przepisanie, ale nie jest to wyjątek

10

Natknąłem się na kod wyglądający mniej więcej tak:

void run() {
    try {
        doSomething();
    } catch (Exception ex) {
        System.out.println("Error: " + ex);
        throw ex;
    }
}

void doSomething() {
    throw new RuntimeException();
}

Ten kod mnie zaskakuje, ponieważ wygląda na to, że run()-metoda jest w stanie wyrzucić Exception, ponieważ łapie, Exceptiona następnie ponownie rzuca, ale metoda nie jest zadeklarowana do rzucenia Exceptioni najwyraźniej nie musi być. Ten kod dobrze się kompiluje (przynajmniej w Javie 11).

Oczekuję , że będę musiał zadeklarować throws Exceptionw run()metodzie.

Dodatkowe informacje

W podobny sposób, jeśli doSomethingzostanie zadeklarowany jako rzut, IOExceptionto IOExceptionmusi być zadeklarowany tylko w run()metodzie, nawet jeśli Exceptionzostanie złapany i ponownie rzucony.

void run() throws IOException {
    try {
        doSomething();
    } catch (Exception ex) {
        System.out.println("Error: " + ex);
        throw ex;
    }
}

void doSomething() throws IOException {
    // ... whatever code you may want ...
}

Pytanie

Java zazwyczaj lubi jasność, jaki jest powód tego zachowania? Czy zawsze tak było? Co w specyfikacji języka Java pozwala run()metodzie nie musieć deklarować throws Exceptionw powyższych fragmentach kodu? (Gdybym dodał, IntelliJ ostrzega mnie, że Exceptionnigdy nie zostanie wyrzucony).

Simon Forsberg
źródło
3
Ciekawy. Z jakiego kompilatora korzystasz? Jeśli jest to kompilator IDE, sprawdź to javac- natknąłem się na przypadki, w których kompilator Eclipse był bardziej łagodny.
M. Prochorow
2
Mogę odtworzyć to zachowanie na openjdk-8. Szczególnie kompilacja z -source 1.6flagą powoduje błąd kompilacji zgodnie z oczekiwaniami. Kompilacja z kompatybilności źródło 7 czy nie podnieść błąd kompilacji
Vogel612
1
wygląda na to, że kompilator jest mądrzejszy od Java 7 i wykonuje więcej kontroli rzeczywistego wyjątku, który może zostać zgłoszony.
michalk
2
To pytanie nie jest duplikatem, a odpowiedź można znaleźć w linku, który In detail, in Java SE 7 and later, when you declare one or more exception types in a catch clause, and rethrow the exception handled by this catch block, the compiler verifies that the type of the rethrown exception meets the following conditions : 1. 1. The try block is able to throw it. 2. There are no other preceding catch blocks that can handle it. 3. It is a subtype or supertype of one of the catch clause's exception parameters.
podałem
2
Aktualnie zaznaczone duplikat jest na pewno właściwe, ale nie zapewnia wystarczająco szczegółowe odpowiedzi IMO. W komentarzach do odpowiedzi znajduje się jeden link do JLS , poza tym brak informacji.
Simon Forsberg

Odpowiedzi:

0

Nie skanowałem tego, JLSco zadałeś w swoim pytaniu, więc proszę, odpowiedz na to z odrobiną soli. Chciałem napisać komentarz, ale byłby zbyt duży.


Czasami javacwydaje mi się zabawne, jak to jest całkiem „inteligentne” w niektórych przypadkach (jak w twoim przypadku), ale pozostawia wiele innych rzeczy do załatwienia później JIT. W tym przypadku kompilator „może powiedzieć”, RuntimeExceptionże zostanie złapany tylko a . To oczywiste, że to jedyna rzecz, do której wrzucasz doSomething. Jeśli nieznacznie zmienisz kod na:

void run() {
    try {
        doSomething();
    } catch (Exception ex) {
        Exception ex2 = new Exception();
        System.out.println("Error: " + ex);
        throw ex2;
    }
}

zobaczysz inne zachowanie, ponieważ teraz javacmożesz stwierdzić, Exceptionże rzucasz nowe, niezwiązane z tym, które złapałeś.

Ale rzeczy są dalekie od ideału, możesz ponownie „oszukać” kompilator poprzez:

void run() {
    try {
        doSomething();
    } catch (Exception ex) {
        Exception ex2 = new Exception();
        ex2 = ex;
        System.out.println("Error: " + ex);
        throw ex2;
    }
}

IMO z ex2 = ex;tego powodu nie powinno ponownie zawieść, ale tak się dzieje.

Na wypadek gdyby zostało to skompilowane javac 13+33

Eugene
źródło
Przeczytałem w pewnym linku, że ktoś podał, że jeśli ponownie przypiszesz złapany wyjątek w bloku catch, kompilator nie będzie w stanie być inteligentny. Zakładam, że coś podobnego dotyczy w tym przypadku. Kompilator wie, że ex2wyjątek zostanie zgłoszony, został pierwotnie utworzony jako, Exceptionale następnie został ponownie przypisany ex, a zatem kompilator nie może być inteligentny.
Simon Forsberg
@ SimonForsberg JLSmoże przyjść ktoś, kto ma pasję i dostarczył potrzebne cytaty, aby to udowodnić; niestety ich nie mam.
Eugene
Dla przypomnienia, kiedy zmieniam blok catch, aby zawierał ponowne przypisanie przechwyconego wyjątku do siebie ( ex = ex;), heurystyka nie jest już stosowana. To zachowanie wydaje się być stosowane na wszystkich poziomach źródła od 7 do 11 i prawdopodobnie 13
Vogel612,
Spójrz na to pytanie, które jest również duplikatem. Ten i duplikat możliwego duplikatu to wyjaśnia, a także linki do JLS.
michalk