CodeGym /Kursy /JAVA 25 SELF /Wyjątki jako część API i try-with-resources

Wyjątki jako część API i try-with-resources

JAVA 25 SELF
Poziom 24 , Lekcja 4
Dostępny

1. Wyjątki jako część API

Dlaczego wyjątki to część „kontraktu” metody?

Pisząc metodę, określasz nie tylko parametry i wartość zwracaną, ale też jakie wyjątki może ona zgłosić. To część „kontraktu” między twoją metodą a tymi, którzy będą jej używać. Jeśli metoda może zgłosić wyjątek, powinni o tym wiedzieć wszyscy, którzy ją wywołują – aby poprawnie obsłużyć błąd (albo przynajmniej nie dziwić się, gdy program nagle zakończy się z błędem).

Przykład:

public void readFile(String filename) throws IOException {
    // ... odczyt pliku
}

Tutaj wyraźnie wskazano, że metoda może zgłosić IOException. To sygnał dla użytkownika metody: „Bądź gotów obsłużyć błąd odczytu pliku!”

Dokumentowanie wyjątków: znacznik @throws w Javadoc

Aby inni programiści rozumieli, jakie wyjątki może zgłosić twoja metoda, używaj znacznika @throws (lub @exception) w Javadoc.

Przykład:

/**
 * Odczytuje zawartość pliku.
 *
 * @param filename nazwa pliku
 * @return zawartość pliku jako String
 * @throws IOException jeśli wystąpił błąd odczytu pliku
 */
public String readFile(String filename) throws IOException {
    // ...
}

Po co to robić?

  • Pomaga innym programistom zrozumieć, jakie błędy należy obsłużyć.
  • IDE i generatory dokumentacji (np. Javadoc) pokazują te wyjątki bezpośrednio w podpowiedziach.
  • Zwiększa niezawodność i przewidywalność kodu.

Wyjątki checked i unchecked w API

Wyjątki checked (pochodne Exception, ale nie RuntimeException) są częścią kontraktu metody. Należy je albo obsłużyć, albo jawnie przekazać dalej (throws).

Wyjątki unchecked (RuntimeException i pochodne) zwykle sygnalizują błędy programistyczne (np. NullPointerException, IllegalArgumentException). Nie trzeba ich wskazywać w sygnaturze, ale jeśli twoja metoda może taki wyjątek zgłosić (np. przy niepoprawnych argumentach), warto to również opisać w Javadoc.

Przykład:

/**
 * Dzieli a przez b.
 * @param a dzielna
 * @param b dzielnik
 * @return wynik dzielenia
 * @throws IllegalArgumentException jeśli b == 0
 */
public int divide(int a, int b) {
    if (b == 0) throw new IllegalArgumentException("Dzielnik nie może być zerem");
    return a / b;
}

Wyjątki a projektowanie API

  • Myśl o użytkowniku swojej metody: jakie błędy może i powinien obsłużyć? Które to błędy programistyczne, a które – „normalne” sytuacje?
  • Nie nadużywaj wyjątków checked: jeśli błąd to błąd programistyczny (np. nieprawidłowy argument), lepiej zgłaszać wyjątek unchecked.
  • Dokumentuj wszystkie wyjątki, które mogą zostać propagowane wyżej.

2. Konstrukcja try-with-resources

Problem: jak bezpiecznie zamykać zasoby?

W wielu zadaniach pracujemy z zasobami, które trzeba koniecznie zamykać po użyciu: pliki, połączenia sieciowe, bazy danych itd. Jeśli zapomnisz zamknąć zasób – możesz doprowadzić do wycieku pamięci, zablokowania pliku lub innych problemów.

Dawniej:

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("data.txt"));
    String line = reader.readLine();
    // ...
} catch (IOException e) {
    // obsługa błędu
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            // obsługa błędu podczas zamykania
        }
    }
}

Dużo kodu, łatwo o błąd, można zapomnieć zamknąć zasób.

Rozwiązanie: try-with-resources

Od Javy 7 dostępna jest konstrukcja try-with-resources, która automatycznie zamyka wszystkie zasoby, nawet jeśli wewnątrz bloku wystąpi wyjątek.

Składnia:

try (ResourceType resource = new ResourceType(...)) {
    // praca z zasobem
} catch (ExceptionType e) {
    // obsługa błędu
}

Przykład:

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("Błąd podczas odczytu pliku: " + e.getMessage());
}
// reader.close() zostanie wywołane automatycznie!

Jak to działa?

  • W nawiasach po try deklaruje się zasoby, które należy zamknąć.
  • Po wyjściu z bloku try (nawet jeśli wystąpił wyjątek!) dla każdego zasobu wywoływana jest metoda close().
  • Działa to tylko dla zasobów implementujących interfejs AutoCloseable (lub jego przodek Closeable).

Interfejs AutoCloseable:

public interface AutoCloseable {
    void close() throws Exception;
}

Wszystkie standardowe zasoby Javy (pliki, strumienie, połączenia z bazą danych) implementują ten interfejs.

Można deklarować wiele zasobów

try (
    BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
}

Oba zasoby zostaną zamknięte automatycznie, nawet jeśli wystąpi wyjątek.

Zalety try-with-resources

  • Bezpieczeństwo: zasoby są zawsze zamykane, nawet przy błędach.
  • Zwięzłość: mniej kodu, mniejsze ryzyko pomyłki.
  • Czytelność: od razu widać, jakie zasoby są używane i kiedy są zamykane.

3. Praktyka: piszemy bezpieczny kod z try-with-resources

Przykład: odczyt pliku

public static void printFirstLine(String filename) {
    try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
        String line = reader.readLine();
        System.out.println("Pierwsza linia: " + line);
    } catch (IOException e) {
        System.out.println("Błąd: " + e.getMessage());
    }
}

Przykład: zapis do pliku

public static void writeToFile(String filename, String text) {
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
        writer.write(text);
    } catch (IOException e) {
        System.out.println("Błąd podczas zapisu: " + e.getMessage());
    }
}

Przykład: własny zasób

Jeśli tworzysz własną klasę, którą należy zamykać, po prostu zaimplementuj AutoCloseable:

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("Zasób zamknięty!");
    }
}

Teraz można go używać w try-with-resources:

try (MyResource res = new MyResource()) {
    // praca z zasobem
} 

4. Typowe błędy i najlepsze praktyki

Błąd nr 1: zapomniano zamknąć zasób (bez try-with-resources).
Jeśli nie używasz try-with-resources, łatwo zapomnieć zamknąć plik lub strumień – prowadzi to do wycieków zasobów.

Błąd nr 2: próba użycia try-with-resources z obiektem, który nie implementuje AutoCloseable.
Jeśli twoja klasa nie implementuje tego interfejsu, kompilator nie pozwoli użyć jej w try-with-resources.

Błąd nr 3: brak dokumentowania wyjątków w API.
Jeśli twoja metoda może zgłosić wyjątek – koniecznie wskaż to w sygnaturze (throws) i w Javadoc (@throws). To pomoże innym poprawnie korzystać z twojego kodu.

Błąd nr 4: łapanie Exception zamiast konkretnych wyjątków.
Lepiej łapać tylko te wyjątki, których rzeczywiście się spodziewasz i które potrafisz obsłużyć.

1
Ankieta/quiz
Hierarchia wyjątków, poziom 24, lekcja 4
Niedostępny
Hierarchia wyjątków
Zaawansowana praca z wyjątkami
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION