Características de Java 8 con ejemplos

Java 8 fue lanzado el 18 de marzo de 2014. Eso fue hace mucho tiempo, pero aún muchos proyectos se están ejecutando en Java 8. Esto se debe a que fue un lanzamiento importante con muchas características nuevas. Veamos todas las características emocionantes y principales de Java 8 con ejemplos de código.

Visión general rápida de las características de Java 8

Algunas de las características importantes de Java 8 son;

  1. El método forEach() en la interfaz Iterable
  2. Métodos default y estáticos en Interfaces
  3. Interfaces Funcionales y Expresiones Lambda
  4. API de Java Stream para Operaciones de Datos en Colecciones
  5. API de Tiempo de Java
  6. Mejoras en la API de Colecciones
  7. Mejoras en la API de Concurrencia
  8. Mejoras en IO de Java

Echemos un vistazo breve a estas características de Java 8. Proporcionaré algunos fragmentos de código para comprender mejor las características de una manera sencilla.

1. Método forEach() en la interfaz Iterable

Siempre que necesitemos recorrer una colección, necesitamos crear un Iterador cuyo único propósito sea iterar, y luego tenemos lógica de negocio en un bucle para cada uno de los elementos de la colección. Podríamos obtener una ConcurrentModificationException si el iterador no se utiliza correctamente.

Java 8 ha introducido el método forEach en la interfaz java.lang.Iterable para que al escribir código nos centremos en la lógica de negocio. El método forEach toma un objeto java.util.function.Consumer como argumento, por lo que ayuda a tener nuestra lógica de negocio en un lugar separado que podemos reutilizar. Veamos el uso de forEach con un ejemplo simple.

package com.journaldev.java8.foreach;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.lang.Integer;

public class Java8ForEachExample {

	public static void main(String[] args) {
		
		//creando una colección de muestra
		List<Integer> myList = new ArrayList<Integer>();
		for(int i=0; i<10; i++) myList.add(i);
		
		//recorriendo usando un Iterador
		Iterator<Integer> it = myList.iterator();
		while(it.hasNext()){
			Integer i = it.next();
			System.out.println("Iterator Value::"+i);
		}
		
		//recorriendo mediante el método forEach de Iterable con una clase anónima
		myList.forEach(new Consumer<Integer>() {

			public void accept(Integer t) {
				System.out.println("forEach anonymous class Value::"+t);
			}

		});
		
		//recorriendo con la implementación de la interfaz Consumer
		MyConsumer action = new MyConsumer();
		myList.forEach(action);
		
	}

}

//Implementación de Consumer que se puede reutilizar
class MyConsumer implements Consumer<Integer>{

	public void accept(Integer t) {
		System.out.println("Consumer impl Value::"+t);
	}
}

El número de líneas puede aumentar, pero el método forEach ayuda a tener la lógica de iteración y la lógica de negocio en lugares separados, lo que resulta en una mayor separación de preocupaciones y un código más limpio.

2. Métodos por defecto y estáticos en Interfaces

Si lees cuidadosamente los detalles del método forEach, notarás que está definido en la interfaz Iterable, pero sabemos que las interfaces no pueden tener un cuerpo de método. A partir de Java 8, las interfaces se mejoraron para tener un método con implementación. Podemos usar la palabra clave default y static para crear interfaces con implementación de método. La implementación del método forEach en la interfaz Iterable es:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

Sabemos que Java no proporciona herencia múltiple en Clases porque conduce al Problema del Diamante. Entonces, ¿cómo se manejará con interfaces ahora ya que las interfaces son similares a las clases abstractas?

La solución es que el compilador lanzará una excepción en este escenario y tendremos que proporcionar la lógica de implementación en la clase que implementa las interfaces.

package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface1 {

	void method1(String str);
	
	default void log(String str){
		System.out.println("I1 logging::"+str);
	}
	
	static void print(String str){
		System.out.println("Printing "+str);
	}
	
	// Intentar anular el método de Object da un error en tiempo de compilación como
	// "Un método predeterminado no puede anular un método de java.lang.Object"
	
//	default String toString(){
//		return "i1";
//	}
	
}
package com.journaldev.java8.defaultmethod;

@FunctionalInterface
public interface Interface2 {

	void method2();
	
	default void log(String str){
		System.out.println("I2 logging::"+str);
	}

}

Ten en cuenta que ambas interfaces tienen un método común llamado log() con una lógica de implementación.

package com.journaldev.java8.defaultmethod;

public class MyClass implements Interface1, Interface2 {

	@Override
	public void method2() {
	}

