Mejora en el manejo de recursos con Java 7 – ARM

Java 7 añade nuevas características bastante interesantes como son mejoras en la JVM, posibilidad del uso de String en los switch, formatos binarios de números… Entre estas nuevas características se encuentra la sentencia try-with-resources o ARM (Automatic Resource Management). Hasta ahora el manejo de los recursos, como ficheros, sockets, streams o conexiones a base de datos, debía ser realizado por el los programador, con los posibles errores que ésto puede originar. Con esta nueva sentencia, la JVM es la encargada de manejar todos los recursos que sean declarados dentro de la sentencia try-with-resources.

Antes de Java 7

Antes de Java 7 los recursos se solían manejar dentro de un bloque try-catch-finally, de forma que dentro del bloque try se utilizaban los recursos y en el bloque finally se cerraban todos los recursos abiertos. Ésto podía causar problemas de memoria y de rendimiento cuando se olvidaba cerrar estos recursos. Un ejemplo de esta forma de manejar los recursos sería:


[crayon]
public static void main(String[] args) {

BufferedReader buffer = null;
try {
String linea;
buffer = new BufferedReader(new FileReader(“C:test.txt”));
while ((linea = buffer.readLine()) != null) {
System.out.println(linea);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (buffer != null) {
buffer.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
[/crayon]

Uso del ARM

Ahora si se utiliza la sentencia try-with-resources nos quedaría el siguiente método:

[crayon]
public static void main(String[] args) {

try (BufferedReader buffer = new BufferedReader(new FileReader(“C:test.txt”))){
String linea;
while ((linea = buffer.readLine()) != null) {
System.out.println(linea);
}
} catch (IOException e) {
e.printStackTrace();
}
}
[/crayon]

Como se puede observar el código es más sencillo y más fácil de leer, reduciendo bastante el número de líneas y también se elimina el bloque finally para el cierre de recursos.

En este ejemplo, el recurso declarado en la sentencia try-with-resources es un BufferedReader. La declaración aparece entre paréntesis a continuación del try, de esta manera la instancia declarada en la sentencia try-with-resource será cerrada automáticamente cuando el bloque de sentencias del try sean ejecutadas completamente o cuando se produzca una excepción.

de la sentencia se pueden declarar tantos recursos como sea necesario, cerrándose de manera inversa a como han sido declarados:

[crayon]
public void readEmployees(Connection con){
String sql = “Select * from Employee”;

try (Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sql)){
//Código…
} catch (SQLException sqe) {
sqe.printStackTrace();
}
}
[/crayon]

En este caso, primero se cerraría el ResultSet y a continuación el Statement.

¿Qué hay nuevo en el API?

Para permitir el uso del ARM se ha añadido el interfaz java.lang.AutoCloseable en el API, este interfaz contiene únicamente la definición del método close().

[crayon]
public interface AutoClosable {
public void close() throws Exception;
}
[/crayon]

Este interfaz es padre del interfaz java.io.Closeable del que heredan todos los recursos de entrada y salida. El método close() es que se invoca para el cierra todos los recursos incluidos en la cabecera de la sentencia try, llamándose automáticamente una vez finalizado el bloque try o después de producirse una excepción. De manera que todos los recursos declarados en la cabecera del bloque try deberán implementar AutoClosable o Closable. Si se declara algún recurso que no herede de AutoClosable se producirá un error de compilación.

Aunque como se indica en el API, la implementación de este método no es obligatoria, aunque si es aconsejable para un mejor manejo de los recursos. De esta manera, cada una de las clases que hereden de Closeable se encargara de implementar específicamente el método close(). Por ejemplo, la implementación de este método en la clase BufferReader sería:

[crayon]
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
in.close();
in = null;
cb = null;
}
}
[/crayon]

Uso con JDBC

Otro posible uso es en el manejo de base de datos con JDBC. Antes de java 7, todos los recursos debían ser cerrados manualmente una vez finalizado su uso, dando la posibilidad de olvidar cerrar alguno y producir graves errores en la base de datos.

[crayon]
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(“jdbc:h2:mem:test”, “”, “”);
stmt = conn.createStatement();
rs = stmt.executeQuery(“SELECT * FROM INFORMATION_SCHEMA.USERS”);
while (rs.next()) {
System.out.println(rs.getString(1));
}
} catch (SQLException e) {
e.getCause();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.getCause();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.getCause();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.getCause();
}
}
}
[/crayon]

Ahora con la nueva sintaxis, el mismo código quedaría de la siguiente manera:

