Wyjątek wrzucony w catch i wreszcie klauzula


155

W pytaniu do Javy na uniwersytecie był taki fragment kodu:

class MyExc1 extends Exception {}
class MyExc2 extends Exception {}
class MyExc3 extends MyExc2 {}

public class C1 {
    public static void main(String[] args) throws Exception {
        try {
            System.out.print(1);
            q();
        }
        catch (Exception i) {
            throw new MyExc2();
        }
        finally {
            System.out.print(2);
            throw new MyExc1();
        }
    }

    static void q() throws Exception {
        try {
            throw new MyExc1();
        }
        catch (Exception y) {
        }
        finally {
            System.out.print(3);
            throw new Exception();
        }
    }
}

Poproszono mnie o przedstawienie wyników. Odpowiedziałem 13Exception in thread main MyExc2, ale prawidłowa odpowiedź brzmi 132Exception in thread main MyExc1. Dlaczego tak jest? Po prostu nie mogę zrozumieć, dokąd MyExc2zmierza.

Odpowiedzi:


167

Po przeczytaniu odpowiedzi i sprawdzeniu, w jaki sposób prawdopodobnie ją wymyśliłeś, sądzę, że uważasz, że „wyjątek w toku” ma „pierwszeństwo”. Pamiętać:

Gdy nowy wyjątek zostanie zgłoszony w bloku catch lub w bloku final który będzie propagowany z tego bloku, bieżący wyjątek zostanie przerwany (i zapomniany), gdy nowy wyjątek zostanie propagowany na zewnątrz. Nowy wyjątek rozpoczyna rozwijanie stosu, tak jak każdy inny wyjątek, przerywając działanie z bieżącego bloku (blok catch lub final) i podlegając po drodze stosownym blokom catch lub final.

Zwróć uwagę, że odpowiedni catch lub last blokuje obejmują:

Gdy nowy wyjątek zostanie zgłoszony w bloku catch, nowy wyjątek nadal podlega blokowi finalnemu tego catch, jeśli istnieje.

Teraz prześledź wykonanie, pamiętając, że po każdym trafieniu thrownależy przerwać śledzenie bieżącego wyjątku i rozpocząć śledzenie nowego wyjątku.


7
«Po przeczytaniu twojej odpowiedzi i zobaczeniu, jak prawdopodobnie ją wymyśliłeś, sądzę, że uważasz, że„ wyjątek będący w toku ”ma„ pierwszeństwo ”» Dziękuję ... tak właśnie pomyślałem :)
Jubstuff

39

Oto, o czym Wikipedia mówi o klauzuli końcowej:

Bardziej powszechna jest klauzula pokrewna (wreszcie lub upewnij się), która jest wykonywana niezależnie od tego, czy wystąpił wyjątek, czy nie, zwykle w celu zwolnienia zasobów uzyskanych w treści bloku obsługi wyjątków.

Przeanalizujmy Twój program.

try {
    System.out.print(1);
    q();
}

Tak, 1zostanie wyświetlona na ekranie, a następnie q()zostanie wywołana. W programie q()jest zgłaszany wyjątek. Wyjątek zostaje wtedy złapany, Exception yale nic nie robi. Końcu punkt jest następnie wykonywane (musi), więc 3będą wyświetlane na ekranie. Ponieważ (w sposób q()nie wyjątkiem wyrzucane w końcu klauzuli również q()sposób przechodzi wyjątku stosu macierzystego (Nawiasem throws Exceptionw zgłoszeniu metoda) new Exception()zostanie wyrzucony i złapać catch ( Exception i ), MyExc2zostanie wyrzucony wyjątek (na razie dodać go do stosu wyjątku ), ale ostatni w mainbloku zostanie wykonany jako pierwszy.

Więc w

catch ( Exception i ) {
    throw( new MyExc2() );
} 
finally {
    System.out.print(2);
    throw( new MyExc1() );
}

Wreszcie klauzula nazywa ... (pamiętam, właśnie złapany Exception ii wyrzucony MyExc2), w istocie, 2jest drukowany na ekranie ... i po 2wydrukowany na ekran, MyExc1jest wyjątek. MyExc1jest obsługiwany przezpublic static void main(...) metodę.

Wynik:

„132Exception w głównym wątku MyExc1”

Wykładowca ma rację! :-)

Zasadniczo , jeśli masz klauzulę final in try / catch, zostanie wykonana funkcja final ( po przechwyceniu wyjątku przed wyrzuceniem przechwyconego wyjątku)


