PO Wyjątki: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Jsroka (dyskusja | edycje)
Jsroka (dyskusja | edycje)
Linia 8: Linia 8:


== Nieobsłużone wyjątki ==
== Nieobsłużone wyjątki ==
Metoda ''głębiej()'' w pokazanej poniżej klasie ''UnhandledException'' zgłasza wyjątek klasy ''Exception'' (podstawowa klasa reprezentująca wyjątki w Javie), gdy przekazany jej parametr ma wartość '''null'''.
Metoda ''głębiej()'' w pokazanej poniżej klasie ''NieobslugiwanyWyjatek'' zgłasza wyjątek klasy ''Exception'' (podstawowa klasa reprezentująca wyjątki w Javie), gdy przekazany jej parametr ma wartość '''null'''.
  '''public''' '''class''' UnhandledException {
  '''public''' '''class''' NieobslugiwanyWyjatek {
   '''void''' głęboko(String s) '''throws''' Exception {
   '''void''' głęboko(String s) '''throws''' Exception {
     System.out.println("początek głęboko");
     System.out.println("początek głęboko");
Linia 23: Linia 23:


   '''public''' '''static''' '''void''' main(String[] args) '''throws''' Exception {
   '''public''' '''static''' '''void''' main(String[] args) '''throws''' Exception {
     UnhandledException ue = new UnhandledException();
     NieobslugiwanyWyjatek ue = new NieobslugiwanyWyjatek();
     System.out.println("przed głęboko");
     System.out.println("przed głęboko");
     ue.głęboko('''null''');
     ue.głęboko('''null''');
Linia 34: Linia 34:
  początek głębiej
  początek głębiej
  Exception in thread "main" java.lang.Exception
  Exception in thread "main" java.lang.Exception
   at test.UnhandledException.głębiej(UnhandledException.java:12)
   at test.NieobslugiwanyWyjatek.głębiej(NieobslugiwanyWyjatek.java:12)
   at test.UnhandledException.głęboko(UnhandledException.java:6)
   at test.NieobslugiwanyWyjatek.głęboko(NieobslugiwanyWyjatek.java:6)
   at test.UnhandledException.main(UnhandledException.java:19)
   at test.NieobslugiwanyWyjatek.main(NieobslugiwanyWyjatek.java:19)


=== Nadzorowanie obsługi wyjątków przez kompilator ===
=== Nadzorowanie obsługi wyjątków przez kompilator ===
Warto zwrócić uwagę, że w deklaracjach wszystkich metod klasy ''UnhandledException'' występuje klauzula '''throws''', która informuje jakich wyjątków można się podziewać w wyniku wywołania danej metody. Ta klauzula jest wymuszane przez kompilator, jeżeli z metody mogą wydostać sie jakieś nieobsłużone wyjątki. Dzięki temu programista nie ma możliwości przeoczyć żadnego wyjątku i jeżeli nie chce wszystkich obsłużyć musi świadomie wymienić je (bądź ich nadklasy) w deklaracji metody.
Warto zwrócić uwagę, że w deklaracjach wszystkich metod klasy ''NieobslugiwanyWyjatek'' występuje klauzula '''throws''', która informuje jakich wyjątków można się podziewać w wyniku wywołania danej metody. Ta klauzula jest wymuszane przez kompilator, jeżeli z metody mogą wydostać sie jakieś nieobsłużone wyjątki. Dzięki temu programista nie ma możliwości przeoczyć żadnego wyjątku i jeżeli nie chce wszystkich obsłużyć musi świadomie wymienić je (bądź ich nadklasy) w deklaracji metody.


== Obsługa wyjątku ==
== Obsługa wyjątku ==

Wersja z 17:45, 23 lip 2006

Obsługa sytuacji wyjątkowych

Nawet w proprawnie napisanych programach należy się liczyć z możliwością wystąpienia sytuacji wyjątkowych. Najczęstrzym rodzajem błedów których nie można uniknąć jest niedostępność różnego rodzaju zasobów, np. niemożliwość zapisu do pliku dyskowego czy problemy z komunikacją przez sięć. Wiele problemów może się pojawić gdy nieumiejętnie korzystamy z kodu napisanego przez kogoś innego lub gdy inni nieumiejętnie korzystają z kodu napisanego przez nas. Wiąże się to zazwyczaj z niezaglądaniem do dokumentacji technicznej lub z jej słabą jakością. Typowym przykładem takiej sytuacji jest przekazanie metodzie niepoprawnych danych, np. wartości null gdy spodziewała się referencji do obiektu lub krótszej niż oczekiwała tablicy.

Dobrze napisany kod powinien zakładać możliwość wystąpienia takich wyjątkowych sytuacji. Zazwyczaj jednak nie da się podjąć żadnych działań naprawczych w miejscu wykrycia błędu, trzeba przerwać normalny tok działania programu i przekazać informacje o błędzie "na zewnętrz" do szerszego kontekstu (do metody, która wywołała metodę w której występiła sytuacja wyjątkowa lub jeszcze dalej), gdzie mogą być pojęte działania napracze. Starsze języki programowania jak C nie zawierały żadnych specjalnych mechanizmów ułatwiających radzenie sobie w takich sytuacjach. Istniał natomiast kilka ogólnie przyjętych schematów postępowania. Można zwracać specjalną wartość oznaczającą błąd lub ustawiać glabalnie dostępną flagę, którą następnie trzeba skontrolować. Z czasem okazało się jednak, że programiści nie byli konsekwentni w przestrzeganiu tych konwencji. Zakładali, że błędy zdarzają się jedynie w kodzie innych, a w ich nie. Często byli też po prostu leniwi, gdyż obsługa wszystkich nietypowych sytuacji, np. sprawdzanie czy błąd nie wystąpił po wywołaniu każdej kolejnej metody, powodowałaby wielokrotne napęcznienie kodu oraz drastycznie utrudniła jego stworzenie i zrozumienie. W efekcie sytuacje wyjątkowe nie były prawidłowo rozpoznawane i program wykonywał się dalej powodując wystąpienie jeszcze większych problemów. Brak specjanlnych mechanizmów wymuszających obsługę sytuacji wyjątkowych był ważnym ograniczeniem przy tworzeniu dużych, solidnych i łatwych w pielęgnowaniu programów.

Mechanizm obsługi wyjątków

Nowoczesne obiektowe języki programowania mają wbudowany specjalny mechanizm ułatwiający i upraszczający radzenie sobie z obsługą systuacji wyjątkowych. W chwili wykrycia takiej sytuacji można stworzyć specjalny obiekt nazywany wyjątkiem (ang. exception), zawrzeć w nim wszystkie informacje na temat tego co się stało i przy pomocy specjalnej instrukcji throw (w niktórych językach raise) zgłosić ten wyjątek do obsłużenia. Zgłoszenie wyjątku wymusza przerwanie normalnego trybu wykonywania programu i rozwinięcie stosu wywołań aż do napotkania kontekstu zawierającego kod obsługi dla wyjątków tego rodzaju. Jeżeli cały stos zostanie rozwinięty, a wyjątku nie obsłużono, program zostaje przerywany.

Nieobsłużone wyjątki

Metoda głębiej() w pokazanej poniżej klasie NieobslugiwanyWyjatek zgłasza wyjątek klasy Exception (podstawowa klasa reprezentująca wyjątki w Javie), gdy przekazany jej parametr ma wartość null.

public class NieobslugiwanyWyjatek {
  void głęboko(String s) throws Exception {
    System.out.println("początek głęboko");
    głębiej(s);
    System.out.println("koniec głęboko");
  }
  
  void głębiej(String s) throws Exception {
    System.out.println("początek głębiej");
    if (s == null) throw new Exception();
    System.out.println("koniec głębiej");
  }
  public static void main(String[] args) throws Exception {
    NieobslugiwanyWyjatek ue = new NieobslugiwanyWyjatek();
    System.out.println("przed głęboko");
    ue.głęboko(null);
    System.out.println("po głęboko");
  }
}

Z powodu zgłoszenia wyjątku stos wywołań jest rozwijany, a ponieważ żadna z kolejno znajdujących się tam metod głębiej(), głęboko() i main() nie zawiera kodu obsługi wyjątku, program zostaje przerwany. W takiej sytuacji Java wypisuje na standardowe wyjście błędu informacje o wyjątku, który spowodował przerwanie programu. Ich poziom szczegółowości zależy od ustawień kompilatora, ale przy ustawieniach domyślnych podawany jest również stan stosu wywołań oraz numery linii w kodzie źródłowym odpowiadające jego pozycjom.

przed głęboko
początek głęboko
początek głębiej
Exception in thread "main" java.lang.Exception
  at test.NieobslugiwanyWyjatek.głębiej(NieobslugiwanyWyjatek.java:12)
  at test.NieobslugiwanyWyjatek.głęboko(NieobslugiwanyWyjatek.java:6)
  at test.NieobslugiwanyWyjatek.main(NieobslugiwanyWyjatek.java:19)

Nadzorowanie obsługi wyjątków przez kompilator

Warto zwrócić uwagę, że w deklaracjach wszystkich metod klasy NieobslugiwanyWyjatek występuje klauzula throws, która informuje jakich wyjątków można się podziewać w wyniku wywołania danej metody. Ta klauzula jest wymuszane przez kompilator, jeżeli z metody mogą wydostać sie jakieś nieobsłużone wyjątki. Dzięki temu programista nie ma możliwości przeoczyć żadnego wyjątku i jeżeli nie chce wszystkich obsłużyć musi świadomie wymienić je (bądź ich nadklasy) w deklaracji metody.

Obsługa wyjątku

try {
  //kod który może zgłosić wyjątki
} catch (Typ1 w) {
  //obsługa wyjątków typu Typ1
} catch (Typ2 w) {
  //obsługa wyjątków typu Typ2
} catch (Typ3 w) {
  //obsługa wyjątków typu Typ3
}

Do obsługi wyjątków służy instrukcja try-catch. Po try podaje się blok instrukcji, których wyjątki chcemy obsługiwać. Następnie następuje lista przypominających deklaracje metod klauzul catch, z których każda obsługuje wyjątki określonego typu. Typ wyjątku podaje się między nawiasami bezpośrednio po słowie kluczowym catch wraz z nazwą zmiennej, na którą zostanie przypisany złapany egzemplarz wyjątku. Do tej zmiennej można się odwoływać w kodzie obsługi wyjątku. Do obsługi wyjątku Wybierana jest zawsze pierwsza pasująca klauzula catch. Ponieważ wyjątki są obiektami "pasowanie" oznacza tu po prostu możliwość przypisania wyjątku na zmienną wymienioną po słowie kluczowym catch. Wszystkie dalsze klauzule catch są pomijane, nawet jeżeli pasowały. W pokazanej poniżej klasie HandledException, która jest modyfikacją poprzedniego przykładu, wyjątek Exception jest już obsługiwany i nie powoduje przerwania programu.

public class HandledException {
  void głęboko(String s) {
    try {
      System.out.println("początek głęboko");
      głębiej(s);
      System.out.println("koniec głęboko");
    } catch (Exception e) {
      System.out.println("obsługa wyjątku");
      e.printStackTrace(System.out);
    }
    System.out.println("po obsłużeniu wyjątku");
  }

  void głębiej(String s) throws Exception {
    System.out.println("początek głębiej");
    if (s == null) throw new Exception();
    System.out.println("koniec głębiej");
  }

  public static void main(String[] args) {
    HandledException ep = new HandledException();
    System.out.println("przed głęboko");
    ep.głęboko(null);
    System.out.println("po głęboko");
  }
}

Warto zwrócić uwagę, że metody głęboko() i main() nie muszą już posiadać klauzuli throws.

Na konsolę nadal wypisywane są informacje o wyjątku, ale jest to spowodowane użyciem w kodzie obsługi wyjątku metody printStackTrace() z parametrem System.out. Metoda ta jest odziedziczona z klasy Throwable, po której muszą dziedziczyć wszystkie wyjątki.

przed głęboko
początek głęboko
początek głębiej
obsługa wyjątku
java.lang.Exception
  at test.HandledException.głębiej(HandledException.java:18)
  at test.HandledException.głęboko(HandledException.java:7)
  at test.HandledException.main(HandledException.java:25)
po obsłużeniu wyjątku
po głęboko