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 + childEl 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 stringConsulta 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 IOExceptiongetAbsolutePath 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 lanzarIOException.
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); // FileFilterTanto 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) { ... } // correctEl 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 booleanEl 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 backLos 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.
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::isFilees una referencia a método para elFileFilterde un argumento. Ambas son interfaces funcionales, el mismo vocabulario que la Parte 12 —Fileha estado "lista para lambdas" mucho antes de que existieran las lambdas. notDir.listFiles()devolviónullporquedata.csvno es un directorio. El bucleforhabrí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()ysub.delete()devolvieron unboolean. Los dos primeros son fáciles de interpretar; el tercero devolviófalseporque 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 cierraFiles.delete(path).root.toPath()es el puente haciajava.nio.file. Una vez que tienes unPath, el resto de la Parte 13 aplica —Files.readString,Files.lines,Files.walk, todos los helpersstatic.
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.