catchJest wykonywany od q()rzucił Exceptionz własnego finallybloku.
Péter Török

„W q () jest zgłaszany wyjątek, ale zanim wyjątek zostanie w pełni zgłoszony, najpierw wykonywana jest klauzula końcowa, więc na ekranie zostanie wydrukowane 3.” Eee… nie, pierwszy zgłoszony wyjątek qprzekazuje wykonanie do pusty catchblok w q(który połyka ten wyjątek), a następnie do finallybloku q. Wreszcie wymieniony blok drukuje 3, a następnie generuje nowy wyjątek, które dzięki qjest throws Exceptionprzekazywany na stos do nadrzędnego.
Powerlord

38

Wyjątki w bloku końcowym zastępują wyjątki w bloku catch.

Cytat z wydania Java Language Specification 14 :

Jeśli blok catch kończy się nagle z powodu R, wykonywany jest ostatni blok. Następnie jest wybór:

  • Jeśli ostatni blok kończy się normalnie, instrukcja try kończy się nagle z powodu R.

  • Jeśli ostatni blok kończy się nagle z powodu S, to instrukcja try kończy się nagle z powodu S (i powód R jest odrzucany).


21

Ostateczna klauzula jest wykonywana nawet wtedy, gdy wyjątek jest zgłaszany z dowolnego miejsca w bloku try / catch.

Ponieważ jest to ostatni do wykonania w main i zgłasza wyjątek, jest to wyjątek, który widzą wywołujący.

Stąd tak ważne jest upewnienie się, że finallyklauzula niczego nie wyrzuca, ponieważ może połknąć wyjątki z trybloku.


5
Zostanie również wykonany NAWET, jeśli nie zostanie
zgłoszony

2
+1: Skieruj i do punktu bez meandrowania w dół całego stosu, który OP wydaje się już rozumieć.
Powerlord

9

Nie methodmoże mieć throwdwóch wyjątków w tym samym czasie. Zawsze wyrzuci ostatni rzucony exception, który w tym przypadku będzie zawsze tym zfinally bloku.

Kiedy pierwszy wyjątek od metody q() zostanie wyrzucony, zostanie złapany, a następnie połknięty przez ostatni wyrzucony wyjątek.

q () -> thrown new Exception -> main catch Exception -> throw new Exception -> finally wrzuć nowy exception(a ten z tego catchjest „zagubiony”)


3

Najłatwiej o tym pomyśleć, wyobrażając sobie, że istnieje zmienna globalna dla całej aplikacji, która przechowuje bieżący wyjątek.

Exception currentException = null;

Gdy zgłaszany jest każdy wyjątek, „currentException” jest ustawiany na ten wyjątek. Gdy aplikacja kończy się, jeśli currentException ma wartość! = Null, środowisko wykonawcze zgłasza błąd.

Ponadto ostatnie bloki są zawsze uruchamiane przed zakończeniem metody. Następnie możesz zażądać fragmentu kodu, aby:

public class C1 {

    public static void main(String [] argv) throws Exception {
        try {
            System.out.print(1);
            q();

        }
        catch ( Exception i ) {
            // <-- currentException = Exception, as thrown by q()'s finally block
            throw( new MyExc2() ); // <-- currentException = MyExc2
        }
        finally {
             // <-- currentException = MyExc2, thrown from main()'s catch block
            System.out.print(2);
            throw( new MyExc1() ); // <-- currentException = MyExc1
        }

    }  // <-- At application exit, currentException = MyExc1, from main()'s finally block. Java now dumps that to the console.

    static void q() throws Exception {
        try {
            throw( new MyExc1() ); // <-- currentException = MyExc1
        }
        catch( Exception y ) {
           // <-- currentException = null, because the exception is caught and not rethrown
        }
        finally {
            System.out.print(3);
            throw( new Exception() ); // <-- currentException = Exception
        }
    }
}

Kolejność wykonywania aplikacji to:

main()
{
  try
    q()
    {
      try
      catch
      finally
    }
  catch
  finally
}

1

Powszechnie wiadomo, że ostatni blok jest wykonywany po wykonaniu try and catch i zawsze jest wykonywany .... Ale jak widzieliście, czasami jest to trochę skomplikowane, sprawdzajcie ten fragment kodu poniżej i okaże się, że instrukcje return i throw nie nie zawsze robią to, co powinni robić w kolejności, w jakiej oczekujemy od tematu.

Twoje zdrowie.

/////////////Return dont always return///////

try{

    return "In Try";

}

finally{

    return "In Finally";

}

////////////////////////////////////////////


////////////////////////////////////////////    
while(true) { 

    try {

        return "In try";

   } 

   finally{

        break;     

    }          
}              
return "Out of try";      
///////////////////////////////////////////


///////////////////////////////////////////////////

while (true) {     

    try {            

        return "In try";    

     } 
     finally {   

         continue;  

     }                         
}
//////////////////////////////////////////////////

/////////////////Throw dont always throw/////////

try {

    throw new RuntimeException();

} 
finally {

    return "Ouuuups no throw!";

}
////////////////////////////////////////////////// 

1
class MyExc1 extends Exception {}
class MyExc2 extends Exception {}
class MyExc3 extends MyExc2 {}

public class C1 {
    public static void main(String[] args) throws Exception {
        try {
            System.out.print("TryA L1\n");
            q();
            System.out.print("TryB L1\n");
        }
        catch (Exception i) {
            System.out.print("Catch L1\n");                
        }
        finally {
            System.out.print("Finally L1\n");
            throw new MyExc1();
        }
    }

    static void q() throws Exception {
        try {
            System.out.print("TryA L2\n");
            q2();
            System.out.print("TryB L2\n");
        }
        catch (Exception y) {
            System.out.print("Catch L2\n");
            throw new MyExc2();  
        }
        finally {
            System.out.print("Finally L2\n");
            throw new Exception();
        }
    }

    static void q2() throws Exception {
        throw new MyExc1();
    }
}

Zamówienie:

TryA L1
TryA L2
Catch L2
Finally L2
Catch L1
Finally L1        
Exception in thread "main" MyExc1 at C1.main(C1.java:30)

https://www.compilejava.net/


1
Chociaż ten fragment kodu może być rozwiązaniem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a ci ludzie mogą nie znać powodów twojej sugestii kodu
Rahul Gupta

1

Logika jest jasna aż do zakończenia drukowania 13. Wtedy wyjątek wrzucony q()zostaje złapany przez catch (Exception i)się main()i new MyEx2()jest gotowy do rzucania. Jednak przed wyrzuceniem wyjątku finallynależy najpierw wykonać blok. Następnie wyjście staje się 132i finallyprosi o zgłoszenie kolejnego wyjątku new MyEx1().

Ponieważ metoda nie może wyrzucić więcej niż jednej Exception, zawsze będzie rzucać ostatnią Exception. Innymi słowy, jeśli oba catchi finallybloki spróbują rzucić Exception, połkniętyException zostanie złapany i tylko wyjątekfinally zostanie wyrzucony .

Tak więc w tym programie Wyjątek MyEx2jest połykany i MyEx1wyrzucany. Ten wyjątek jest wyrzucany main()i nie jest już przechwytywany, dlatego JVM zatrzymuje się, a ostateczny wynik jest 132Exception in thread main MyExc1.

W istocie, jeśli masz finallyw try/catchklauzuli, a finallyzostanie wykonane PO złapaniu wyjątku , ale PRZED wyrzuceniem dowolnego przechwyconego wyjątku , a na końcu zostanie wyrzucony TYLKO ostatni wyjątek .


0

Myślę, że po prostu musisz przejść finallybloki:

  1. Wydrukuj „1”.
  2. finallyw qdruku „3”.
  3. finallyw maindruku „2”.

0

Aby poradzić sobie z tego rodzaju sytuacjami, tj. Obsłużyć wyjątek wywołany przez blok final. Możesz otoczyć ostatni blok blokiem try: Spójrz na poniższy przykład w pythonie:

try:
   fh = open("testfile", "w")
   try:
      fh.write("This is my test file for exception handling!!")
   finally:
      print "Going to close the file"
      fh.close()
except IOError:
   print "Error: can\'t find file or read data"

-1

Myślę, że to rozwiązuje problem:

boolean allOk = false;
try{
  q();
  allOk = true;
} finally {
  try {
     is.close();
  } catch (Exception e) {
     if(allOk) {
       throw new SomeException(e);
     }
  }
}

3
Jaki problem zamierzasz „rozwiązać”? Masz na myśli pytanie na egzaminie? dobrze to już odpowiedział. Jeśli masz na myśli problem z podanym kodem, ponieważ jest to tylko pytanie egzaminacyjne, nie ma sensu go winić.
Earth Engine
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.