W3docs

Clase File de Java

Representa rutas del sistema de archivos en Java con la clase legacy java.io.File: exists, isFile, isDirectory, listFiles.

java.io.File es el tipo original "esta cadena es una ruta" de Java 1.0. La clase en sí no realiza I/O — no abre, lee ni escribe datos — simplemente nombra una ubicación en el sistema de archivos y ofrece un conjunto de métodos para consultar al sistema operativo sobre dicha ubicación y realizar operaciones puntuales sobre ella (exists, isDirectory, delete, renameTo, listFiles).

java.nio.file.Path (Java 7) es el reemplazo moderno y es lo que debería usarse en código nuevo, pero encontrarás File en toda base de código anterior a ~2012, y muchas APIs antiguas aún aceptan y devuelven este tipo. Este capítulo cubre lo que hace, dónde están sus limitaciones y cómo se conecta con Path.

Construcción

Un File envuelve una cadena de ruta. Cuatro constructores cubren los casos más comunes:

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

El constructor no realiza ninguna validación — pasar una ruta sin sentido construye un File sin problemas; solo cuando llamas a exists(), delete(), etc. el sistema operativo se involucra.

Usa el constructor de dos argumentos para "padre + nombre" en lugar de concatenación de cadenas. Elige el separador correcto (/ en Unix, \ en Windows) y evita el error donde la ruta padre puede o no terminar en un separador:

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

Consulta del sistema de archivos

File expone un amplio conjunto de consultas que devuelven boolean y long. Las más comunes:

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();

Cada una de estas llamadas accede al sistema operativo. Son baratas individualmente, pero no son gratuitas — llamar a exists(), luego isDirectory() y luego length() son tres llamadas al sistema. Si necesitas varios atributos de un mismo archivo, Files.readAttributes(path, BasicFileAttributes.class) (próxima parte) es una sola llamada al sistema.

Vistas de la ruta

File te ofrece varias formas de ver la misma cadena subyacente:

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 y getCanonicalPath son el par más confuso de la clase:

  • getAbsolutePath — antepone el directorio de trabajo actual del JVM si la ruta es relativa. Devuelve la cadena con los segmentos .. aún presentes.
  • getCanonicalPath — igual que la ruta absoluta, pero luego resuelve .. y ., y sigue los enlaces simbólicos. Puede acceder al disco y lanzar IOException.

Para verificaciones sensibles a la seguridad (¿está esta ruta proporcionada por el usuario dentro del directorio permitido?), getCanonicalPath es la única opción segura — de lo contrario, una ruta relativa como safe-dir/../../../etc/passwd puede eludir una verificación con startsWith("safe-dir").

Listado de un directorio

Cuatro variantes, dos pares:

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

Tanto FilenameFilter como FileFilter son interfaces funcionales de un solo método (vocabulario de la Parte 12), por lo que una lambda o una referencia a método funcionan directamente. La diferencia: FilenameFilter recibe el directorio padre y el nombre simple; FileFilter recibe el File hijo construido. Usa FileFilter si necesitas llamar a isDirectory() o length() para decidir; usa FilenameFilter si la coincidencia por nombre es suficiente.

Los cuatro métodos devuelven null si la ruta no es un directorio — no lanzan excepción. Eso es una fuente clásica de 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

El moderno Files.list(path) devuelve un Stream<Path> vacío para un directorio inexistente o lanza una NotDirectoryException clara. La API de File simplemente devuelve null y te deja que el programa falle.

Crear, eliminar, renombrar

File expone algunos métodos de mutación:

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

El patrón recurrente — valores de retorno boolean que no indican por qué — es la razón principal por la que existe Files. f.delete() devuelve false si el archivo no existía, si carecías de permisos, si era un directorio no vacío, o si otro proceso lo tenía abierto en Windows. No puedes saber cuál fue el motivo a partir del valor de retorno. El correspondiente Files.delete(path) lanza una excepción específica (NoSuchFileException, AccessDeniedException, DirectoryNotEmptyException) y es la API que quieres para un manejo de errores real.

renameTo es el peor caso: puede fallar sin lanzar ninguna excepción, y los modos de fallo (renombrado entre volúmenes, el destino ya existe, permisos, bloqueos) dependen del sistema operativo. Files.move(src, dst, REPLACE_EXISTING) es el reemplazo moderno e indica qué salió mal.

Conexión con Path

Cada File conoce su Path y viceversa:

File f = new File("data/users.txt");
Path p = f.toPath();              // bridge to java.nio.file
File g = p.toFile();              // bridge back

Los dos interoperan de forma económica. Cuando estás atrapado con una API legacy que devuelve File, lo habitual es llamar a f.toPath() y luego usar Files.* sobre él. El código nuevo debería comenzar con Path.of(...) y convertir a File solo en el punto de llamada de un método legacy.

Ejemplo práctico: construir un árbol y recorrerlo con File

El programa siguiente crea un pequeño árbol de directorios bajo el directorio temporal del sistema, lo puebla con algunos archivos y luego consulta cada entrada con File. Demuestra las lambdas FilenameFilter y FileFilter, la trampa del retorno nulo, la resolución de rutas canónicas y el problema de la falta de información de error con delete(). Todos los artefactos se limpian con deleteOnExit.

java— editable, runs on the server

Qué extraer de la ejecución:

  • a.getCanonicalPath() imprimió una ruta absoluta normalizada sin segmentos ... getAbsolutePath() no normaliza — para una verificación de seguridad, la versión canónica es la que debes comparar contra un prefijo permitido.
  • La forma FilenameFilter (d, name) -> name.endsWith(".txt") es una lambda de dos argumentos; File::isFile es una referencia a método para el FileFilter de un argumento. Ambas son interfaces funcionales, el mismo vocabulario que la Parte 12 — File ha estado "lista para lambdas" mucho antes de que existieran las lambdas.
  • notDir.listFiles() devolvió null porque data.csv no es un directorio. El bucle for habría lanzado un NPE si hubiéramos omitido la comprobación de nulo. Files.list(path) lanza una excepción clara para el mismo caso.
  • ghost.delete(), a.delete() y sub.delete() devolvieron un boolean. Los dos primeros son fáciles de interpretar; el tercero devolvió false porque el directorio no estaba vacío, y la API no nos dio nada para distinguir "no estaba vacío" de "sin permisos". Esa es la brecha que cierra Files.delete(path).
  • root.toPath() es el puente hacia java.nio.file. Una vez que tienes un Path, el resto de la Parte 13 aplica — Files.readString, Files.lines, Files.walk, todos los helpers static.

Qué sigue

El próximo capítulo, Creación de archivos en Java, cubre las tres formas de crear un nuevo archivo o directorio — el legacy File.createNewFile y mkdir(s), más los modernos Files.createFile, Files.createDirectory y Files.createDirectories — y cuál elegir según el caso.

Práctica

Práctica
`dir.listFiles()` en un `File` que apunta a un archivo regular (no a un directorio) devuelve…
`dir.listFiles()` en un `File` que apunta a un archivo regular (no a un directorio) devuelve…
Was this page helpful?