Programowanie średnio-zaawansowane #20: wyjątki

Zanim zacznę omawiać, czym jest wyjątek (ang. exception), postaraj się przetestować poniższy kod:

ArrayList<String> list = null;
list.get(0)

Wynik na konsoli powinien być podobny do poniższego:

Exception in thread "main" java.lang.NullPointerException
	at basics.exception.NullpointerCatch.main(NullpointerCatch.java:9)

Co to oznacza? Ponieważ użyłem zmiennej, do którego nie przypisałem wcześniej żadnej referencji (brak inicjalizacji), to wyświetlił mi się komunikat na konsoli, oznaczający, że zaszedł Nullpointer Exception. Widziałeś już na pewno takie informacje nie raz, próbując implementować swoje programy. Czas dowiedzieć się co one oznaczają oraz jak z nimi pracować.

Wyjątki to pewnego rodzaju błędy, ale mniejszego kalibru. Stwierdzają one, że coś jest nie tak z Twoim kodem. Kiedy zajdzie wyjątek możesz spróbować go złapać.

try {
	ArrayList<String> list = null;
	list.get(0);
} catch (Exception e) {
	System.out.println("Nullpointer was catched.");
}

Teraz zamiast mało zrozumiałem komunikatu, w przypadku, gdy zajdzie ten wyjątek na konsoli zobaczysz:

Nullpointer was catched.

Do łapania wyjątków oraz błędów służy konstrukcja try catch. W pierwszej sekcji powinien zawierać się kod, o który podejrzewasz, że może wyrzucić wyjątek. W tym przypadku jest on oczywisty, ale często w bardzo skomplikowanym kodzie trudno jest stwierdzić, w jaki sposób się on zachowa. Wtedy używanie try catch jest jak najbardziej uzasadnione. W drugiej sekcji powinieneś zawrzeć nazwę klasy wyjątku, który chcesz obsłużyć (np. Exception) i kod, który się wykona, gdy on zajdzie.

Wyjątki dzielą się na te, które musisz ‚obsłużyć’ oraz na te, które nie mają takiego wymogu (np. NullpointerException). Są miejsca w kodzie, kiedy kompilator sam wymusi na Tobie użycie try catch. Z reguły dzieje się to, gdy chcesz używać kod, który polega na jakiś zewnętrznych programach. Na przykład, jeśli chcesz otworzyć strumień danych, używając poniższego polecenia:

FileReader file = new FileReader("somefile.txt");

Niestety kompilator nie pozwoli Ci użyć tak wprost tej linii. To jest przykład kiedy programista jest zmuszony do użycia try catch. Prawidłowy kod powinien wyglądać tak:

try {
     FileReader file = new FileReader("somefile.txt");
} catch (FileNotFoundException e) {
     e.printStackTrace();
}

Teraz wszystko skompiluje się poprawnie. Wyjątki, które muszą zostać obsłużone często nazywa się checked, natomiast te, które mogą ale nie muszą być obsługiwane nazywa są unchecked.

Pomimo, że raz złapiesz wyjątek możesz go wyrzucić dalej. Przydaje się to, gdy chcesz napisać dodatkową logikę w sekcji catch w innej metodzie. Aby wyrzucić wyjątek należy użyć polecenia throw new w sekcji catch i napisać klasę wyjątku, który ma być wyrzucony dalej. To jednak nie koniec, w sygnaturze metody należy dodać także klauzulę throws, po której wypisujesz wyjątki wyrzucone w jej ciele (jeśli będziesz wyrzucał więcej wyjątków niż jeden).

private static void fileFacade() throws FileNotFoundException {
	try {
		FileReader file = new FileReader("somefile.txt");
	} catch (FileNotFoundException e) {
		e.printStackTrace();
		throw new FileNotFoundException();
	}
}

Teraz każda metoda, która użyje musi albo ponownie wyrzucić wyjątek albo go finalnie obsłużyć. Przykładowa metoda main w tym przypadku będzie wyglądać tak:

public static void main(String [] args) {
	try {
		fileFacade();
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	}
}

Ostatnią ciekawą kwestią jest klauzula finally, która dodana po sekcji catch oznacza, że kod w niej zawarty zawsze się wykona, bez względu na to czy blok try wykonał się do końca poprawnie (mógł zajść np. inny wyjątek). Stosuje się ten blok z reguły do sprzątania po implementacji zawartej w try.

private static void fileFacade() throws IOException {
		FileReader file = null;
		try {
			file = new FileReader("c:\\somefile.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} finally {
			file.close();
		}
	}

Pamiętaj, że aby powyższy kod zadziałał poprawnie, musisz stworzyć plik w ścieżkę, którą podałem w konstruktorze klasy FileReader. Jest tylko jeden sposób, aby sekcja finally nie została wykonana i jest nim polecenie System.exit(0); Ale potraktuj to jako ciekawostkę.

Dodaj komentarz