[crayon]
try(Connection conn = DriverManager.getConnection(“jdbc:h2:mem:test”, “sa”, “”);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(“SELECT * FROM INFORMATION_SCHEMA.USERS”)) {
while (rs.next()) {
System.out.println(rs.getString(1));
}
} catch (SQLException e) {
e.getCause();
}
[/crayon]

Las variables connection, prepared statement y result set son creadas en la sentencia try y después de que se iteren sobre todos los resultados, cuando el bloque termina, la JVM automáticamente cierra todos los recursos.

Manejo de excepciones

El manejo de excepciones es un poco diferente entre try-catch-finally y try-with-resources. Si las excepciones son lanzadas en el bloque try y en el finally, en un try-catch-finally se devuelve la excepción lanzada en el bloque finally mientras que si en una sentencia try-with-resources el metodo devuelve la excepción lanzada por el bloque try. Unos ejemplos podrian ser:

[crayon]
public class ExceptionARM{

public static void main(String[] args) throws Exception {
try {
tryWithResourceException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
try {
normalTryException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

private static void normalTryException() throws Exception {
MyResource mr = null;
try {
mr = new MyResource();
System.out.println(“MyResource created in try block”);
if (true)
throw new Exception(“Exception in try”);
} finally {
if (mr != null)
mr.close();
}

}

private static void tryWithResourceException() throws Exception {
try (MyResource mr = new MyResource()) {
System.out.println(“MyResource created in try-with-resources”);
if (true)
throw new Exception(“Exception in try”);
}
}

static class MyResource implements AutoCloseable {

@Override
public void close() throws Exception {
System.out.println(“Closing MyResource”);
throw new Exception(“Exception in Closing”);
}

}
}
[/crayon]

La salida del programa será:

[crayon]
MyResource created in try-with-resources
Closing MyResource
Exception in try
MyResource created in try block
Closing MyResource
Exception in Closing
[/crayon]

Esto se debe a que la excepción producida en el método close() es ocultada en la primera excepción. A este tipo de excepciones se le llama “suppressed exceptions”. Se pueden recuperar con el método getSuppressed() de la clase Throwable.

Creación de un recuros AutoClosable

Para crear un recurso que pueda ser utilizado en una sentencia try-with-resources, necesitamos implementar el interfaz AutoCloseable. Como se ha dicho anteriormente el interfaz AutoClosable únicamente tiene un método close(). Una simple implementación podría ser:

[crayon]
public class Adult implements AutoCloseable{

public Adult() {
System.out.println(“Me levanto por la mañana”);
}

public void work() {
System.out.println(“Hago mi trabajo”);
}

@Override
public void close() throws Exception {
System.out.println(“Me voy a dormir”);
}
}

public class Car implements AutoCloseable{

public Car() {
System.out.println(“Coche aparcado en el garaje”);
}

public void drive() {
System.out.println(“Voy al trabajo con el coche”);
}

@Override
public void close() throws Exception {
System.out.println(“Aparco el coche en el garaje”);
}
}

public class ExampleARM {

public static void main(String[] args) {
try (Adult adult = new Adult(); Car car = new Car()) {
car.drive();
adult.work();
} catch (Exception e) {
System.out.println(e);
} finally {
System.out.println(“Fin del día.”);
}

}
}
[/crayon]

La salida del ejemplo será:

[crayon]
Me levanto por la mañana
Coche aparcado en el garaje
Voy al trabajo con el coche
Hago mi trabajo
Aparco el coche en el garaje
Me voy a dormir
Fin del día.
[/crayon]

Java Language Specification

Como se dice en Java Language Specification, la estructura:

[crayon]
try (VariableModifiersopt R Identifier = Expression …)
Block
[/crayon]

Es convertido a la siguiente estructura:

[crayon]
{
final VariableModifiers_minus_final R Identifier = Expression;
Throwable #primaryExc = null;

try ResourceSpecification_tail
Block
catch (Throwable #t) {
#primaryExc = #t;
throw #t;
} finally {
if (Identifier != null) {
if (#primaryExc != null) {
try {
Identifier.close();
} catch (Throwable #suppressedExc) {
#primaryExc.addSuppressed(#suppressedExc);
}
} else {
Identifier.close();
}
}
}
}
[/crayon]

Como se puede ver al final tenemos la misma estructura que se ha utilizado siempre en un bloque try-catch-finally, pero con una sintaxis más sencilla y menos propensa a cometer errores en el uso de recursos.

Enlace | The try-with-resources Statement