A pesar de que java 8 lleva poco mas de dos años en la calle, es increíble el impacto que esta ha tenido en la vida diara de los desarrolladores java. Debo admitir que toma un par de tazas de café y un poco de código para acostumbrarse a los paradigmas que introduce, que a pesar de haber estado presentes en otros lenguajes de programación durante años java se pone al día con estilo.

Así que resumiendo todo en una lista corta, podríamos decir que lo nuevo y relevante de java 8 son los siguientes features:

  • Expresiones Lambda
  • Referencias a métodos
  • Streams
  • Interfaces aumentadas
  • Nuevo API para fechas y tiempo
  • CompletableFuture
  • Optionals

Ciertamente un gran trabajo de lo que los que hacen java posible deben sentirse orgullosos. Vamos a repasar resumidamente con algunos ejemplos de que se trata todo esto y luego haré un post mas especifico para cada tema.

Expresiones Lambda

La maravilla de las expresiones lambda es que soluciona el eterno problema en java de las clases anónimas, ya que si queríamos implementar una clase como ‘Runnable’ que solo implementan un método de una interfaz tendríamos un lió como el siguiente:

Runnable runnable = new Runnable() {
   @Override
   public void run() {
       System.out.println("Hola Mundo");
   }
};
new Thread(runnable).start();

Con esto ademas de que era bastante difícil de asimilar, ademas de que al momento de compilar generaría 2 archivos .class pero ahora con la llegada de las expresiones lambda esto podría resumirse a esto:

new Thread(() -> System.out.println("Hola Mundo")).start();

es como magia! Siendo que la interfaz funcional de runnable  tiene un único método run que no recibe ningún parámetro este se implementa con el arrow operator después de los paréntesis. Podríamos hacer lo mismo con los parámetros requeridos.

Así que podemos concluir que la sintaxis general para expresiones lambda es:

(parameters) -> expression

O bien:

(parameters) -> { statement; ... }

Interfaces Aumentadas

Las interfaces en java 8 pueden declarar métodos con implementación  gracias a dos mejoras:

  1. default methods
  2. static methods

El primero permite definir métodos default en la interfaz, de manera que si una clase implementa una interfaz que tiene métodos default y estos no se sobre-escriben  es como que si la clase estuviera heredando ese metodo (herencia múltiple). Esto es una ventaja ya que realmente no se hereda todo un objeto mas bien solo ciertos comportamientos default del un objeto u objetos en cuestión.

Entonces si queremos definir un método default lo podemos hacer algo asi:

default void sort(Comparator<? super E> c) {
    Collections.sort(this, c);
}

El modificador default puede ser utilizada en interfaces solamente.

La segunda nos deja poner métodos estáticos en ya implementados en las interfaces de forma que podríamos poner métodos de utilidades en las interfaces sin repetir codigo en las clases que lo implementan, siempre con la posibilidad de poner la anotación @Override y re-escribir estos métodos.

Para definir un método estático en una interfaz podemos hacerlo así:

public static <T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

Estas nuevas “libertades” le dan un nuevo propósito a las interfaces, y hasta cierto punto previenen que se tenga codigo repetido a lo largo de varias aplicaciones.

Referencias a métodos

Esto va muy de la mano con lo que antes mencionábamos e introduce un nuevo operador “:: el dos puntos doble permite hacer referencia a un método de una clase que necesitemos y poder pasarlo como parámetro de una función.

Antes de java 8 si quisiéramos ordenar una lista de strings haríamos algo como:

List<String> letras = Arrays.asList("C", "a", "A", "b");
Collections.sort(letras, new Comparator<String>() {
	@Override
	public int compare(String s1, String s2) {
 		return s1.compareToIgnoreCase(s2);
 	}
});

Es clásico de java ser bastante amplio y descriptivo, pero java 8 se mueve lejos de eso y nos permite reescribir el código anterior a algo simple como:

Collections.sort(letras, String::compareToIgnoreCase);

la referencia al método hace que nos ahorremos todo el código de la implementacion del  comparador, y pasemos directo al método clave compareToIngoreCase toma los elementos de la colección y se los pasa como parámetros al método que los ordena.

Este tipo de abstracción hace que lo que esta pasando sea menos obvio, pero mas funcional. Es decir logramos mas con menos código.

Streams

Los streams en java 8 introducen una forma bastante óptima de trabajar con estructuras de datos que antes solía ser muy verbose y además difícil de paralelizar. Tomemos como ejemplo el siguiente codigo que encuentra los id de una colección que contiene un tópico y un monto y los ordena por monto.

