Duke Treasure Box

En marzo del 2014, se lanzó Java 8 después de 3 años de espera con características interesantes. Aunque algunas un poco tarde, Java demostró una vez que puede adecuarse a los cambios. Las características más resaltantes a continuación:

Lambdas

Aquellos que han trabajado con Swing recordarán mucho la interfaz ActionListener, en ese entonces era esa la forma de enviar code as data. En pocas palabras, se puede decir que Lambdas es la nueva forma de trabajar con las ya conocidas Anonymous classes, con la particularidad que se puede usar con aquellas interfaces que solo tienen un método abstracto, también conocidas como Functional Interfaces.

En Java < 8:

JButton saveButton = new JButton();
ActionListener saveAction = new ActionListener() {
  @Override
  public void actionPerformed(ActionEvent e) {
    System.out.println("Save!!!");
  }
};
saveButton.addActionListener(saveAction);

En Java >= 8:

JButton saveButton = new JButton();
saveButton.addActionListener(() -> System.out.println("Save!!!"));

Si se quiere escribir más de una línea de código dentro del lambda, esta la es forma:

JButton saveButton = new JButton();
saveButton.addActionListener(() -> {
  System.out.println("Dentro de un bloque.");
  System.out.println("Save!!!");
});

Functional Interfaces

Como ya se mencionó, son aquellas interfaces que cuentan con un método abstracto. Estas interfaces se encuentran en el paquete java.util.function. Las principales son:

  • Consumer

  • Function

  • Predicate

  • Supplier

Interfaces cómo FileFilter y Runnable, por mencionar algunas, también son consideradas Functional Interfaces.

NOTA: Tener en cuenta que en Java 8 se agregó Default Methods.

Default Methods

Hasta Java 7, una interfaz no puede contener métodos implementados, solo la definición del método. Ahora, imaginemos que se quiere agregar una nuevo método a una interfaz, pero esta está siendo utilizada por varias clases que la implementan. No podriamos hacer el cambio sin tener un impacto en estas clases. Default Methods ayuda a lidiar con este tipo de problemas, definiendo un comportamiento por defecto, este default method no tiene que ser implementado en todas las implementaciones pero si podrias ser sobreescrito.

Un ejemplo de una interfaz con 1 método abstracto es la clase Runnable:

public interface Runnable {

  public abstract void run();

}

A continuación, la definición de la clase CustomRunnable y su implementación CustomRunnableImpl:

public interface CustomRunnable {

  public abstract void run();

  default String name() {
    return "custom-runnable";
  }

}
public class CustomRunnableImpl implements CustomRunnable {

  @Override
  public void run() {
    System.out.println("Ejecutando " + name());
  }

}

En el ejemplo previo, se puede apreciar que no es necesario implementar el método name definido en la interfaz CustomRunnable porque ya tiene una implementación por defecto.

Method References

En algunas ocasiones, como hemos visto en ejemplos previos, un lambda no hace mas que llamar a un método. En estos casos, hacer uso de method references es la mejor opción. De esta manera, el código se hace compacto y de fácil entendimiento.

El ejemplo a continuación hace uso de lambdas:

String[] languages = {"Ceylon", "Groovy", "Kotlin", "Scala"};
Arrays.stream(languages).forEach((language) -> System.out.println(language));

El mismo ejemplo ahora ha sido reemplazado usando method references:

String[] languages = {"Ceylon", "Groovy", "Kotlin", "Scala"};
Arrays.stream(languages).forEach(System.out::println);

Streams

Permiten la manipulacion de las colecciones y puede procesar datos de manera paralela de una manera muy sencilla.

List<String> languages = Arrays.asList("Ceylon", "Groovy", "Kotlin", "Scala");
for (String language : languages) {
  if (language.contains("o")) {
    System.out.println(language);
  }
}

El mismo código, es ahora procesado usando Streams y el código es legible:

List<String> languages = Arrays.asList("Ceylon", "Groovy", "Kotlin", "Scala");

languages.stream().filter(lang -> lang.contains("o"))
                  .forEach(System.out::println);

Si queremos procesar los datos en paralelo solo debemos cambiar el método stream() por parallelStream().

Optional

NullPointerException ha sido un problema por el cual todos los desarrolladores han pasado. Esto basicamente ocurre por la ausencia de un valor. En Java 8, Optional llegó al rescate y nos ayuda a lidiar con las referencias nulas. Una vez más, el código se vuelve legigle con el uso de esta clase.

El siguiente código, debe lucir familiar para muchos:

if (persona == null ) {
  persona = new Persona("Duke");
}

ó usando el operador ternario:

Persona persona = persona != null ? persona : new Persona("Duke");

En Java 8, el código anterior ha sido reducido con el uso de Optional:

Optional<Persona> personaOpt = findById(1);
Persona persona = personaOpt.orElse(new Persona("Duke"));

StringJoiner

En Java 8, se agregaron clases de apoyo como por ejemplo StringJoiner, la cual permite construir una secuencia de caracteres separados por un delimitador, a su vez también se puede añadir el prefijo y sufijo.

El siguiente código se agrega un delimitador , para divir los elementos:

String delimiter = ",";
StringJoiner languages = new StringJoiner(delimiter);
languages.add("Ceylon").add("Groovy").add("Kotlin").add("Scala");
System.out.println(languages.toString());

El resultado es el siguiente:

Ceylon,Groovy,Kotlin,Scala

El siguiente código se agrega un delimitador ,, prefijo >> y sufijo <<:

String delimiter = ",";
String prefix = ">>";
String suffix = "<<";
StringJoiner languages = new StringJoiner(delimiter, prefix, suffix);
languages.add("Ceylon").add("Groovy").add("Kotlin").add("Scala");
System.out.println(languages.toString());

El resultado es el siguiente:

>>Ceylon,Groovy,Kotlin,Scala<<

Date API

Los días complicados de trabajar con fechas en Java han pasado. Ahora, Java 8 cuenta con un API renovada basada en el conocido proyecto Joda-Time.

Crear fecha y hora personalizadas de la siguiente manera:

LocalDate customDate = LocalDate.of(1989, 11, 11); // year month day
LocalTime customTime = LocalTime.of(5, 30, 45); // hour minutes seconds
LocalDateTime customDateTime = LocalDateTime.of(1989,11, 11, 5, 30,45); // year month day hour minutes seconds

Obtener la fecha y hora actual:

LocalDate currentDate = LocalDate.now();
LocalTime currentTime = LocalTime.now();

También se puede realizar operaciones como la suma o resta de días, semanas, meses, años, horas, minutos, etc:

LocalDateTime currentDateTime = LocalDateTime.now().plus(1, ChronoUnit.DAYS);
LocalDate currentLocalDate = currentDateTime.toLocalDate();
LocalTime currentLocalTime = currentDateTime.toLocalTime();

CompletableFuture

En Java 5, la interfaz Future fue introducida. En Java 8, CompletableFuture fue agregado como implementación de Future y CompletionStage, este último también recién agregado. Este nos permite crear code non-blocking cuando se consumen APIs síncronas y crear composiciones entre varios CompletableFuture.

En el siguiente código, se muestra que se tiene que esperar 5 segundos para obtener el valor "world":

CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
  try {
    Thread.sleep(5000L);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  return "World";
});

System.out.println("World is done? " + world.isDone());
while(!world.isDone()) {
  Thread.sleep(1000L);
  System.out.println("wait...");
}
System.out.println("World is done? " + world.isDone());
System.out.println("Result " + hello.get() + " " + world.get());

JDeps

Como ya se sabe Project Jigsaw forma parte de Java 9 y JDeps fue introducida en Java 8 como herramienta de ayuda para los desarrolladores, y preparar la transición a sistemas modulares.

El propósito de esta herramienta es analizar aplicaciones y librerías e identificar el uso de APIs internas.

A continuación, el análisis de la librería spring-core-4.3.0.RELEASE.jar.

jdeps -P -jdkinternals spring-core-4.3.0.RELEASE.jar

El resultado será el siguiente:

spring-core-4.3.0.RELEASE.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/lib/rt.jar (Full JRE)
   org.springframework.objenesis.instantiator.sun.UnsafeFactoryInstantiator (spring-core-4.3.0.RELEASE.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)

Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependency on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

Gracias a Takipi por dejarme usar sus imágenes.


Eddú Meléndez

Java Software Engineer, Open Source Contributor