Las sirenas poseen un arma más poderosa que su canto: su silencio
Franz Kafka

Java7: Mejora en el manejo de Recursos

NOTA: Debido a problemas con el editor del blog, los estilos no se muestran correctamente. Intentaré arreglarlo y modificar el post en el futuro, pero puedes descargar el archivo PDF (Java7, Mejora en el manejo de recursos) donde sí se muestran los estilos.

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:

  • public 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.

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 J

Java7: Nuevo soporte del tipo String en sentencias switch

NOTA: Debido a problemas con el editor del blog, los estilos no se muestran correctamente. Intentaré arreglarlo y modificar el post en el futuro, pero puedes descargar el archivo PDF (Java7, Nuevo soporte del tipo String en sentencias switch) donde sí se muestran los estilos.

En la versión 7 de Java, el lenguaje ha sido modificado para poder soportar una variable String en una sentencia switch. Se trata de un cambio meramente estético ya que no supone más que sustituir un bloque if - else anidados por una sentencia switch.

Código antiguo (aún soportado en Java7, por supuesto):

String mes  = null;

if (mes == null) {

System.out.println(“Mes nulo.”);

} else if (mes.equals(“Enero”)||mes.equals(“Febrero”)||mes.equals(“Marzo”)){

System.out.println(“1er trimerstre”);

} else if (mes.equals(“Abril”)||mes.equals(“Mayo”)||mes.equals(“Junio”)) {

System.out.println(“2nd trimestre”);

} else if (mes.equals(“Julio”)|| mes.equals(“Agosto”)|| mes.equals(“Septiembre”)) {

System.out.println(“3rd trimestre”);

} else if (mes.equals(“Octubre”)||mes.equals(“Noviembre”)||mes.equals(“Diciembre”)) {

System.out.println(“4th trimestre”);

} else {

System.out.println(“Mes no válido.”);

}

Código nuevo Java7

String mes      = null;

if (mes == null) {

System.out.println(“Mes nulo.”);

} else {

switch (mes) {

case “Enero”:

case “Febrero”:

case “Marzo”: System.out.println(“1er trimerstre “); break;

case “Abril”:

case “Mayo”:

case “Junio”: System.out.println(“2nd trimestre”); break;

case “Julio”:

case “Agosto”:

case “Septiembre”: System.out.println(“3rd trimestre”); break;

case “Octubre”:

case “Noviembre”:

case “Diciembre”: System.out.println(“4th trimestre”); break;

default: System.out.println(“Mes no válido.”);break;

}

}

Desde luego hay un problema: el tipo String no es un tipo primitivo, por lo cual puede tomar el valor de null. Si se intenta aplicar una sentencia switch sobre una variable cuyo valor es null, el resultado será que la sentencia lanzará una excepción NullPointerException en tiempo de ejecución.

NOTAR que en la nueva sentencia switch para comparar se utiliza el método equals() y no el método equalsIgnoreCase().

Ventajas:

  • El código será ligeramente más claro.
  • He leído que genera un código binario más eficiente y por tanto más rápido, pero no he encontrado referencias a pruebas de estrés que sostengan esta afirmación.

Inconvenientes:

  • No se soporta un “case null”, que en mi opinión además debería ser obligatorio declararlo cuando la variable no sea de un tipo primitivo.
  • No he encontrado pruebas de rendimiento para comprobar si es más eficiente que bloques if - else anidados, o si es más eficiente refactorizar para utilizar un enumeration. Esta funcionalidad se solicitó hace 16 años, siendo probablemente por rendimiento (para objetos nullables) por lo que no se implementó antes.
  • Se perderá potencia si previamente debemos comprobar mediante una sentencia if si la variable es nula.
  • Si no se comprueba y se encapsula en un bloque trycatch se consume más recursos que un if.
  • Si se utiliza la sentencia assert estaremos empleando dos prácticas desaconsejadas en mi opinión: rompemos el flujo esperado del programa y utilizamos una excepción para controlar el flujo.
  • Ya que se ha abierto la veda para utilizar la sentencia switch con variables que no son de tipo primitivo, por qué no utilizar esta sentencia para cualquier tipo no primitivo? Con sobreescribir el método equals de nuestros objetos Y gestionar el case null en nuestra sentencia podríamos clarificar bastante más nuestro código.

Mi conclusión:

Pobre, pero al menos mejora la legibilidad aparentemente sin contrapartidas.

No se han atrevido a dar un paso completo y bien gestionado. Fuente más que probable de futuros bugs en nuestros programas, e injustificadamente menos potente de lo que debería y podría ser.

Probablemente el compilador simplemente traduzca esta nueva sentencia a bloques if - else anidados.

Java7: Cambios en la sintaxis del lenguaje

NOTA: Debido a problemas con el editor del blog, los estilos no se muestran correctamente. Intentaré arreglarlo y modificar el post en el futuro, pero puedes descargar el archivo PDF (Java7, cambios en la sintaxis del lenguaje) donde sí se muestran los estilos.

Los dos mayores cambios en la sintaxis de Java son tratados a parte en sendos artículos:

El resto de cambios menores son:

Números en formato binario. Hasta ahora podíamos introducir valores en formato decimal y en formato hexadecimal.

//An 8-bit ‘byte’ value:

byte aByte = (byte)0b00100001;

// A 16-bit ‘short’ value:

short aShort = (short)0b1010000101000101;

// Some 32-bit ‘int’ values:

int anInt1 = 0b10100001010001011010000101000101;

int anInt2 = 0b101;

int anInt3 = 0B101; // The B can be upper or lower case.

// A 64-bit ‘long’ value. Note the “L” suffix:

long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;

Captura de múltiples excepciones. Hasta ahora cada bloque capturaba una excepción, por lo que si queremos implementar el mismo código para tratar dos excepciones debíamos repetir código:

catch (IOException ex) {

logger.debug(ex);

throw ex;

catch (SQLException ex) {

logger.debug(ex);

throw ex;

}

Ahora podemos declarar más de una excepción en cada bloque, por lo que mejoramos la legibilidad, reducimos líneas de código y sobre todo eliminamos duplicidades.

catch (IOException|SQLException ex) {

logger.debug(ex);

throw ex;

}

En este caso la variable ex es implícitamente final, es decir no podemos modificarla.

Relanzamiento de excepciones mejorado. Anteriormente si queríamos tratar dos excepciones en el mismo bloque capturando por herencia, posteriormente no podíamos declarar en la sentencia throws el tipo de las excepciones capturadas, sólo especificar el padre (que es la clase utilizada en el bloque catch).

static class FirstException extends Exception { }

static class SecondException extends Exception { }

public void rethrowException(String exceptionName) throws Exception {

try {

if (exceptionName.equals(“First”)) {

throw new FirstException();

} else {

throw new SecondException();

}

} catch (Exception e) {

throw e;

}

}

Ahora es posible especificar cada tipo en la declaración, a pesar de no ser utilizadas en el bloque catch :

public void rethrowException(String exceptionName) throws FirstException, SecondException {

try {

// …

}

catch (Exception e) {

throw e;

}

}

Carácter subrayado en literales numéricos. Para facilitar la legibilidad del código, a partir de esta versión se permite utilizar el carácter subrayado (“_”) para separar grupos de cifras en literales numéricos.

long creditCardNumber = 1234_5678_9012_3456L;

long hexBytes = 0xFF_EC_DE_5E;

long maxLong = 0x7fff_ffff_ffff_ffffL;

long bytes = 0b11010010_01101001_10010100_10010010;

Más en detalle:

float pi1 = 3_.1415F;      // Invalid; cannot put underscores adjacent to a decimal point

float pi2 = 3._1415F;      // Invalid; cannot put underscores adjacent to a decimal point

long socialSecurityNumber1

= 999_99_9999_L;         // Invalid; cannot put underscores prior to an L suffix

int x1 = _52;              // This is an identifier, not a numeric literal

int x2 = 5_2;              // OK (decimal literal)

int x3 = 52_;              // Invalid; cannot put underscores at the end of a literal

int x4 = 5_______2;        // OK (decimal literal)

int x5 = 0_x52;            // Invalid; cannot put underscores in the 0x radix prefix

int x6 = 0x_52;            // Invalid; cannot put underscores at the beginning of a number

int x7 = 0x5_2;            // OK (hexadecimal literal)

int x8 = 0x52_;            // Invalid; cannot put underscores at the end of a number

int x9 = 0_52;             // OK (octal literal)

int x10 = 05_2;            // OK (octal literal)

int x11 = 052_;           // Invalid; cannot put underscores at the end of a number

Tipo de instanciación inferida. Anteriormente debemos explicitar en el constructor el tipo de instanciación que deseamos:

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

En la nueva versión Java7 no es necesario si el compilador puede inferir el tipo. Sería suficiente con el operador <>

Map<String, List<String>> myMap = new HashMap<>();

En caso de no utilizar el operador <>, se generaría un aviso de conversión no chequeada

Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning

Java7 Liberado

Java7Para ser exactos Java7 se liberó ya hace unos días. A  principios de Julio ya se publicó una versión candidata (Release Candidate) y finalmente se ha liberado la versión definitiva a finales del mismo mes (Major Release).

Pero de eso ya os habréis enterado por otros medios y yo necesitaba un tiempo para leer y cacharrear antes de informar y opinar :)

El caso es que ya está entre nosotros la nueva versión de Java, la primera gran entrega de Oracle, que compró Sun. “Sólo” han pasado unos 5 años desde la última liberación importante, un tiempo enorme en términos de nuestra industria en los que ha habido significativos cambios tecnológicos  (HTML5, Chrome, Android, iPad..) .

Los cambios más relevantes en esta entrega han sido

  • La JVM ha sido extendida para mejorar la gestión automática de la memoria, mejorando entre otros puntos el colector de basura y ofrecer soporte a otros lenguajes.
  • Extensiones varias en la API, que afectan entre otros a Swing, IO, Seguridad, XML, Internacionalización.
  • Modificaciones de la sintaxis de Java. Publicaré otras dos entradas en la que se mostrarán en más profundidad.

A pesar de los 5 años transcurridos, se han debido aparcar para próximas versiones varias funcionalidades que se esperaban para esta versión.

Aunque la calidad de las liberaciones han mejorado con Oracle (por ejemplo son más estables las nuevas versiones de MySQL), ya se han reportado un bug de considerable impacto : una optimización en Java6 con una deficiente gestión de recursos  por defecto no activada pasa a estar por defecto activada en Java7, por lo cual al Apache Software Foundation recomienda no utilizar Java7 en un entorno de producción con sus productos.

Oracle ha admitido que tuvo conocimiento de dicho bug unos días antes de la publicación, y sin embargo no hizo nada! Oracle debió retrasar la publicación? Creo que no, nos da tiempo a leer y cacharrear. Seamos prácticos, el asunto del bug se está sobrevalorando.

Pero debió comunicar mejor el bug? Por supuesto.

Este no es un caso aislado sino una muestra más de una comunicación deficiente sobre Java por parte de Oracle. También he percibido mucha confusión respecto a la información de esta liberación desde Oracle: informaciones duplicadas, otras antiguas, es más fácil encontrar documentación buscando en Google que en el propio site de Oracle.

Supongo que en parte se deberá a que no es para estar orgulloso ni por parte de Sun ni por parte de Oracle, y que mejorará con el tiempo cuando tengan claro dónde quieren llevar a Java.

Mi Conclusión

Mi opinión de esta Release es poca sustancia con maquillaje pero con suficientes lagunas para no ser utilizada, aún. Así mismo Oracle debe mejorar su deficiente comunicación. Aunque la siguiente entrega queda lejos, esperemos que sea una versión v02.00 en lugar de la v01.08.

Y vosotros qué pensáis de esta publicación? Suficiente? Extraordinaria? Decepcionante?

Más información del Sitio Oficial Java7

PD

Como con todas las publicaciones suficientemente significativas de cualquier producto, aún debemos dejar pasar un tiempo prudencial antes de utilizarlo en un entorno de producción para estudiarlo y esperar a que se estabilice lo suficiente.

PPDD

Esperemos que la modularización de la JDK aparcada para la próxima entrega traiga consigo una refactorización significativa y nos encontremos al fin con la tan (al menos por mí) esperada versión 2.0 de Java en lugar de con la versión 1.8 o un nuevo aplazamiento, aunque sea a coste de incompatibilidades con versiones anteriores: Java se está haciendo una bola de nieve gigantesca en crecimiento parcheado eterno y continuo.

Sobre el eterno debate sobre composición, y sobre si la POO debería ser enterrada dignamente por los servicios prestados, da material para otro post.

Comenta