CodeGym /Kursy /JAVA 25 SELF /finally i throw: zakończenie i generowanie wyjątków

finally i throw: zakończenie i generowanie wyjątków

JAVA 25 SELF
Poziom 11 , Lekcja 3
Dostępny

1. Wprowadzenie do bloku finally

Gdy pracujesz z zasobami — plikami, połączeniami sieciowymi, bazami danych — ważne jest, aby mieć pewność, że będą zamknięte lub zwolnione zawsze, nawet jeśli w trakcie pracy pojawi się błąd. W Java służy do tego specjalny blok — finally.

Jak działa finally?

Blok finally to część konstrukcji try-catch-finally. Kod wewnątrz finally wykonuje się zawsze (jeśli został napisany) — niezależnie od tego, czy wystąpił wyjątek, czy nie. Nawet jeśli w try był return lub rzucono wyjątek — finally i tak się wykona (chyba że wyłączono komputer albo program zakończono wymuszeniem przez System.exit(0)).

Składnia:

try {
    // Kod, w którym może wystąpić wyjątek
} catch (ExceptionType e) {
    // Obsługa błędu
} finally {
    // Ten kod wykona się zawsze!
}

Przykład

try {
    System.out.println("Początek pracy");
    int result = 10 / 0; // tutaj wystąpi błąd
    System.out.println("Wynik: " + result);
} catch (ArithmeticException e) {
    System.out.println("Błąd: dzielenie przez zero");
} finally {
    System.out.println("Ten kod wykona się w każdym przypadku");
}

Wynik działania:

Początek pracy
Błąd: dzielenie przez zero
Ten kod wykona się w każdym przypadku

Co się dzieje?

  1. W try próbujemy podzielić dwie liczby i otrzymujemy błąd.
  2. Jeśli podczas dzielenia wystąpi błąd — catch go obsłuży.
  3. Ale! W każdym przypadku finally wykona się i wypisze komunikat na konsolę.

2. finally bez catch

Istnieją 3 warianty konstrukcji:

  • Pełny: try-catch-finally
  • Bez finally: try-catch
  • Bez catch: try-finally

Trzeci wariant stosuje się, gdy przechwytywanie i obsługa błędu nastąpi w metodzie o poziom wyżej. Jednak blok finally jest potrzebny, aby zagwarantować wykonanie określonego kodu:

  • Zamykanie plików, połączeń sieciowych, baz danych.
  • Zwalnianie wszelkich zasobów (np. blokad).
  • Logowanie: zapis informacji o zakończeniu operacji.

Przykład:

try {
    System.out.println("Dzielimy liczby");
    int result = 10 / 0;   // błąd!
    System.out.println("Wynik: " + result);
} finally {
    System.out.println("Blok finally został wykonany");
}

Wynik:

Dzielimy liczby
Blok finally został wykonany
Exception in thread "main" java.lang.ArithmeticException: / by zero

Kiedy finally NIE jest wykonywany?

Wykonywany jest prawie zawsze. Wyjątki — gdy:

  1. Wymuszono zakończenie programu za pomocą: System.exit(0).
  2. Wymuszenie „zabito” wątek, w którym wykonywany jest finally.
  3. Wyłączono komputer.

3. Operator throw: jak samodzielnie wygenerować wyjątek

Czasem Java sama „rzuca” wyjątki (np. dzielenie przez zero, wyjście poza granice tablicy). Ale zdarzają się sytuacje, gdy chcesz wyraźnie zakomunikować: „To błąd! Nie mogę kontynuować wykonania!”. Do tego w Java służy operator throw.

Analogia: Jeśli w sklepie widzisz przeterminowany towar — „rzucasz” skargę. Tak samo w kodzie: jeśli coś jest nie tak — rzucasz wyjątek.

Składnia throw

throw new ExceptionType("Komunikat o błędzie");

ExceptionType — dowolna klasa dziedzicząca po Throwable (zwykle Exception lub RuntimeException). W nawiasach — komunikat, który pomoże zrozumieć, co poszło nie tak.

Przykład: weryfikacja argumentów metody

public static int safeDivide(int a, int b) {
    if (b == 0) {
        throw new IllegalArgumentException("Dzielnik nie może być równy zeru");
    }
    return a / b;
}

Użycie:

public static void main(String[] args) {
    try {
        int result = safeDivide(10, 0);
        System.out.println("Wynik: " + result);
    } catch (IllegalArgumentException e) {
        System.out.println("Błąd: " + e.getMessage());
    }
}

Wynik:

Błąd: Dzielnik nie może być równy zeru

Kiedy używać throw?

  • Weryfikacja argumentów metody (np. jeśli przyszedł null lub niepoprawne dane).
  • Weryfikacja stanu obiektu (np. próba wypłaty środków z konta, na którym jest 0 euro).
  • Wewnątrz catch — jeśli chcesz „przerzucić” wyjątek dalej (np. dodać dodatkowe informacje).

4. Łączenie try-catch-finally i throw

Czasem te konstrukcje działają razem. Na przykład łapiesz jeden błąd, ale potem decydujesz się rzucić własny, bardziej informacyjny.

public static int parseAndDivide(String text, int divisor) {
    try {
        int number = Integer.parseInt(text);
        if (divisor == 0) {
            throw new IllegalArgumentException("Dzielnik nie może być równy zeru");
        }
        return number / divisor;
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("Ciąg '" + text + "' nie jest liczbą");
    } finally {
        System.out.println("Próba przetworzenia ciągu: " + text);
    }
}

Użycie:

try {
    int result = parseAndDivide("42a", 2);
    System.out.println("Wynik: " + result);
} catch (IllegalArgumentException e) {
    System.out.println("Błąd: " + e.getMessage());
}

Wynik:

Próba przetworzenia ciągu: 42a
Błąd: Ciąg '42a' nie jest liczbą

Ważna uwaga: return i finally

Nawet jeśli w bloku try jest return, finally i tak się wykona!

public static int getValue() {
    try {
        return 10;
    } finally {
        System.out.println("finally i tak się wykona!");
    }
}

Wywołanie getValue() wypisze:

finally i tak się wykona!

5. Typowe błędy przy użyciu finally i throw

Błąd nr 1: zapomniano zamknąć zasób bez finally.
Bardzo częsty problem: otwarto plik, nie zamknięto — doszło do wycieku zasobów. Zawsze używaj finally (lub try-with-resources, o którym powiemy później).

Błąd nr 2: rzucono wyjątek, ale go nie obsłużono.
Jeśli rzucasz wyjątek za pomocą throw, ale nigdzie go nie łapiesz (brak try-catch), program zakończy się awaryjnie. Zawsze myśl, kto będzie łapał twój wyjątek.

Błąd nr 3: return w finally.
Jeśli przez pomyłkę napiszesz return wewnątrz finally, „nadpisze” on wszystkie wcześniejsze return lub throw. To może prowadzić do bardzo trudnych do wychwycenia błędów. Tak robić jest kategorycznie odradzane!

public int tricky() {
    try {
        return 1;
    } finally {
        return 2; // NIEBEZPIECZNE: zwróci 2, a nie 1!
    }
}

Wynik: zostanie zwrócone 2, chociaż w try było 1.

Błąd nr 4: utrata informacji o wyjątku.
Jeśli łapiesz jeden wyjątek, a potem rzucasz nowy, nie zachowując informacji o starym (e), tracisz stos wywołań, co utrudnia debugowanie. Lepiej pisać tak:

catch (NumberFormatException e) {
    throw new IllegalArgumentException("Błąd konwersji", e);
}
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION