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ść |
|---|---|---|---|
|
Tak | Tak (isFile(), isDirectory()) | Stary |
|
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 try–catch, 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 try–catch.
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".
GO TO FULL VERSION