Antes de java 8:

List<Articulo> articulosMedicinales = new ArrayList<>();
for(Articulo registro: recibo) {
	if(registro.getTitle().contains("Medicinal")) {
		articulosMedicinales.add(registro);
	}
}

Collections.sort(articulosMedicinales, new Comparator() {
	public int compare(Articulo rec1, Articulo rec2) {
		return rec2.getAmount().compareTo(rec1.getAmount());
	}
});

List<Integer> ids = new ArrayList<>();
for(Articulo registro: articulosMedicinales) {
	ids.add(registro.getId());
}

Utilizando java 8 streams

List<Integer> ids = registro.stream()
	.filter(inv -> inv.getTitle().contains("Medicinal"))
	.sorted(comparingDouble(Invoice::getAmount).reversed())
	.map(Invoice::getId)
	.collect(Collectors.toList());

Aqui basicamente lo que pasó es que la Lista registro, siendo una objeto del tipo List necesariamente implementa la interfaz Collection que a su vez nos brinda toda la bondad de los StreamsSi combinamos esto con un CompletableFuture del cual hablaremos más adelante tendremos una lista que puede ser llenada en el futuro de manera asíncrona optimizando así los tiempos de nuestras aplicaciones.

Nuevo API para fechas y tiempo

La forma en que java 8 maneja las fechas y el tiempo es completamente nuevo y tiene algunas caracteristicas que mejoran sobremanera las antiguas clases de Date y Calendar y fue diseñado sobre dos principales patrones de diseño:

  1. Diseño guiado por el dominio
  2. Inmutabilidad

Basándose en lo anterior introducen clases como Period que sirve para representar un periodo de tiempo como “3 meses y 4 dias”.  ZonedDateTime para representar un timestamp  con zona horaria. Y para hacer el código mas legible podemos usar el LocalDateTime de la siguiente forma para añadir tiempo a un timestamp:

LocatedDateTime vacaciones = LocalDateTime.now()
    .plusHours(24)
    .plusMinutes(30);

para crear un coqueto contador de cuanto hace falta para que te vayas de vacaciones.

Realmente este es uno de los features que me pone mas contento ya que antes de java 8 manejar fechas y horas era un poco doloroso pero la magia ha llegado. Si quieres ver todas las funcionalidades del nuevo API aqui esta la referencia completa

CompletableFuture

El CompletableFuture es un gran paso para java y la forma en como abstraemos la programaciona asíncrona.

Supongamos que tenemos una app que consulta cierta información de 2 servicios diferentes, un servicio responde muy rápido el resultado es impreciso, mientras que el otro se tarda un poco mas pero el resultado siempre es predecible, sea cual sea el caso nos interesa tener el valor. Asi que utilizamos CompletableFuture para hacer un comportamiento “race” entre ambas consultas

CompletableFuture<String> rapido = consultaRapido();
CompletableFuture<String> predecible = consultaPredecible();
rapido.acceptEither(predecible, s -> {
    System.out.println("Resultado: " + s);
});

Lo cual nos devolverá el CompletableFuture que termine primero. Este es solo un ejemplo pero hay una larga lista de comportamientos que podríamos esperar de un CompletableFuture, entre otros:

Y muchos mas.

Optionals

Inspirado en los lenguajes de programación funcional java 8 introduce la clase Optional como una alternativa para evitar los famosos NullPointerException y sirve básicamente para representar cuando un valor está presente o vacío logrando cierta consistencia al verificar, ya que no necesariamente se tiene que comparar un objeto con null.

Veamos un ejemplo:

InputReader file1 = null;
InputReader file2 = new InputReader();

Optional<InputReader> readerOptionalAusente = Optional.ofNullable(file1);
Optional<InputReader> readerOptionalPresente = Optional.ofNullable(file2);

System.out.println("El objeto file1 esta presente: " + readerOptionalAusente.isPresent());
System.out.println("El objeto file2 esta presente: " + readerOptionalPresente.isPresent());

Cuya salida seria:

El objeto file1 esta presente: false
El objeto file2 esta presente: true

la clave aquí está en revisar el método isPresent() para evitar los null checks, particularmente útil cuando queremos acceder a una propiedad o campo de un objeto que es null, lo cual provocaría un inevitable desastre.

Conclusion

En conclusión los features de java 8 son muy extensos para estudiarlos a detalle en un solo post, pero revisar cada uno de manera breve nos permite darnos cuenta de la evolución funcional que sufrió el lenguaje en este nuevo release logrando una experiencia mas placentera al programar con este lenguaje. The future is now!

 

Categorized in: