CodeGym /Kursy /JAVA 25 SELF /Sprawdzanie istnienia plików i katalogów

Sprawdzanie istnienia plików i katalogów

JAVA 25 SELF
Poziom 38 , Lekcja 1
Dostępny

1. Metody sprawdzania istnienia plików i katalogów

Praca z plikami bywa jak spacer po polu minowym: nigdy nie wiesz, co czeka za następnym bajtem. Dobra wiadomość: Java daje nam „wykrywacz metalu” — metody do sprawdzania istnienia plików i katalogów.

Klasa File: sprawdzanie przez exists(), isFile(), isDirectory()

Klasyczny sposób — użyć klasy java.io.File:

File file = new File("example.txt");
if (file.exists()) {
    System.out.println("Plik istnieje!");
} else {
    System.out.println("Pliku nie znaleziono.");
}

Metoda exists() zwraca true, jeśli plik lub katalog o takiej nazwie istnieje. Ale to nie wszystko! Czasem trzeba wiedzieć, co dokładnie istnieje: plik czy katalog.

if (file.isFile()) {
    System.out.println("To jest plik.");
} else if (file.isDirectory()) {
    System.out.println("To jest katalog.");
} else {
    System.out.println("Nic nie znaleziono.");
}

Klasy Path i Files: nowoczesne podejście

We współczesnych programach lepiej używać nowszego i potężniejszego API java.nio.file. Tutaj do sprawdzania istnienia używa się metody statycznej Files.exists():

import java.nio.file.*;

Path path = Paths.get("example.txt");
if (Files.exists(path)) {
    System.out.println("Plik znaleziony za pomocą NIO!");
}

Do sprawdzenia typu obiektu użyj:

if (Files.isRegularFile(path)) {
    System.out.println("To jest zwykły plik!");
}
if (Files.isDirectory(path)) {
    System.out.println("To jest katalog!");
}

Wskazówka: do nowych projektów lepiej od razu używać NIO (Path, Files), ponieważ to API jest nowocześniejsze, obsługuje więcej funkcji i dobrze współpracuje z try-with-resources.

Tabela: porównanie podejść

Sposób Sprawdzenie istnienia Sprawdzenie typu (plik/katalog) Nowoczesność
File.exists()
Tak Tak (isFile(), isDirectory()) Stary
Files.exists(Path)
Tak Tak (isRegularFile(), isDirectory()) Zalecane

2. Problem TOCTOU: dlaczego sprawdzenie to nie panaceum

Na czym polega problem?

Załóżmy, że sprawdziłeś: „Plik istnieje!” — i od razu postanowiłeś go odczytać. Jednak między tymi dwoma działaniami może upłynąć wieczność w kategoriach procesora. W tym czasie plik może zostać usunięty, przeniesiony, podmieniony przez inny proces albo mogą zmienić się jego uprawnienia.

To jak zajrzeć do lodówki, zauważyć, że jest tort, zamknąć drzwiczki, a potem otworzyć je ponownie — i odkryć, że tort już zjedzony. W programowaniu też są tacy „domownicy”: inne procesy, użytkownicy, antywirusy, sama system plików.

Dlaczego to ważne?

W rezultacie, nawet jeśli sprawdziłeś, że plik istnieje, przy próbie jego otwarcia i tak może wystąpić wyjątek — na przykład FileNotFoundException lub AccessDeniedException.

Samo sprawdzenie to nie gwarancja, a jedynie dodatkowe zabezpieczenie. Zawsze bądź przygotowany na wyjątki i obsługuj je!

3. Praktyka: sprawdzamy istnienie pliku przed odczytem

Dodajmy funkcję, która wypisuje zawartość pliku, jeśli on istnieje, i informuje użytkownika, jeśli pliku nie ma. Pokażemy dwie wersje: przez stare i nowe API.

Wariant 1: przez File

import java.io.*;

public class FileExistenceCheck {
    public static void main(String[] args) {
        File file = new File("notes.txt");
        if (file.exists() && file.isFile()) {
            try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                System.out.println("Błąd podczas odczytu pliku: " + e.getMessage());
            }
        } else {
            System.out.println("Plik 'notes.txt' nie został znaleziony.");
        }
    }
}

Wariant 2: przez Path i Files

import java.nio.file.*;
import java.io.IOException;

public class PathExistenceCheck {
    public static void main(String[] args) {
        Path path = Paths.get("notes.txt");
        if (Files.exists(path) && Files.isRegularFile(path)) {
            try {
                Files.lines(path).forEach(System.out::println);
            } catch (IOException e) {
                System.out.println("Błąd podczas odczytu pliku: " + e.getMessage());
            }
        } else {
            System.out.println("Plik 'notes.txt' nie został znaleziony.");
        }
    }
}

Nawet po sprawdzeniu — łapiemy wyjątki!

W obu przykładach, mimo wstępnego sprawdzenia, i tak używamy bloków trycatch, ponieważ plik może zniknąć lub stać się niedostępny w każdej chwili. To złota zasada pracy z plikami!

4. Sprawdzanie istnienia katalogu

Analogicznie można sprawdzić istnienie katalogu, a w razie braku — utworzyć go:

import java.nio.file.*;

public class DirectoryCheck {
    public static void main(String[] args) {
        Path dir = Paths.get("data");
        if (Files.exists(dir) && Files.isDirectory(dir)) {
            System.out.println("Katalog 'data' znaleziony.");
        } else {
            System.out.println("Katalog 'data' nie został znaleziony. Tworzymy...");
            try {
                Files.createDirectory(dir);
                System.out.println("Katalog utworzony!");
            } catch (IOException e) {
                System.out.println("Błąd podczas tworzenia katalogu: " + e.getMessage());
            }
        }
    }
}

Nawiasem mówiąc:
Metoda Files.createDirectory() zgłasza wyjątek, jeśli katalog już istnieje. Jeśli chcesz utworzyć łańcuch katalogów (na przykład "data/2025/09"), użyj Files.createDirectories(), która nie narzeka, jeśli część katalogów już istnieje.

5. Specyfika i niuanse: ścieżki względne i bezwzględne

Ścieżki względne

Gdy piszesz "notes.txt", program szuka pliku w „bieżącym katalogu roboczym”. To, gdzie on jest, zależy od tego, jak i skąd uruchamiasz aplikację (IDE, terminal, podwójne kliknięcie w JAR itd.).

Ścieżki bezwzględne

Jeśli musisz mieć pewność, gdzie szukać, lepiej użyć ścieżek bezwzględnych albo budować je dynamicznie:

String userHome = System.getProperty("user.home");
Path filePath = Paths.get(userHome, "myapp", "notes.txt");

Sprawdzanie typu obiektu

Czasem „plik” może niespodziewanie okazać się katalogiem. Dlatego sprawdzaj nie tylko istnienie, ale i typ:

if (Files.isRegularFile(path)) {
    // To jest właśnie plik!
}
if (Files.isDirectory(path)) {
    // To jest katalog!
}

6. Demonstracja problemu TOCTOU w praktyce

Zasymulujmy sytuację, w której plik znika po sprawdzeniu, ale przed otwarciem. Uruchom kod i usuń plik ręcznie między sprawdzeniem a odczytem:

import java.io.*;
import java.nio.file.*;

public class TOCTOUExample {
    public static void main(String[] args) {
        Path path = Paths.get("notes.txt");
        if (Files.exists(path)) {
            System.out.println("Plik znaleziono, zaraz będziemy czytać...");
            // W tym momencie otwórz eksplorator i usuń plik "notes.txt" ręcznie!
            try {
                Files.lines(path).forEach(System.out::println);
            } catch (IOException e) {
                System.out.println("Oj! Plik zniknął: " + e.getMessage());
            }
        } else {
            System.out.println("Pliku nie znaleziono.");
        }
    }
}

Wynik:
Jeśli zdążysz usunąć plik między sprawdzeniem a odczytem, otrzymasz wyjątek. To dobitnie pokazuje, że nawet staranna weryfikacja nie chroni przed nieprzewidzianymi zmianami.

7. Typowe błędy przy sprawdzaniu istnienia plików i katalogów

Błąd nr 1: Polegać wyłącznie na sprawdzeniu, bez użycia try–catch. Skoro „plik jest”, to można go spokojnie czytać — myślą początkujący. Jednak plik może zniknąć lub stać się niedostępny i program się „wysypie”.

Błąd nr 2: Sprawdzać tylko istnienie, nie sprawdzając typu. Jeśli sprawdzasz tylko exists(), a nie doprecyzowujesz, czy to plik, czy katalog, możesz spróbować otworzyć katalog jak plik — i dostać błąd.

Błąd nr 3: Używać ścieżek względnych bez zrozumienia katalogu roboczego. Program szuka pliku „nie tam, gdzie trzeba”, a użytkownik nie rozumie, dlaczego nic nie działa.

Błąd nr 4: Nie uwzględniać uprawnień dostępu. Plik lub katalog może istnieć, ale brak praw do odczytu/zapisu. Takie błędy ujawniają się dopiero przy próbie otwarcia — zawsze używaj trycatch.

Błąd nr 5: Nie uwzględniać wielkości liter w nazwie pliku na różnych OS. W Windows nazwy plików są nieczułe na wielkość liter, a w Linuksie — czułe. Program może nie znaleźć "Notes.txt", jeśli szuka "notes.txt".

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION