Obsługa wyjątków

Wyjątki są rzucane i propagowane tak długo jak długo pozostają nie przechwycone. Wyjątki nie przechwycone propagują się aż do metody main(…) i – jeśli także tam nie są przechwycone – powodują przerwanie wykonania programu. O tym w jaki sposób wyjątki są propagowane pisałem w artykule „Propagowanie wyjątków”. O tym, że pewne wyjątki trzeba deklarować i w jaki sposób to robić dowiadujemy się z artykułu „Deklarowanie wyjątków”. W niniejszym artykule nauczymy się wyjątki przechwytywać.

Aby przechwycić wyjątek rzucany przez pewien fragment kodu należy ten kod ując w klauzulę try-catch-finally. Składnia poniżej:

try {
  {kod programu}
} catch( {deklaracja zmiennej dla obiektu wyjątku} ) {
  {kod obsługi błędu}
} finally {
  {kod wykonywany zawsze}
}

Kod którego wyjątki chcemy przechwytywać to {kod programu}. Element {deklaracja zmiennej dla obiektu wyjątku} to deklaracja zmiennej typu takiego jak obsługiwany wyjątek do której będzie przypisany obiekt rzuconego i przechwyconego wyjątku. Wewnątrz klauzuli catch umieszczamy kod obsługi błędu – kod ten będzie wykonany tylko i wyłącznie wówczas gdy kod {kod programu} rzuci wyjątek typu zgodnego z typem określonym w deklaracji {deklaracja zmiennej dla obiektu wyjątku}. Kod umieszczony wewnątrz klauzuli finally będzie wykonany zawsze, niezależnie od tego czy {kod programu} rzucił wyjątek czy nie.

Klauzula finally jest opcjonalna, za to klauzul catch może być dowolnie wiele. Może zdarzyć się – i często się zdarza – że kod ujęty w pojedynczej klauzuli try zwraca kilka różnych typów wyjątków. Możemy wtedy umieścić osobne klauzule catch dla obsługi każdego z tych wyjątków, ale możemy też umieścić jedną klauzulę catch do obsługi ich wszystkich – wystarczy, że korzystając z własności polimorfizmu (patrz artykuł „Polimorfizm”) określimy typ wyjątku jako Exception.

Zmodyfikujmy teraz przykład z artykułu „Deklarowanie wyjątków” tak, aby wyjątek rzucany w metodzie someOp(…) był przechwytywany i obsługiwany w metodzie main(…). Poniżej kod nie zawierający jeszcze obsługi wyjątku:

public class TestClass {
  public static void main(String[] args) throws Exception {
    System.out.println("Przed wywołaniem");
  
    someOp(args);
  
    System.out.println("Za wywołaniem");
  }

  static void someOp(String[] args) throws Exception {
    if (args.length == 0)
      throw new Exception("Brak parametrów");

    System.out.println("Za wyjątkiem w metodzie");
  }
}

Powyższy przykład zmodyfikujemy w ten sposób, że metodę someOp(…) pozostawimy bez zmian, za to wywołanie tej metody w metodzie main(…) ujmiemy w klauzulę try. Metoda main(…) powinna wyglądać następująco:

public static void main(String[] args) {
  System.out.println("Przed wywołaniem");
  
  try {
    someOp(args);

    System.out.println("Za wyjątkiem");
  } catch (Exception exc) {
    System.out.println("Obsługa wyjątku");
  } finally {
    System.out.println("Zawsze się wykona");
  }
  
  System.out.println("Za wywołaniem");
}

Metoda someOp(…) zgłasza wyjątek, więc tekst „Za wyjątkiem w metodzie” nie zostanie wyświetlony – tu jeszcze nie ma obsługi wyjątku więc wykonanie metody będzie przerwane gdy zostanie on rzucony. Wyjątek rzucony w metodzie someOp(…) jest propagowany do metody main(…). W metodzie main(…) wyjątek ten otrzymujemy w rezultacie wywołania metody someOp(…). Wywołanie to jest ujęte w klauzulę try, więc wyjątek jest przechwycony – nie będzie przerwane wykonanie metody main(…), jednak będzie przerwane wykonanie kodu ujętego w klauzuli try, zatem tekst „Za wyjątkiem” nie zostanie wyświetlony. Od razu po wywołaniu metody someOp(…), z której otrzymaliśmy wyjątek, sekwencja wykonania przechodzi do kodu obsługi wyjątku – w naszym przypadku jest to wyświetlenie napisu „Obsługa wyjątku”. Po wykonaniu kodu obsługi wyjątku wykonywany jest kod z klauzuli finally, tak więc wyświetlany jest tekst „Zawsze się wykona”. Na tym kończy się sekwencja zdarzeń związanych z wyjątkiem. Wyjątek jest przechwycony i obsłużony, tak więc program może się teraz wykonywać normalnie – wykonanie metody main(…) nie jest przerywane a więc napis „Za wywołaniem” zostanie wyświetlony. Zauważmy także, że nie deklarujemy już że metoda main(…) rzuca wyjątek typu Exception (nie musimy w deklaracji metody main(…) pisać throws Exception). Wyjątek ten jest przez nas zawsze obsługiwany, tak więc nigdy nie będzie przez tą metodę zwrócony.