English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Análisis profundo y simple de JDK8Nuevas características: expresiones Lambda

La primera vez que conocí la expresión Lambda fue en TypeScript (el superconjunto de JavaScript), en ese momento fue para que el método this de TypeScript se use fuera en lugar de dentro del método. Después de usarlo, de repente pensé que Lambda no es JDK8¿es una nueva característica de peso pesado? Entonces siento que busco información relevante y la registro:

I. Parámetro de comportamiento

El parámetro de comportamiento, en términos simples, significa que el cuerpo de la función solo contiene código genérico de clase de plantilla, y algunos lógicos que cambian con el escenario de negocio se pasan a la función en forma de parámetros. La adopción del parámetro de comportamiento hace que el programa sea más genérico, para hacer frente a las necesidades de cambio frecuentes.

Consideremos un escenario de negocio, supongamos que necesitamos filtrar manzanas a través de un programa, primero definamos una entidad de manzana:

public class Apple {
/** número de identificación */
private long id;
/** color */
private Color color;
/** Peso */
private float weight;
/** Origen */
private String origin;
public Apple() {
}
public Apple(long id, Color color, float weight, String origin) {
this.id = id;
this.color = color;
this.weight = weight;
this.origin = origin;
}
// Omitir getter y setter
}

La necesidad inicial del usuario puede ser solo la de poder filtrar las manzanas verdes a través del programa, por lo que podemos implementar rápidamente a través del programa:

public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);}}
}
}
return filterApples;
}

Este código es muy simple, no hay nada que decir. Pero si la necesidad del usuario cambia a verde, parece que modificar el código también es simple, nada más que cambiar la condición de juicio de verde a rojo. Pero necesitamos considerar otro problema, ¿qué hacer si las condiciones de cambio son muy frecuentes?63; si es solo un cambio de color, bien, permitimos al usuario pasar la condición de juicio del color, el parámetro del método de juicio cambia a "el conjunto a juicio y el color a filtrar". Pero ¿qué pasa si el usuario no solo quiere juicio de color, sino también de peso, tamaño y otros? ¿No te parece que agregar diferentes parámetros en sucesión para completar el juicio es suficiente? ¿Pero es realmente bueno pasar parámetros de esta manera? Si las condiciones de filtrado se vuelven cada vez más complejas, ¿no tendremos que considerar todas las situaciones y tener una estrategia de respuesta para cada una?63; en este momento podemos parametrizar la acción, extraer las condiciones de filtrado y pasarlas como parámetros, en este momento podemos encapsular una interfaz de juicio:

public interface AppleFilter {
/**
* Abstracto de condiciones de filtrado
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}
/**
* Encapsulate the filtering conditions into an interface
*
* @param apples
* @param filter
* @return
*/
public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (filter.accept(apple)) {
filterApples.add(apple);}}
}
}
return filterApples;
}

Después de abstraer la acción anterior, podemos establecer las condiciones de filtrado en el lugar de la llamada específica y pasar las condiciones como parámetros al método, en este caso utilizando el método de clase anónima:

public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
// Filtrar manzanas
List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() {
@Override
public boolean accept(Apple apple) {
// Filtrar por peso mayor que10Manzana roja de 0g
return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100;
}
});
}

Este diseño se utiliza con frecuencia internamente en JDK, como Java.util.Comparator, java.util.concurrent.Callable, etc. Al usar este tipo de interfaz, podemos especificar la lógica de ejecución de la función específica en el lugar de la llamada utilizando una clase anónima, pero, como se puede ver en el bloque de código anterior, aunque es muy geek, no es lo suficientemente conciso en java8Podemos simplificarlo mediante lambda:

// Filtrar manzanas
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
//()->xxx () son los parámetros del método, xxx es la implementación del método

II. Definición de expresión lambda

Podemos definir la expresión lambda como una función anónima concise y pasable, primero necesitamos aclarar que la expresión lambda es esencialmente una función, aunque no pertenece a una clase específica, tiene lista de parámetros, cuerpo de función, tipo de retorno y puede lanzar excepciones; en segundo lugar, es anónima, la expresión lambda no tiene nombre de función; la expresión lambda se puede pasar como un parámetro, lo que simplifica significativamente la escritura del código. La definición del formato es la siguiente:

