22 lipca 2008

SCJP – Wyjątki

Pod pojęciem wyjątków kryje się wiele zagadnień. Wyjątki są tworzone i rzucane a następnie łapane i obsługiwane. Pewne wyjątki trzeba deklarować a innych nie trzeba, ale można. Z perspektywy egzaminu SCJP – i nie tylko – ważna jest także znajomość sytuacji, które powodują rzucenie pewnych typowych wyjątków, takich jak ‘NullPointerException’. Dziś będzie o większości z tych rzeczy, tj. o wszystkim z wyjątkiem opisu konkretnych klas wyjątków. O tym w następnym artykule.

Wyjątek w języku Java to obiekt, który opisuje pewną sytuację błędną lub nieprawidłową – wyjątkową. Jest to obiekt odpowiedniego typu, tj. obiekt klasy ‘Throwable’ lub dowolnej podklasy. Poniższy diagram przedstawia trzon hierarchii dziedziczenia dla klas wyjątków.



Każda z klas ‘Error’, ‘Exception’ i ‘RuntimeException’ ma jeszcze wiele innych podklas, oznaczających konkretny rodzaj sytuacji wyjątkowej. Z punktu widzenia programisty wyjątki dzielą się na dwa rodzaje: kontrolowane (ang. checked) i niekontrolowane (ang. unchecked). Wyjątki kontrolowane tym różnią się od niekontrolowanych, że muszą być jawnie obsłużone w metodach, tj. albo muszą być złapane, albo zadeklarowane na liście wyjątków rzucanych przez metodę. Zerknijmy na poniższy przykład.

class SomeClass {
void someOp() {
throw new NullPointerException();
}

void someOtherOp() throws IOException {
throw new IOException();
}

void nextOp() {
try {
throw new IOException();
} catch (IOException ioExc) {
ioExc.printStackTrace();
}
}
}

W metodzie ‘someOp()’ rzucamy wyjątek ‘NullPointerException’. Jest to wyjątek niekontrolowany, więc nic specjalnego nie musimy robić. W metodzie ‘someOtherOp()’ rzucamy wyjątek ‘IOException’. Jest on wyjątkiem kontrolowanym, więc nie możemy go zignorować – deklarujemy, że metoda rzuca ten wyjątek. W metodzie ‘nextOp()’ również pojawia się wyjątek ‘IOException’, jednak w tym wypadku od razu go łapiemy i obsługujemy.

Wyjątki niekontrolowane to instancje klas ‘Error’ i ‘RuntimeException’ oraz ich dowolnych podklas. Wyjątki kontrolowane to instancje klas ‘Throwable’ i ‘Exception’ oraz ich podklas, oczywiście z wyłączeniem klas ‘Error’ i ‘RuntimeException’. Wyjątków niekontrolowanych nie musimy deklarować czy obsługiwać, ale możemy, poprawny jest więc poniższy kod.

class SomeClass {
void someOp() throws Error { // ta deklaracja jest nadmiarowa
throw new Error();
}
}

Obsługa wyjątków polega na tym, że kod, który może rzucić wyjątek ujmujemy w klauzulę ‘try’. Bezpośrednio po klauzuli ‘try’ następuje seria klauzul ‘catch’. Po klauzulach ‘catch’ następuje klauzula ‘finally’. Klauzula ‘finally’ jest opcjonalna. Opcjonalne są także klauzule ‘catch’, ale jeśli nie ma żadnej klauzuli ‘catch’, to wymagana jest klauzula ‘finally’. Jeśli nie ma klauzuli ‘finally’, to wymagana jest co najmniej jedna klauzula ‘catch’. W klauzuli ‘finally’ umieszczamy kod, który ma się wykonać zawsze, po wykonaniu kodu objętego klauzulą ‘try’. Jest to dobre miejsce na umieszczenie kodu sprzątającego, który powinien się uruchomić niezależnie od tego, co się stało w klauzuli ‘try’. To, że kod ten uruchamia się zawsze demonstruje dobrze poniższy przykład - uruchomienie programu spowoduje wyświetlenie napisu "klauzula finally".

public class Test {
public static void main(String[] args) {
someOp();
}

public static int someOp() {
try {
return 1;
} finally {
System.out.println("klauzula finally");
}
}
}

Klauzul ‘catch’ może być dowolna ilość. Co prawda moglibyśmy zadeklarować jedną klauzulę ‘catch’ która łapie wszystkie wyjątki, ale przeważnie chcemy wykonać różne akcje w zależności od typu wyjątku, przynajmniej dla niektórych z nich. Klauzule obsługujące konkretne wyjątki umieszczamy jako pierwsze a w dalszej kolejności występują klauzule bardziej ogólne. Demonstruje to poniższy przykład.

class SubException extends Exception {}

class NextException extends Exception {}

public class Test {
public void someOp() {
try {
throwingOp();

} catch (SubException subExc) {
// jakieś specyficzne operacje
} catch (Exception exc) {
// operacje ogólne, np. logowanie błędu
}
}

public void throwingOp() throws SubException, NextException {}
}

Wyjątek ‘SubException’ obsługujemy w pewien szczególny sposób, więc umieściliśmy dedykowaną klauzulę ‘catch’. Następna klauzula obsługuje wszystkie inne wyjątki, w tym wyjątki klasy ‘NextException’. Umieszczenie tych klauzul w odwrotnej kolejności byłoby błędem czasu kompilacji – klauzule bardziej ogólne muszą znajdować się za tymi wyspecjalizowanymi.

3 komentarze:

Anonimowy pisze...

Dobra inicjatywa, jestem trochę dalej w tej ksiazce, ale na pewno przed egzaminem [chce zdac za rok] wroce na Twoj blog przeczytam wszystkie posty:)

Mariusz Lipiński pisze...

Przerobiłeś już ponad połowę książki, a egzamin chcesz zdawać dopiero za rok? Czemu?

Pozdr. Mariusz

Mariusz Lipiński pisze...

Zapomniałem napisać o jednym - jeśli pewna instrukcja rzuca wyjątek, to wykonanie programu jest natychmiast wstrzymywane i zaczyna się obsługa wyjątku, tak więc instrukcje znajdujące się bezpośrednio za instrukcją która rzuciła wyjątek nigdy się nie wykonają (naturalnie nie dotyczy to instrukcji znajdujących się na kodem który wyjątek obsłuży).