Desatando el poder de Java 8: La guía definitiva de sus funciones clave
Java 8 introdujo varias características nuevas que ayudaron a hacerlo más versátil y flexible. La característica de Java 8 hace posible la programación funcional . Estas son algunas de las características clave de Java 8:
- Expresiones Lambda
- Interfaces funcionales
- API de transmisión
- Métodos predeterminados
- Referencias de métodos
- Opcional
- API de fecha/hora
1. Expresiones Lambda
Usando expresiones lambda podemos representar un bloque de código que se puede ejecutar más tarde.
Las expresiones lambda se utilizan para definir funciones anónimas , que se pueden pasar como argumentos de método o se pueden devolver desde métodos como cualquier otro objeto.
Sintaxis de la expresión lambda:
(parameters) -> { statements };
Ejemplos:
(i) sin argumentos
()-> System.out.println("lambda expression with no arguments");
// lambda expression with one argument
(a) -> System.out.println(String.format("Value of a is: %s", a);
// lambda expression with multiple arguments
(a, b) -> a + b;
// also argument type can be provided explicitely
(Integer a, Integer b) -> a + b;
(Integer a, Integer b) -> { // parentheses needed for multiple statements
System.out.println(String.format("Returning maximum from %s and %s", a, b));
return (a > b) a : b;
};
// functional interface, we will see below in detail
interface MaxFinder {
int findMax(int a, int b);
}
public class LambdaExpressionExample {
public static void main(String[] args) {
// return maximum of two numbers using lambda expression
MaxFinder maxFinder = (a, b) -> (a > b) ? a : b;
System.out.println(maxFinder.findMax(5, 10));
System.out.println(maxFinder.findMax(15, 5));
}
}
Ahora, veamos cómo se puede usar la expresión lambda con forEach loop:
public class LambdaExpressionExample {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
nums.add(1);
nums.add(2);
nums.add(3);
nums.add(4);
nums.forEach(num -> System.out.println(String.format("Square of %s is %s", num, num * num)));
}
}
2. Interfaces funcionales
Una interfaz que tiene exactamente un método abstracto se denomina interfaz funcional y también se denomina Interfaces de método abstracto único (interfaces SAM) .
Las interfaces funcionales son la base de las expresiones Lambda y permiten escribir código conciso y expresivo al eliminar la necesidad de clases internas anónimas.
Java 8 proporciona varias interfaces funcionales integradas, algunas de ellas son predicado , función , consumidor y proveedor .
Puede consultar el ejemplo simple anterior donde hemos definido una interfaz funcional MaxFinder .
La anotación @FunctionalInterface se usa para representar una interfaz funcional.
(i) Predicado
La interfaz de predicado tiene una sola prueba de método abstracto (T t) que toma un solo argumento y devuelve un valor booleano .
Generalmente se usa para filtrar valores o para probar si un valor cumple una condición.
T : tipo del argumento de entrada del método
Aquí hay un ejemplo usando Predicate :
Predicate<Integer> isEligibleForVoting = (age) -> age >= 18;
boolean eligible = isEligibleForVoting.test(22); // true
La interfaz de función tiene un solo método abstracto apply (T t) que toma un solo argumento y devuelve un objeto .
Generalmente se usa para transformar o mapear valores de un tipo a otro.
Aquí hay un ejemplo usando Function :
Function<Integer, Integer> findSquare = (num) -> (num*num);
int square = findSquare.apply(5); // 25
La interfaz del consumidor tiene un solo método abstracto accept(T t) que toma un solo argumento y no devuelve ningún resultado .
Generalmente se usa para realizar efectos secundarios como imprimir o registrar .
Aquí hay un ejemplo usando Consumer :
Consumer<String> printValue = (x) -> System.out.println(x);
printValue.accept("Java 8 features!"); // prints "Java 8 features!"
La interfaz del proveedor tiene un único método abstracto get() que no acepta ningún argumento y devuelve un resultado .
Generalmente se usa para la inicialización diferida o para generar valores aleatorios.
Aquí hay un ejemplo usando Proveedor:
Supplier<Integer> getRandomInteger = () -> (int) (Math.random() * 500);
int randomInteger = getRandomInteger.get(); // random integer between 0 and 500
public class FunctionalInterfacesExample {
public static void main(String[] args) {
// Using Predicate functional interface
Predicate<Integer> isEligibleForVoting = (age) -> age >= 18;
boolean eligible = isEligibleForVoting.test(22); // true
System.out.println(String.format("Eligible for voting: %s", eligible));
// Using Function functional interface
Function<Integer, Integer> findSquare = (num) -> (num * num);
int square = findSquare.apply(5); // 25
System.out.println(String.format("Square of 5: %s", square));
// Using Consumer functional interface
Consumer<String> printValue = (val) -> System.out.println(val);
printValue.accept("Java 8 features!"); // prints "Java 8 features!"
// Using Supplier functional interface
Supplier<Integer> getRandomInteger = () -> (int) (Math.random() * 500);
int randomInteger = getRandomInteger.get(); // random integer between 0 and 500
System.out.println(String.format("Random number: %s", randomInteger));
}
}
3. API de transmisión
Java 8 Stream API es una poderosa herramienta para procesar colecciones de datos.
El uso de tareas complejas de manipulación de datos de Stream API se puede realizar de una manera más concisa y expresiva que con declaraciones condicionales y bucles tradicionales.
Un flujo es una secuencia ordenada de elementos que se pueden procesar secuencial o paralelamente .
Con la ayuda de interfaces y métodos funcionales, podemos crear , transformar y procesar flujos.
El flujo de una colección se puede crear usando el método stream() .
La API de Streams es una nueva adición a Java Collections Framework que proporciona una forma declarativa de procesar colecciones de objetos. Los flujos le permiten realizar varias operaciones , como filtrar , mapear y reducir colecciones de objetos de una manera más concisa y legible que los bucles for tradicionales.
La Stream API consta de tres tipos de operaciones:
(i) Operaciones Intermedias:
Las operaciones intermedias se utilizan para transformar o filtrar los elementos de un flujo.
Devuelven un nuevo flujo que luego puede ser procesado por operaciones intermedias o terminales .
Las operaciones intermedias más utilizadas son:
- filter() : se utiliza para filtrar valores en función de una condición
- sorted() : usado para ordenar elementos
- distinguido() : usado para encontrar elementos distintos
- map() : se usa para mapear el valor de un tipo a otro
Las operaciones de terminal se utilizan para producir un resultado final a partir de una secuencia.
Se llaman después de todas las operaciones intermedias que se han aplicado a la corriente.
Las operaciones de terminal más utilizadas son:
- collect() : se utiliza para devolver el resultado de las operaciones intermedias
- reduce() : se utiliza para reducir los elementos de una secuencia en un solo elemento.
- forEach() : se usa para iterar a través de cada elemento en la secuencia
- count() : devuelve el recuento de elementos en esta secuencia
Las operaciones de circuito corto se utilizan para producir un resultado temprano, sin procesar todo el flujo.
Las operaciones intermedias y de terminal de flujo de Java 8 pueden provocar un cortocircuito.
Las operaciones de terminal más utilizadas son:
- limit () : se usa para devolver la transmisión tan pronto como alcanza el valor límite dado
- anyMatch() : se utiliza para comprobar si algún elemento de esta secuencia coincide con el predicado proporcionado.
- findFirst() : devuelve un Opcional que describe el primer elemento de la secuencia, o un Opcional vacío si la secuencia está vacía
- findAny() : devuelve un Opcional que describe algún elemento del flujo, o un Opcional vacío si el flujo está vacío.
public class StreamAPIExample {
public static void main(String[] args) {
List<Integer> nums = Arrays.asList(3, 1, 4, 2, 6, 8, 5, 7);
System.out.print("Given numbers list: ");
nums.forEach(num -> System.out.print(String.format("%s, ", num)));
System.out.println();
// sort list of numbers
List<Integer> sortedNums = nums.stream().sorted().collect(Collectors.toList());
System.out.print("Sorted numbers are: ");
sortedNums.forEach(num -> System.out.print(String.format("%s, ", num)));
System.out.println();
// filter all numbers less than 6
List<Integer> filteredNums = nums.stream()
.filter(i -> i < 6)
.collect(Collectors.toList());
System.out.print("Numbers less then 5 are: ");
filteredNums.forEach(num -> System.out.print(String.format("%s, ", num)));
System.out.println();
// odd numbers
long oddNumsCount = nums.stream()
.filter(i -> i % 2 == 1).count();
System.out.println(String.format("Total odd numbers are: %s", oddNumsCount));
// print first 2 even numbers found from list while iterating stream
System.out.print("first 2 even numbers found from list while iterating stream: ");
nums.stream()
.filter(num -> num % 2 == 0)
.limit(2)
.forEach(num -> System.out.print(String.format("%s, ", num)));
System.out.println();
// find squares of numbers
List<Integer> squares = nums.stream()
.map(num -> (num * num))
.collect(Collectors.toList());
System.out.print("Squares of numbers are: ");
squares.forEach(square -> System.out.print(String.format("%s, ", square)));
}
}
Gracias por leer, espero que esta historia le haya ayudado a aprender las características de Java 8.
Sígueme si te gusta el artículo.
Suscríbete para estar atento a los próximos artículos.

![¿Qué es una lista vinculada, de todos modos? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































