Java7, Mejora en el manejo de recursos

Anuncio
Java7, Mejora en el manejo de recursos.
En este contexto entendemos por “recurso” un objeto que después de haber sido inicializado
con éxito debe ser liberado manualmente por el desarrollador (y no automáticamente por el
colector de basura) cuando su ciclo de vida haya concluido. Por ejemplo objetos files, streams,
sockets y database connections.
Estas operaciones manuales degradan la legibilidad del código desviando la atención de lo
importante y generando código superfluo, son una fuente recurrente de errores incluso en
programadores avanzados (incluso en la propia implementación de la JDK!).
Por eso se ha modificado la sintaxis del lenguaje para permitir la gestión automática y segura
de estos recursos, liberando así al desarrollador de su gestión y mejorando de paso la
legibilidad del código, con una nueva sentencia try-with-resources.
A continuación mostramos un ejemplo de un manejo incorrecto, un manejo correcto en
versiones anteriores de Java, y el manejo mejorado en Java7.
private void incorrectWriting() throws IOException {
DataOutputStream out = new DataOutputStream(new
FileOutputStream("data"));
out.writeInt(666);
out.writeUTF("Hello");
out.close();
}
private void correctWriting() throws IOException {
DataOutputStream out = null;
try {
out = new DataOutputStream(new FileOutputStream("data"));
out.writeInt(666);
out.writeUTF("Hello");
} finally {
if (out != null) {
out.close();
}
}
}
private void writingWithARM() throws IOException {
try (DataOutputStream out
= new DataOutputStream(new FileOutputStream("data"))) {
out.writeInt(666);
out.writeUTF("Hello");
}
}
A primera vista la sintaxis incorrecta parece inofensiva, incluso bella en su sencillez. Sin
embargo si entre la inicialización y el cierre del objeto out se produce una excepción, el objeto
no se cerrará y por tanto no se liberará la memoria que consume. Es más, el propio método
close () puede generar una excepción y no liberar dicha memoria. Si este método se ejecuta
suficientes veces, o el mismo error se encuentra en otros métodos similares, entonces los
recursos no liberados pueden suponer un problema silencioso llegando incluso a consumir
toda la memoria disponible provocando un fallo del programa. Se trata además de un error
difícil de rastrear.
La forma correcta es más engorrosa y desde luego menos grácil y bella. Incluso debemos
propagar la Excepción al método que invoca para asegurarnos de que liberamos ese recurso.
La nueva sintaxis no sólo elimina el código añadido en la forma correcta, sino que además
elimina del código la instrucción close() que será invocada automáticamente. Los bloques
catch y finally son opcionales.
También es extensible si queremos utilizar varios recursos en el mismo bloque aunque no es
seguro.
try (
FileOutputStream out = new FileOutputStream("output");
FileInputStream in1 = new FileInputStream("input1");
FileInputStream in2 = new FileInputStream("input2")
) {
// Do something useful with those 3 streams!
}
// out, in1 and in2 will be closed in ¿any? case
No obstante una buena práctica recomendada es utilizar sentencias separadas para cada
recurso para asegurar que se invoquen todos los método close(): una excepción en uno
de los métodos close() provocará que no se invoque a los que quedaban pendientes.
También es una solución extensible a nuestros propios recursos, simplemente debemos
implementar AutoCloseable:
public class AutoClose implements AutoCloseable {
@Override
public void close() {
System.out.println(">>> close()");
throw new RuntimeException("Exception in close()");
}
public void work() throws MyException {
System.out.println(">>> work()");
throw new MyException("Exception in work()");
}
}
public static void main(String[] args) {
try (AutoClose autoClose = new AutoClose()) {
autoClose.work();
} catch (MyException e) {
e.printStackTrace();
}
}
class MyException extends Exception {
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
Observando la salida de consola del método main, verificamos que close() es invocado antes
incluso de atender la excepción generada en work(), y que la excepción generada en el
método close() no enmascara a la raíz generada en work() como ocurría en versiones
anteriores:
>>> work()
>>> close()
MyException: Exception in work()
at AutoClose.work(AutoClose.java:11)
at AutoClose.main(AutoClose.java:16)
Suppressed: java.lang.RuntimeException: Exception in close()
at AutoClose.close(AutoClose.java:6)
at AutoClose.main(AutoClose.java:17)
Para conseguir esto, ha sido necesario introducir dos nuevas extensiones en la clase
java.lang.Throwable:
•
•
final void addSuppressed(Throwable exception) añade una
excepción suprimida a otra excepción para evitar que la suprimida enmascare a
la raíz.
public final Throwable[] getSuppressed() devuelve la excepción
suprimida añadida a una excepción raíz.
public
Ventajas
•
Código más legible.
•
Automatizar siempre es bueno: mayor productividad, reduce errores.
•
Solución fácilmente extensible a mis propios recursos.
•
La interpretación del compilador hace que no suponga un consumo extra de recursos
esta gestión de excepciones.
Inconvenientes
•
No me gusta cómo se asegura que el manejo de recursos es seguro y luego de pasada
se menciona que no es seguro declarar varios recursos en la misma sentencia.
•
No es una solución al “estilo Java”, no al menos en un sentido old fashion.
Me parece más purista si se hubiese convertido la sintaxis bella e incorrecta en simplemente
bella y correcta. Por ejemplo modificado las APIs para este tipo de objetos creando un método
autoclose (), quizás otro close (boolean autoclose), quizás modificar el constructor con un
boolean para indicar si el propio constructor debería asegurarse de liberar los recursos.
Supongo que simplemente no se ha querido tocar algo tan antiguo que funciona 
Descargar