W3docs

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 + child

Il 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 string

Interrogare 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 IOException

getAbsolutePath 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 lanciare IOException.

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);                            // FileFilter

Sia 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) { ... }         // correct

Il 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 boolean

Il 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 back

I 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.

java— editable, runs on the server

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 il FileFilter a un argomento. Entrambi sono interfacce funzionali, lo stesso vocabolario della Parte 12 — File era "lambda-ready" molto prima che esistessero i lambda.
  • notDir.listFiles() ha restituito null perché data.csv non è una directory. Il ciclo for avrebbe lanciato una NPE se avessimo saltato il controllo null. Files.list(path) solleva un'eccezione chiara per lo stesso caso.
  • ghost.delete(), a.delete() e sub.delete() hanno tutti restituito un boolean. I primi due sono facili da interpretare; il terzo ha restituito false perché 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 che Files.delete(path) colma.
  • root.toPath() è il ponte verso java.nio.file. Una volta ottenuto un Path, si applica il resto della Parte 13 — Files.readString, Files.lines, Files.walk, tutti gli helper static.

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.

Pratica

Pratica
`dir.listFiles()` su un `File` che punta a un file normale (non una directory) restituisce…
`dir.listFiles()` su un `File` che punta a un file normale (non una directory) restituisce…
Was this page helpful?