	@Override
	public void method1(String str) {
	}

	//MiClase no se compilará sin tener su propia implementación de log()
	@Override
	public void log(String str){
		System.out.println("MyClass logging::"+str);
		Interface1.print("abc");
	}
	
}

Como puedes ver, Interface1 tiene una implementación de método estático que se utiliza en la implementación del método MyClass.log(). Java 8 utiliza ampliamente métodos default y static en la API de colecciones y se agregan métodos default para que nuestro código siga siendo compatible hacia atrás.

Si alguna clase en la jerarquía tiene un método con la misma firma, entonces los métodos default se vuelven irrelevantes. Object es la clase base, así que si tenemos métodos default equals(), hashCode() en la interfaz, se volverán irrelevantes. Por eso, para una mejor claridad, no se permite que las interfaces tengan métodos default de Object.

Para obtener detalles completos sobre los cambios en las interfaces en Java 8, por favor lee Java 8 cambios en las interfaces.

3. Interfaces Funcionales y Expresiones Lambda

Si notas el código de interfaz anterior, verás la anotación @FunctionalInterface. Las interfaces funcionales son un nuevo concepto introducido en Java 8. Una interfaz con exactamente un método abstracto se convierte en una Interfaz Funcional. No necesitamos usar la anotación @FunctionalInterface para marcar una interfaz como una Interfaz Funcional.

La anotación @FunctionalInterface es una facilidad para evitar la adición accidental de métodos abstractos en las interfaces funcionales. Puedes pensar en ella como la anotación @Override y es una buena práctica usarla. java.lang.Runnable con un único método abstracto run() es un gran ejemplo de una interfaz funcional.

Uno de los principales beneficios de la interfaz funcional es la posibilidad de usar expresiones lambda para instanciarlas. Podemos instanciar una interfaz con una clase anónima pero el código se ve voluminoso.

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println("My Runnable");
			}};

Dado que las interfaces funcionales tienen solo un método, las expresiones lambda pueden proporcionar fácilmente la implementación del método. Solo necesitamos proporcionar los argumentos del método y la lógica del negocio. Por ejemplo, podemos escribir la implementación anterior usando una expresión lambda como:

Runnable r1 = () -> {
			System.out.println("My Runnable");
		};

Si tienes una sola declaración en la implementación del método, tampoco necesitamos llaves. Por ejemplo, la clase anónima Interface1 de arriba puede instanciarse usando lambda de la siguiente manera:

Interface1 i1 = (s) -> System.out.println(s);
		
i1.method1("abc");

Entonces, las expresiones lambda son un medio para crear fácilmente clases anónimas de interfaces funcionales. No hay beneficios en tiempo de ejecución al usar expresiones lambda, así que las usaré con precaución porque no me importa escribir unas cuantas líneas extra de código.

A new package java.util.function has been added with bunch of functional interfaces to provide target types for lambda expressions and method references. Lambda expressions are a huge topic, I will write a separate article on that in the future.

Puedes leer el tutorial completo en Tutorial de Expresiones Lambda en Java 8.

4. API de Stream de Java para Operaciones Masivas en Colecciones

A new java.util.stream has been added in Java 8 to perform filter/map/reduce like operations with the collection. Stream API will allow sequential as well as parallel execution. This is one of the best features for me because I work a lot with Collections and usually with Big Data, we need to filter out them based on some conditions.

La interfaz Collection se ha extendido con los métodos predeterminados stream() y parallelStream() para obtener el Stream para ejecución secuencial y paralela. Veamos su uso con un ejemplo simple.

package com.journaldev.java8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamExample {

	public static void main(String[] args) {
		
		List<Integer> myList = new ArrayList<>();
		for(int i=0; i<100; i++) myList.add(i);
		
		// flujo secuencial
		Stream<Integer> sequentialStream = myList.stream();
		
		// flujo paralelo
		Stream<Integer> parallelStream = myList.parallelStream();
		
		// usando lambda con la API de Stream, ejemplo de filtro
		Stream<Integer> highNums = parallelStream.filter(p -> p > 90);
		// usando lambda en forEach
		highNums.forEach(p -> System.out.println("High Nums parallel="+p));
		
		Stream<Integer> highNumsSeq = sequentialStream.filter(p -> p > 90);
		highNumsSeq.forEach(p -> System.out.println("High Nums sequential="+p));

	}

}

Si ejecutas el código de ejemplo anterior, obtendrás una salida como esta:

