CodeGym /Kursy /JAVA 25 SELF /IOException i FileNotFoundException: obsługa błędów

IOException i FileNotFoundException: obsługa błędów

JAVA 25 SELF
Poziom 38 , Lekcja 0
Dostępny

1. Hierarchia wyjątków wejścia/wyjścia

W Javie przy pracy z plikami i innymi zewnętrznymi zasobami niemal zawsze masz do czynienia z tzw. wyjątkami wejścia/wyjścia. To obiekty, które są zgłaszane (throw) w przypadku, gdy coś poszło nie tak podczas odczytu lub zapisu danych. Na przykład gdy pliku nie znaleziono, brak dostępu albo dysk nagle „się zmęczył”.

Główni bohaterowie naszego wykładu

W Javie istnieje cała hierarchia takich wyjątków. Oto najważniejsze z nich:

  • IOException — klasa bazowa dla wszystkich błędów wejścia/wyjścia. Jeśli coś poszło nie tak z plikami, strumieniami lub siecią — niemal zawsze „winny” jest on (albo któryś z jego licznych potomków).
  • FileNotFoundException — „syn” IOException, który pojawia się, gdy próbujesz otworzyć plik, który nie istnieje, lub ścieżka jest podana nieprawidłowo.

Inne pochodne:

  • EOFException — również bezpośredni „syn” IOException. Sygnałuje, że podczas odczytu niespodziewanie dotarliśmy do końca pliku.
  • MalformedInputException — „wnuk”: dziedziczy po CharacterCodingException, który z kolei dziedziczy po IOException. Taki błąd pojawia się, gdy pliku nie można poprawnie zinterpretować w zadanym kodowaniu (np. oczekiwano "UTF-8", a dotarła uszkodzona sekwencja).
  • Są też SocketException, ZipException i inni wyspecjalizowani „krewni”, każdy ze swoim zakresem odpowiedzialności. Im głębiej w hierarchię, tym węższa i bardziej specyficzna sytuacja.

Mały uproszczony schemat:

java.lang.Exception
└── java.io.IOException
    ├── java.io.FileNotFoundException
    ├── java.io.EOFException
    ├── java.nio.charset.MalformedInputException
    └── ... (inne)

Ciekawostka:
W Javie prawie wszystkie operacje na plikach wymagają deklarowania lub obsługi IOException — to tzw. wyjątki kontrolowane. Kompilator nie pozwoli ci zapomnieć o obsłudze błędów!

2. Kiedy i dlaczego pojawiają się te wyjątki

Otwarcie nieistniejącego pliku

Najczęstszy przypadek — próbujesz otworzyć plik, a jego nie ma. To jak przyjść na przystanek, a autobusu nie ma nawet w rozkładzie.

FileInputStream fis = new FileInputStream("abracadabra.txt"); // Bach! FileNotFoundException

Próba zapisu do pliku bez uprawnień

Jeśli próbujesz zapisać plik w katalogu, do którego nie masz uprawnień, otrzymasz FileNotFoundException lub IOException (w zależności od sytuacji).

FileOutputStream fos = new FileOutputStream("/system/secret.txt"); // Bach! FileNotFoundException lub IOException

Błędy odczytu/zapisu z powodu uszkodzenia nośnika

Czasem plik niby istnieje, ale dysk jest uszkodzony, plik jest zajęty przez inny program albo nagle wyłączono prąd — wtedy dostaniesz IOException z różnymi komunikatami.

Inne przyczyny

  • Plik jest otwarty tylko do odczytu, a ty próbujesz pisać.
  • Ścieżka do pliku jest zbyt długa lub zawiera niedozwolone znaki.
  • Plik jest używany przez inny proces.
  • Dysk jest pełny.
  • Plik został usunięty przez inny proces między sprawdzeniem a użyciem.

3. Obsługa za pomocą try-catch

Za każdym razem, gdy pracujesz z plikami, strumieniami, siecią — używaj try-catch. To jak poduszka powietrzna: jeśli coś pójdzie nie tak, program się nie wywróci, tylko zareaguje w kontrolowany sposób.

Jak prawidłowo łapać wyjątki?

W Javie bardziej konkretne wyjątki należy łapać wcześniej niż ogólne. Jeśli ogólny catch (IOException e) umieścisz jako pierwszy, to węższe, np. FileNotFoundException, po prostu nie zadziałają — sterowanie do nich nie dotrze.

Prawidłowa struktura:

try {
    // Praca z plikiem
} catch (FileNotFoundException e) {
    // Obsługa sytuacji "plik nie znaleziony"
} catch (IOException e) {
    // Obsługa innych błędów wejścia/wyjścia
}

Dlaczego tak?
Ponieważ FileNotFoundException to szczególny przypadek IOException. Jeśli złapiesz przypadek ogólny wcześniej, to szczegółowy „nie dotrze” do swojego catch.

Przykład kodu: obsługa błędów przy otwieraniu pliku

import java.io.*;

public class FileReaderExample {
    public static void main(String[] args) {
        String filename = "notes.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line = reader.readLine();
            System.out.println("Pierwsza linia pliku: " + line);
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("Nie znaleziono pliku: " + filename);
        } catch (IOException e) {
            System.out.println("Błąd podczas odczytu pliku: " + e.getMessage());
        }
    }
}

Zwróć uwagę:
Nawet jeśli sprawdziłeś, że plik istnieje, zawsze zostawiaj try-catch — plik może zniknąć w każdej chwili (np. usunie go inny proces).

4. Praktyka: piszemy kod z obsługą błędów

Stworzymy proste przykłady i sprawimy, by poprawnie reagowały na brak pliku i inne błędy.

Krok 1: Spróbujmy otworzyć nieistniejący plik

import java.io.*;

public class NotesApp {
    public static void main(String[] args) {
        String filename = "my_notes.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filename));
            String line;
            System.out.println("Twoje notatki:");
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("Oj! Nie znaleziono pliku z notatkami: " + filename);
            System.out.println("Wskazówka: utwórz plik lub sprawdź nazwę.");
        } catch (IOException e) {
            System.out.println("Wystąpił błąd podczas odczytu pliku: " + e.getMessage());
        }
    }
}

Krok 2: Dodajmy obsługę zapisu pliku

import java.io.*;

public class NotesWriter {
    public static void main(String[] args) {
        String filename = "my_notes.txt";
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(filename, true)); // append: true
            writer.write("Nowa notatka!\n");
            writer.close();
            System.out.println("Notatka została pomyślnie dodana!");
        } catch (IOException e) {
            System.out.println("Błąd podczas zapisu do pliku: " + e.getMessage());
        }
    }
}

Krok 3: Uniwersalny przykład z logowaniem błędów

import java.io.*;

public class SafeFileCopier {
    public static void main(String[] args) {
        String source = "source.txt";
        String target = "target.txt";
        try {
            BufferedReader reader = new BufferedReader(new FileReader(source));
            BufferedWriter writer = new BufferedWriter(new FileWriter(target));
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
            reader.close();
            writer.close();
            System.out.println("Plik skopiowano pomyślnie!");
        } catch (FileNotFoundException e) {
            System.out.println("Nie znaleziono pliku: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("Błąd wejścia/wyjścia: " + e.getMessage());
        }
    }
}

Wskazówka:
W rzeczywistych aplikacjach warto logować błędy nie tylko na ekran, ale też do pliku lub dziennika, aby później móc ustalić, co dokładnie poszło nie tak.

5. Tabela: najważniejsze wyjątki wejścia/wyjścia

Wyjątek Kiedy występuje? Jak obsłużyć?
FileNotFoundException
Nie znaleziono pliku, ścieżka nie istnieje, brak uprawnień Poinformować użytkownika, sprawdzić ścieżkę/uprawnienia, w razie potrzeby utworzyć plik
EOFException
Nieoczekiwany koniec pliku podczas odczytu Poinformować o uszkodzeniu/niekompletności, spróbować częściowo odzyskać dane
IOException
Ogólny błąd wejścia/wyjścia (dysk, uprawnienia, blokada) Sprawdzić szczegóły, poprawnie zakończyć operację, w miarę możliwości ponowić próbę
MalformedInputException
Nieprawidłowe kodowanie lub struktura pliku Poinformować o uszkodzeniu, spróbować innego kodowania/źródła

6. Typowe błędy przy obsłudze wyjątków wejścia/wyjścia

Błąd nr 1: Łapanie tylko ogólnego Exception. Bardzo kuszące jest napisać po prostu catch (Exception e), ale wtedy nie rozróżnisz, co dokładnie poszło nie tak. Lepiej najpierw łapać konkretne wyjątki (FileNotFoundException), a potem — ogólny IOException.

Błąd nr 2: Nie zamykanie strumieni przy błędach. Jeśli otworzyłeś plik, a potem wystąpił wyjątek, strumień może pozostać niezamknięty. Używaj try-with-resources lub zamykaj zasoby w finally.

Błąd nr 3: Ignorowanie komunikatów wyjątków. Nie wypisuj po prostu „Błąd!”, tylko pokazuj szczegóły: e.getMessage(). To pomoże szybciej zrozumieć, co poszło nie tak.

Błąd nr 4: Brak obsługi FileNotFoundException przy zapisie. Wiele osób myśli, że FileNotFoundException dotyczy tylko odczytu. W rzeczywistości może wystąpić także przy zapisie (nieprawidłowa ścieżka, brak uprawnień do utworzenia pliku itd.).

Błąd nr 5: Niesprawdzanie uprawnień dostępu. Jeśli program uruchamiany jest z ograniczonymi uprawnieniami, wiele operacji na plikach może zakończyć się błędem. Zawsze bierz to pod uwagę i informuj użytkownika.

1
Zadanie
JAVA 25 SELF, poziom 38, lekcja 0
Niedostępne
Poszukiwanie zagubionego zapisu w starożytnej bibliotece 📚
Poszukiwanie zagubionego zapisu w starożytnej bibliotece 📚
1
Zadanie
JAVA 25 SELF, poziom 38, lekcja 0
Niedostępne
Zapis ważnej depeszy w królewskim archiwum 📜
Zapis ważnej depeszy w królewskim archiwum 📜
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION