1. Las excepciones como parte del API
¿Por qué las excepciones son parte del «contrato» del método?
Cuando escribes un método, defines no solo los parámetros y el valor de retorno, sino también qué excepciones puede lanzar. Esto forma parte del «contrato» entre tu método y quienes lo van a utilizar. Si un método puede lanzar una excepción, todos los que lo llamen deben saberlo —para manejar correctamente el error (o al menos no sorprenderse cuando el programa termine inesperadamente con un error).
Ejemplo:
public void readFile(String filename) throws IOException {
// ... lectura del archivo
}
Aquí se indica explícitamente que el método puede lanzar IOException. Es una señal para el usuario del método: «¡Prepárate para manejar el error de lectura del archivo!»
Documentar excepciones: anotación @throws en Javadoc
Para que otros desarrolladores comprendan qué excepciones puede lanzar tu método, utiliza la anotación @throws (o @exception) en Javadoc.
Ejemplo:
/**
* Lee el contenido de un archivo.
*
* @param filename nombre de archivo
* @return contenido del archivo como una cadena
* @throws IOException si se produjo un error de lectura del archivo
*/
public String readFile(String filename) throws IOException {
// ...
}
¿Para qué sirve?
- Ayuda a otros desarrolladores a entender qué errores deben manejar.
- Las IDE y los generadores de documentación (por ejemplo, Javadoc) muestran estas excepciones directamente en las sugerencias.
- Aumenta la fiabilidad y la previsibilidad del código.
Excepciones checked y unchecked en el API
Excepciones comprobadas (checked) (herederas de Exception, pero no de RuntimeException) — forman parte del contrato del método. Deben manejarse o declararse explícitamente para propagarse (throws).
Excepciones no comprobadas (unchecked) (RuntimeException y sus subclases) — suelen señalar errores de programación (por ejemplo, NullPointerException, IllegalArgumentException). No es obligatorio indicarlas en la firma, pero si tu método puede lanzar este tipo de error (por ejemplo, con argumentos incorrectos), también conviene describirlo en el Javadoc.
Ejemplo:
/**
* Divide a entre b.
* @param a dividendo
* @param b divisor
* @return resultado de la división
* @throws IllegalArgumentException si b == 0
*/
public int divide(int a, int b) {
if (b == 0) throw new IllegalArgumentException("El divisor no puede ser cero");
return a / b;
}
Excepciones y diseño del API
- Piensa en el usuario de tu método: ¿qué errores puede y debe manejar? ¿Cuáles son fallos (bugs) y cuáles son situaciones «normales» de operación?
- No abuses de las excepciones checked: si el error es un bug (por ejemplo, un argumento inválido), es mejor lanzar una excepción unchecked.
- Documenta todas las excepciones que puedan propagarse «hacia arriba».
2. Construcción try-with-resources
Problema: ¿cómo cerrar recursos de forma segura?
En muchas tareas hay que trabajar con recursos que se deben cerrar después de usarlos: archivos, conexiones de red, bases de datos, etc. Si olvidas cerrar un recurso, puedes sufrir fugas de memoria, bloqueo de archivos u otros problemas.
Antes:
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
// ...
} catch (IOException e) {
// manejo del error
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// manejo del error al cerrar
}
}
}
Mucho código; es fácil equivocarse y se puede olvidar cerrar el recurso.
Solución: try-with-resources
Desde Java 7, existe la construcción try-with-resources, que cierra automáticamente todos los recursos, incluso si dentro del bloque se produce una excepción.
Sintaxis:
try (ResourceType resource = new ResourceType(...)) {
// trabajo con el recurso
} catch (ExceptionType e) {
// manejo del error
}
Ejemplo:
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("Error al leer el archivo: " + e.getMessage());
}
// ¡reader.close() se invocará automáticamente!
¿Cómo funciona?
- Entre paréntesis tras try se declaran los recursos que deben cerrarse.
- Al salir del bloque try (¡incluso si se produce una excepción!), para cada recurso se invoca el método close().
- Esto solo funciona para recursos que implementan la interfaz AutoCloseable (o su predecesor Closeable).
Interfaz AutoCloseable:
public interface AutoCloseable {
void close() throws Exception;
}
Todos los recursos estándar de Java (archivos, flujos, conexiones con BD) implementan esta interfaz.
Se pueden declarar varios recursos
try (
BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
Ambos recursos se cerrarán automáticamente, incluso si se produce una excepción.
Ventajas de try-with-resources
- Seguridad: los recursos siempre se cierran, incluso ante errores.
- Brevedad: menos código, menos posibilidades de equivocación.
- Legibilidad: se ve de inmediato qué recursos se usan y cuándo se cierran.
3. Práctica: escribimos código seguro con try-with-resources
Ejemplo: lectura de un archivo
public static void printFirstLine(String filename) {
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line = reader.readLine();
System.out.println("Primera línea: " + line);
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
}
Ejemplo: escritura en un archivo
public static void writeToFile(String filename, String text) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
writer.write(text);
} catch (IOException e) {
System.out.println("Error al escribir: " + e.getMessage());
}
}
Ejemplo: recurso propio
Si escribes tu propia clase que deba cerrarse, simplemente implementa AutoCloseable:
public class MyResource implements AutoCloseable {
@Override
public void close() {
System.out.println("¡Recurso cerrado!");
}
}
Ahora puedes usarlo en try-with-resources:
try (MyResource res = new MyResource()) {
// trabajo con el recurso
}
4. Errores típicos y mejores prácticas
Error n.º 1: olvidar cerrar el recurso (sin try-with-resources).
Si no utilizas try-with-resources, es fácil olvidar cerrar un archivo o flujo —esto lleva a fugas de recursos.
Error n.º 2: intentar usar try-with-resources con un objeto que no implementa AutoCloseable.
Si tu clase no implementa esta interfaz, el compilador no permitirá usarla en try-with-resources.
Error n.º 3: no documentar las excepciones en el API.
Si tu método puede lanzar una excepción, asegúrate de indicarlo en la firma (throws) y en Javadoc (@throws). Esto ayudará a otros a usar correctamente tu código.
Error n.º 4: capturar Exception en lugar de errores concretos.
Es mejor capturar solo aquellas excepciones que realmente esperas y sabes manejar.
GO TO FULL VERSION