Java File Class
Gestisci i percorsi del file system in Java con java.io.File: exists, isFile, isDirectory, listFiles e bridging a Path.
java.io.File è il tipo originale "questa stringa è un percorso" introdotto in Java 1.0. La classe non esegue alcuna operazione di I/O — non apre, legge né scrive dati — si limita a nominare una posizione nel file system e offre una serie di metodi per interrogare il sistema operativo riguardo a quella posizione e per eseguire operazioni singole su di essa (exists, isDirectory, delete, renameTo, listFiles).
java.nio.file.Path (Java 7) è il sostituto moderno e quello che il nuovo codice dovrebbe usare, ma incontrerai File in qualsiasi codebase precedente al ~2012, e molte API più datate ancora lo accettano e lo restituiscono. Questo capitolo illustra cosa fa, dove si trovano i punti critici e come si integra con Path.
Costruzione
Un File racchiude una stringa di percorso. Quattro costruttori coprono i casi comuni:
File a = new File("data/users.txt"); // relative to the JVM's working directory
File b = new File("/var/log/app.log"); // absolute
File c = new File("/tmp", "session.txt"); // parent + child
File d = new File(new File("/tmp"), "session.txt"); // parent File + childIl costruttore non esegue alcuna validazione — passare un percorso privo di senso costruisce un File senza problemi; solo quando si chiama exists(), delete(), ecc. il sistema operativo viene coinvolto.
Usa il costruttore a due argomenti per "genitore + nome" invece della concatenazione di stringhe. Sceglie il separatore corretto (/ su Unix, \ su Windows) ed evita il bug in cui il percorso del genitore potrebbe o meno terminare con un separatore:
File good = new File(parentDir, "data.txt"); // separator handled for you
File bad = new File(parentDir + "/data.txt"); // brittle: depends on parentDir's exact stringInterrogare il file system
File espone un ampio insieme di query che restituiscono boolean e long. Le più comuni:
File f = new File("data/users.txt");
f.exists(); // does the path point to anything?
f.isFile(); // is it a regular file?
f.isDirectory(); // is it a directory?
f.isHidden(); // hidden by OS convention (leading dot on Unix, hidden attr on Windows)
f.length(); // size in bytes (0 for a directory)
f.lastModified(); // epoch millis; 0 if it doesn't exist or can't be queried
f.canRead(); // permission check from the JVM's point of view
f.canWrite();
f.canExecute();Queste chiamate accedono tutte al sistema operativo. Sono economiche singolarmente, ma non gratuite — chiamare exists(), poi isDirectory(), poi length() equivale a tre syscall. Se hai bisogno di più attributi di un singolo file, Files.readAttributes(path, BasicFileAttributes.class) (parte successiva) è una sola syscall.
Viste del percorso
File offre diversi modi per osservare la stessa stringa sottostante:
File f = new File("data/../data/users.txt");
f.getName(); // "users.txt" — last component
f.getParent(); // "data/../data" — String, or null at the root
f.getParentFile(); // File for the parent, or null
f.getPath(); // "data/../data/users.txt" — what you constructed
f.getAbsolutePath(); // resolved against working dir, NOT canonicalised
f.getCanonicalPath(); // resolved, normalised, symlinks followed — can throw IOExceptiongetAbsolutePath e getCanonicalPath sono la coppia più facile da confondere nella classe:
getAbsolutePath— antepone la directory di lavoro corrente della JVM se il percorso è relativo. Restituisce la stringa con i segmenti..ancora presenti.getCanonicalPath— come il percorso assoluto, poi risolve..e., poi segue i link simbolici. Può accedere al disco e lanciareIOException.
Per i controlli di sicurezza (questo percorso fornito dall'utente è all'interno della directory consentita?), getCanonicalPath è l'unico sicuro — altrimenti un percorso relativo come safe-dir/../../../etc/passwd supera un controllo startsWith("safe-dir").
Elencare una directory
Quattro varianti, due coppie:
File dir = new File("/tmp");
String[] names = dir.list(); // child names, no metadata
File[] children = dir.listFiles(); // child File objects
String[] txt = dir.list((d, name) -> name.endsWith(".txt")); // FilenameFilter
File[] files = dir.listFiles(File::isFile); // FileFilterSia FilenameFilter che FileFilter sono interfacce funzionali a metodo singolo (vocabolario della Parte 12), quindi un lambda o un riferimento a metodo funziona direttamente. La differenza: FilenameFilter riceve la directory genitore e il nome semplice; FileFilter riceve il File figlio costruito. Usa FileFilter se hai bisogno di chiamare isDirectory() o length() per decidere; usa FilenameFilter se la corrispondenza per nome è sufficiente.
Tutti e quattro i metodi restituiscono null se il percorso non è una directory — non lanciano eccezioni. Questa è una fonte classica di NPE:
for (File child : dir.listFiles()) { ... } // NPE if dir is not a directory!
File[] children = dir.listFiles();
if (children != null) for (File c : children) { ... } // correctIl moderno Files.list(path) restituisce uno Stream<Path> vuoto per una directory mancante oppure lancia una chiara NotDirectoryException. L'API File restituisce semplicemente null e ti lascia crashare.
Creare, eliminare, rinominare
File espone alcuni metodi di mutazione:
f.createNewFile(); // creates an empty file; returns boolean; throws IOException on real failure
f.mkdir(); // creates this directory; parent must exist
f.mkdirs(); // creates this directory and any missing parents
f.delete(); // deletes this file or empty directory; returns boolean
f.renameTo(other); // OS-specific behaviour; returns booleanIl tema ricorrente — i valori restituiti boolean che non ti dicono perché — è la ragione principale per cui esiste Files. f.delete() restituisce false se il file non esisteva, se non avevi i permessi, se era una directory non vuota, o se un altro processo lo teneva aperto su Windows. Non puoi capire quale sia il motivo dal valore restituito. Il corrispondente Files.delete(path) lancia un'eccezione specifica (NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException) ed è l'API che vuoi per la gestione reale degli errori.
renameTo è il caso peggiore: può fallire senza sollevare alcuna eccezione, e le modalità di fallimento (rinomina tra volumi, destinazione esistente, permessi, lock) dipendono dal sistema operativo. Files.move(src, dst, REPLACE_EXISTING) è il sostituto moderno e comunica l'errore specifico.
Integrare con Path
Ogni File conosce il proprio Path e viceversa:
File f = new File("data/users.txt");
Path p = f.toPath(); // bridge to java.nio.file
File g = p.toFile(); // bridge backI due interoperano a basso costo. Quando sei bloccato con un'API legacy che restituisce File, la mossa giusta è di solito f.toPath() e poi chiamare Files.* su di esso. Il nuovo codice dovrebbe partire da Path.of(...) e convertire in File solo nel punto in cui si chiama un metodo legacy.
Esempio pratico: costruire un albero e visitarlo con File
Il programma seguente crea un piccolo albero di directory sotto la directory temporanea del sistema, lo popola con alcuni file, quindi interroga ogni elemento con File. Dimostra i lambda FilenameFilter e FileFilter, la trappola del ritorno null, la risoluzione del percorso canonico e il problema delle informazioni mancanti sugli errori con delete(). Ogni artefatto viene rimosso con deleteOnExit.
Cosa ricavare dall'esecuzione:
a.getCanonicalPath()ha stampato un percorso assoluto normalizzato senza segmenti...getAbsolutePath()non normalizza — per un controllo di sicurezza, la versione canonica è quella da confrontare con un prefisso consentito.- La forma
FilenameFilter(d, name) -> name.endsWith(".txt")è un lambda a due argomenti;File::isFileè un riferimento a metodo per ilFileFiltera un argomento. Entrambi sono interfacce funzionali, lo stesso vocabolario della Parte 12 —Fileera "lambda-ready" molto prima che esistessero i lambda. notDir.listFiles()ha restituitonullperchédata.csvnon è una directory. Il cicloforavrebbe lanciato una NPE se avessimo saltato il controllo null.Files.list(path)solleva un'eccezione chiara per lo stesso caso.ghost.delete(),a.delete()esub.delete()hanno tutti restituito unboolean. I primi due sono facili da interpretare; il terzo ha restituitofalseperché la directory non era vuota, e l'API non ci ha fornito nulla per distinguere "non era vuota" da "non avevi i permessi". Questo è il divario cheFiles.delete(path)colma.root.toPath()è il ponte versojava.nio.file. Una volta ottenuto unPath, si applica il resto della Parte 13 —Files.readString,Files.lines,Files.walk, tutti gli helperstatic.
Cosa viene dopo
Il capitolo successivo, Creare file in Java, tratta i tre modi per creare un nuovo file o directory — il legacy File.createNewFile e mkdir(s), più i moderni Files.createFile, Files.createDirectory e Files.createDirectories — e quale scegliere per ogni caso.