High Nums parallel=91
High Nums parallel=96
High Nums parallel=93
High Nums parallel=98
High Nums parallel=94
High Nums parallel=95
High Nums parallel=97
High Nums parallel=92
High Nums parallel=99
High Nums sequential=91
High Nums sequential=92
High Nums sequential=93
High Nums sequential=94
High Nums sequential=95
High Nums sequential=96
High Nums sequential=97
High Nums sequential=98
High Nums sequential=99

Observa que los valores del procesamiento paralelo no están en orden, por lo que el procesamiento paralelo será muy útil al trabajar con colecciones enormes.

No es posible cubrir todo sobre Stream API en este post, puedes leer todo sobre Stream API en Java 8 Stream API Example Tutorial.

5. API de Tiempo de Java

Siempre ha sido difícil trabajar con Fechas, Tiempos y Zonas Horarias en java. No había un enfoque estándar o API en java para la fecha y la hora en Java. Una de las adiciones agradables en Java 8 es el paquete java.time que agilizará el proceso de trabajar con el tiempo en java.

Solo con mirar los paquetes de la API de Tiempo de Java, puedo sentir que serán muy fáciles de usar. Tiene algunos subpaquetes java.time.format que proporciona clases para imprimir y analizar fechas y horas y java.time.zone proporciona soporte para zonas horarias y sus reglas.

La nueva API de Tiempo prefiere enums sobre constantes enteras para meses y días de la semana. Una de las clases útiles es DateTimeFormatter para convertir objetos DateTime a cadenas. Para un tutorial completo, dirígete a Java Date Time API Example Tutorial.

6. Mejoras en la API de Colecciones

Ya hemos visto el método forEach() y la API Stream para colecciones. Algunos nuevos métodos agregados en la API de colecciones son:

  • Iterator método predeterminado forEachRemaining(Consumer action) para realizar la acción dada para cada elemento restante hasta que todos los elementos hayan sido procesados o la acción lance una excepción.
  • Collection método predeterminado removeIf(Predicate filter) para eliminar todos los elementos de esta colección que satisfacen el predicado dado.
  • Collection método spliterator() que devuelve una instancia de Spliterator que se puede usar para recorrer los elementos secuencialmente o en paralelo.
  • Map métodos replaceAll(), compute(), merge().
  • Mejora del rendimiento para la clase HashMap con colisiones de claves

7. Mejoras en la API de concurrencia

Algunas mejoras importantes en la API de concurrencia son:

  • ConcurrentHashMap métodos compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() y search().
  • CompletableFuture que puede completarse explícitamente (estableciendo su valor y estado).
  • Executors método newWorkStealingPool() para crear un conjunto de hilos de robo de trabajo utilizando todos los procesadores disponibles como su nivel de paralelismo objetivo.

8. Mejoras en Java IO

Algunas mejoras en IO que conozco son:

  • Files.list(Path dir) que devuelve un Stream poblado de forma perezosa, cuyos elementos son las entradas en el directorio.
  • Files.lines(Path path) que lee todas las líneas de un archivo como un Stream.
  • Files.find() que devuelve un Stream poblado de forma perezosa con Path al buscar archivos en un árbol de archivos que tiene como raíz un archivo de inicio dado.
  • BufferedReader.lines() que devuelve un Stream, cuyos elementos son líneas leídas de este BufferedReader.

Mejoras diversas en la API principal de Java 8

Algunas mejoras diversas en la API que podrían ser útiles son:

  1. El método estático ThreadLocal conInitial(Supplier supplier) para crear instancias fácilmente.
  2. La interfaz Comparator ha sido extendida con muchos métodos por defecto y estáticos para orden natural, orden inverso, etc.
  3. Métodos min(), max() y sum() en las clases envoltorio Integer, Long y Double.
  4. Métodos logicalAnd(), logicalOr() y logicalXor() en la clase Boolean.
  5. Método stream() de ZipFile para obtener un Stream ordenado sobre las entradas del archivo ZIP. Las entradas aparecen en el Stream en el orden en que aparecen en el directorio central del archivo ZIP.
  6. Varios métodos de utilidad en la clase Math.
  7. Se ha añadido el comando jjs para invocar el Motor Nashorn.
  8. Se ha añadido el comando jdeps para analizar archivos de clase
  9. El puente JDBC-ODBC ha sido eliminado.
  10. El espacio de memoria PermGen ha sido eliminado

Eso es todo para las características de Java 8 con ejemplos de programas. Si he pasado por alto algunas características importantes de Java 8, por favor avíseme a través de comentarios.

Source:
https://www.digitalocean.com/community/tutorials/java-8-features-with-examples