Formato uno: lista de parámetros -> expresión

Formato dos: lista de parámetros -> {colección de expresiones}

Es necesario tener en cuenta que la expresión lambda implícitamente contiene la palabra clave return, por lo que en una expresión individual no es necesario escribir explícitamente la palabra clave return, pero cuando la expresión es una colección de instrucciones,则需要 explícitamente agregar return y usar corchetes { } para encerrar múltiples expresiones. Aquí hay algunos ejemplos:

//Devuelve la longitud de la cadena dada, implícitamente return
(String s) -> s.length() 
// Siempre devuelve42método sin parámetros
() -> 42 
// Si contiene expresiones en múltiples líneas, utilice corchetes para delimitarlas
(int x, int y) -> {
int z = x * y;
return x + z;
}

Tercero. Uso de expresiones lambda basado en interfaces funcionales

El uso de expresiones lambda requiere la ayuda de interfaces funcionales, lo que significa que solo en los lugares donde aparecen interfaces funcionales podemos simplificarlas con expresiones lambda.

Interfaz funcional personalizada:

La interfaz funcional se define como una interfaz que solo tiene un método abstracto. java8La mejora en la definición de la interfaz es la introducción de métodos por defecto, lo que permite proporcionar una implementación por defecto para los métodos en la interfaz. Sin embargo, independientemente de cuántos métodos por defecto existan, siempre y cuando haya un solo método abstracto, entonces es una interfaz funcional, como se muestra a continuación (referencia a AppleFilter anterior):

/**
* Interfaz de filtrado de manzanas
*/
@FunctionalInterface
public interface AppleFilter {
/**
* Abstracto de condiciones de filtrado
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}

AppleFilter solo contiene un método abstracto accept(Apple apple), según la definición se puede considerar como una interfaz funcional. Al definir la interfaz, hemos añadido la anotación @FunctionalInterface para marcar que esta interfaz es una interfaz funcional. Aunque esta anotación es opcional, cuando se añade esta interfaz, el compilador limita que la interfaz solo puede tener un método abstracto, de lo contrario se producirá un error. Por lo tanto, se recomienda añadir esta anotación a la interfaz funcional.

Interfaz funcional integrada en jdk:

jdk ha integrado una rica interfaz funcional para expresiones lambda, a continuación, se explican los ejemplos de uso de Predicate<T>, Consumer<T> y Function<T, R> respectivamente.

Predicate:

@FunctionalInterface
public interface Predicate<T> {
/**
* Evalúa este predicado sobre el argumento dado.
*
* @param t el argumento de entrada
* @return {@code true} si el argumento de entrada coincide con el predicado
* de lo contrario {@code false}
*/
boolean test(T t);
}

La función de Predicate es similar a AppleFilter anterior, que utiliza las condiciones establecidas externamente para verificar los parámetros de entrada y devolver el resultado de verificación booleano. A continuación, utilice Predicate para filtrar los elementos de la colección List:

/**
*
* @param list
* @param predicado
* @param <T>
* @return
*/
public <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> newList = new ArrayList<T>();
for (final T t : list) {
if (predicate.test(t)) {
newList.add(t);
}
}
return newList;
}

Usar:

demo.filter(list, (String str -> null != str && !str.isEmpty());

Consumer

@FunctionalInterface
public interface Consumer<T> {
/**
* Ejecuta esta operación en el argumento proporcionado.
*
* @param t el argumento de entrada
*/
void accept(T t);
}

Consumer proporciona una función abstracta accept, que recibe parámetros pero no devuelve valores, a continuación, se utiliza Consumer para recorrer el conjunto.

/**
* Recorrer el conjunto, ejecutar comportamiento personalizado
*
* @param list
* @param consumer
* @param <T>
*/
public <T> void filter(List<T> list, Consumer<T> consumer) {
for (final T t : list) {
consumer.accept(t);
}
}

Utilizando la interfaz funcional superior, recorrer el conjunto de cadenas y imprimir las cadenas no vacías:

demo.filter(list, (String str -> {
if (StringUtils.isNotBlank(str)) {
System.out.println(str);
}
});

Function

@FunctionalInterface
public interface Function<T, R> {
/**
* Aplica esta función al argumento proporcionado.
*
* @param t el argumento de la función
* @return el resultado de la función
*/
R apply(T t);
}

Funcation ejecuta la operación de conversión, la entrada es datos de tipo T, y devuelve datos de tipo R, a continuación, se utiliza Function para convertir el conjunto:

public <T, R> List<R> filter(List<T> list, Function<T, R> function) {
List<R> newList = new ArrayList<R>();
for (final T t : list) {
newList.add(function.apply(t));
}
return newList;
}

Otro:

demo.filter(list, (String str -> Integer.parseInt(str));

Estos interfaces funcionales también proporcionan algunas implementaciones por defecto de operaciones lógicas, se presentarán más adelante en java8Métodos por defecto de la interfaz, hablaremos de ellos más adelante ~

Algunas cosas que hay que tener en cuenta durante su uso:

Inferencia de tipo:

En el proceso de codificación, a veces puede surgir la duda sobre qué interfaz funcional específica se coincidirá con nuestro código de llamada, en realidad, el compilador hará el juicio correcto según los parámetros, el tipo de retorno, el tipo de excepción (si existe)
En la llamada específica, en algunos casos, se puede omitir el tipo de parámetro para simplificar aún más el código:

// Filtrar manzanas
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
// En algunos casos, incluso podemos omitir el tipo de parámetro, el compilador juzgará correctamente según el contexto
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.R
> ED.equals(apple.getColor()) && apple.getWeight() >= 100);

Variable local

En todos los ejemplos anteriores, nuestras expresiones lambda siempre usan sus parámetros de cuerpo, también podemos usar variables locales en la lambda, como follows

int weight = 100;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);:

En este ejemplo, usamos la variable local weight en la lambda, pero para usar variables locales en la lambda, es necesario que se declaren explícitamente como final o efectivamente final, porque las variables locales se almacenan en la pila y la expresión lambda se ejecuta en otro hilo, cuando este hilo intenta acceder a la variable local, existe la posibilidad de que se modifique o recicle, por lo que después de usar final, no existirán problemas de seguridad de hilos.

Métodos de referencia

Usar la referencia de método puede simplificar aún más el código, a veces esta simplificación hace que el código sea más intuitivo, veamos un ejemplo:

/* ... omitir la operación de inicialización de apples */
// Adopta la expresión lambda
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
// usando la referencia de método
apples.sort(Comparator.comparing(Apple::getWeight));

La referencia de método utiliza :: para conectar el método perteneciente y el método propio, y se divide principalmente en tres tipos:

método estático

(args) -> ClassName.staticMethod(args)

convertir en

ClassName::staticMethod

método de instancia de parámetros

(args) -> args.instanceMethod()

convertir en

> ClassName::instanceMethod // ClassName es el tipo de args

método de instancia externo

(args) -> ext.instanceMethod(args)

convertir en

ext::instanceMethod(args)

Referencia:

http://www.codeceo.com/artículo/lambda-de-java-8.html

Lo mencionado anteriormente es lo que el editor les ha presentado sobre JDK8Nueva característica: expresiones lambda, espero que les sea útil. Si tienen alguna pregunta, déjenme un mensaje y editaré las respuestas a tiempo. También les agradezco mucho el apoyo a la página web de tutorial de alarido!

Declaración: El contenido de este artículo se obtiene de la red, pertenece al autor original, se contribuye y sube por los usuarios de Internet de manera autónoma. Este sitio no posee los derechos de propiedad, no ha sido editado por humanos y no asume ninguna responsabilidad legal relacionada. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, reemplace # con @) para denunciar y proporcionar evidencia relevante. Una vez confirmado, este sitio eliminará inmediatamente el contenido sospechoso de infracción.

Te gustará