Mi opinión sobre las expresiones de Lambda en Java está bastante enredado:
Uno creo que de esta manera: las expresiones de lambda reducen la experiencia de lectura de los programas Java. Los programas de Java nunca han sido sobresalientes en expresividad. Por el contrario, uno de los factores que hace que Java sea popular es su seguridad y conservadurismo, incluso los principiantes pueden escribir un código robusto y fácil de mantener siempre que le presten atención. Las expresiones Lambda tienen requisitos relativamente más altos para los desarrolladores, por lo que también aumentan algunas dificultades de mantenimiento.
Creo que otra cosa es: como código de código, es necesario aprender y aceptar nuevas características del idioma. Si renuncia a sus fortalezas expresivas solo por su pobre experiencia de lectura, entonces a algunas personas les resulta difícil entender incluso las expresiones trinoculares. El lenguaje también se está desarrollando, y aquellos que no pueden mantener el ritmo se dejarán atrás voluntariamente.
No quiero que me queden atrás. Sin embargo, si tuviera que tomar una decisión, mi decisión aún era relativamente conservadora: no hay necesidad de usar lambda en el idioma Java: hace que muchas personas en el círculo de Java actual no se acostumbren a él y causará un aumento en los costos laborales. Si te gusta mucho, puedes considerar usar Scala.
De todos modos, todavía comencé a tratar de dominar a Lambda, después de todo, parte del código mantenido en el trabajo usa Lambda (confía en mí, lo eliminaré gradualmente). Los tutoriales para aprender son tutoriales relacionados en el sitio web oficial de Oracle Java.
"` `... ...
Supongamos que actualmente se está creando una aplicación de red social. Una característica es que los administradores pueden realizar ciertas acciones en los miembros que cumplen con los criterios especificados, como enviar mensajes. La siguiente tabla describe este caso de uso en detalle:
| Campo | describir |
| nombre | Acciones para realizar |
| Participantes clave | administrador |
| Requisitos previos | Administrador Iniciar sesión en el sistema |
| Post-condición | Solo realice acciones para miembros que cumplan con los criterios especificados |
| Escenario de éxito principal | 1. El administrador establece los estándares de filtrado para que los miembros del destino realicen la operación; 2. El administrador selecciona la acción para realizar; 3. El administrador hace clic en el botón Enviar; 4. El sistema encuentra miembros que cumplen con los criterios especificados; 5. El sistema realiza operaciones preseleccionadas en los miembros que cumplen con los criterios especificados. |
| Extendido | Antes de seleccionar la operación de ejecución o antes de hacer clic en el botón Enviar, el administrador puede elegir si se debe ver la información de los miembros que cumpla con los criterios de filtrado. |
| Frecuencia de ocurrencia | Sucede muchas veces en un día. |
Use la siguiente clase de persona para representar la información de los miembros en las redes sociales:
Persona de clase pública {Public Enum Sex {hombre, mujer} nombre de cadena; Cumpleaños local; Género sexual; Cadena de correo electrónico de Madre; public int getAge () {// ...} public void printPerson () {// ...}}Suponga que todos los miembros se guardan en una instancia de la lista < -persona>.
En esta sección, comenzamos con un método muy simple, luego tratamos de implementarlo utilizando clases locales y clases anónimas, y al final experimentaremos gradualmente la potencia y la eficiencia de las expresiones lambda. El código completo se puede encontrar aquí.
Solución 1: Crear métodos para encontrar miembros que cumplan con los criterios especificados uno por uno
Esta es la solución más simple y más dura para implementar los casos antes mencionados: es crear varios métodos y cada método verifica un criterio (como la edad o el género). El siguiente código verifica que la edad es mayor que un valor especificado:
public static void printPersonsoldertan (List <Oll> Roster, Int Age) {para (Person P: Roster) {if (p.getage ()> = Age) {P.Printperson (); }}}Esta es una solución muy frágil, y es muy probable que la aplicación no se ejecute debido a una pequeña actualización. Si agregamos nuevas variables de miembros a la clase de persona o cambiamos el algoritmo para medir la edad en el estándar, necesitamos reescribir mucho código para adaptarse a este cambio. Además, las restricciones aquí son demasiado rígidas. Por ejemplo, ¿qué debemos hacer si queremos imprimir miembros que sean más jóvenes que un valor específico? ¿Agregar otro nuevo método printPersonsyoungerthan? Este es obviamente un método estúpido.
Solución 2: crear un método más general
El siguiente método es más adaptable que PrintPersonsolderthan; Este método imprime la información del miembro dentro del grupo de edad especificado:
public static void printPersonSonSwithinagerange (List <Oll> Roster, int Low, int High) {for (persona p: lista) {if (low <= p.getage () && p.getage () <high) {p.printperson (); }}}Ahora hay una nueva idea: ¿qué debemos hacer si queremos imprimir la información de los miembros del género especificado, o eso cumple con el género especificado y está dentro del grupo de edad especificado? ¿Qué pasa si ajustamos la clase de la persona y agregamos propiedades como la amistad y la ubicación geográfica? Aunque escribir métodos como este es más universal que PrintPersonsyoungerthan, escribir un método para cada consulta posible también puede conducir a la fragilidad en el código. Es mejor poner el código de verificación estándar en una nueva clase.
Solución 3: Implementar la inspección estándar en una clase local
El siguiente método imprime la información de los miembros que cumple con los criterios de búsqueda:
public static void printPersons (List <Oll> Roster, Checkperson Tester) {for (Person P: Roster) {if (tester.test (p)) {P.printperson (); }}}Se utiliza un probador de objeto de verificación en el programa para verificar cada instancia en la lista de parámetros de la lista. Si Tester.test () devuelve verdadero, se ejecutará el método printPerson (). Para establecer los criterios de búsqueda, la interfaz de verificación debe implementarse.
La siguiente clase implementa el verificador y proporciona una implementación específica del método de prueba. El método de prueba en esta clase filtra información sobre la membresía que cumple con los requisitos para el servicio militar en los Estados Unidos: es decir, género masculino y edad entre 18 y 25 años.
Class CheckpersonEligibleForSelectiveService Implementa el checkperson {Public Boolean Test (Person P) {return p.gender == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25; }}Para usar esta clase, debe crear una instancia y activar el método PrintPersons:
printpersons (lista, nuevo checkpersoneligibleForSelectiveService ());
El código ahora parece menos frágil: no necesitamos reescribir el código debido a los cambios en la estructura de la clase de la persona. Sin embargo, todavía hay código adicional aquí: una interfaz recientemente definida que define una clase interna para cada estándar de búsqueda en la aplicación.
Debido a que CheckpersonLigibleForSelectiveRervice implementa una interfaz, se puede usar una clase anónima sin definir una clase interna para cada estándar.
Solución 4: Use clases anónimas para implementar la inspección estándar
Un parámetro en el método PrintPersons llamado a continuación es la clase anónima. La función de esta clase anónima es la misma que la de la clase de servicio de control de control de chequeo en el esquema 3: Todos son miembros filtrados con género masculino y edades entre 18 y 25 años.
printPersons (lista, nuevo checkperson () {public boolean test (persona p) {return p.getGender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25;}});Este esquema reduce la cantidad de codificación, porque ya no es necesario crear nuevas clases para cada esquema de búsqueda que se ejecute. Sin embargo, todavía es un poco incómodo hacer esto: aunque la interfaz de verificación tiene solo un método, la clase anónima implementada sigue siendo un poco detallada y voluminosa. En este momento, puede usar la expresión de Lambda para reemplazar las clases anónimas. Lo siguiente explicará cómo usar la expresión de lambda para reemplazar las clases anónimas.
Solución 5: Use expresiones lambda para implementar la verificación estándar
La interfaz de verificación es una interfaz funcional. La llamada interfaz funcional se refiere a cualquier interfaz que solo contenga un método abstracto. (Una interfaz funcional también puede tener múltiples métodos predeterminados o métodos estáticos). Dado que solo hay un método abstracto en la interfaz funcional, el nombre del método del método se puede omitir al implementar el método de esta interfaz funcional. Para implementar esta idea, puede reemplazar las expresiones de clase anónima con expresiones Lambda. En el método PrintPersons reescrito a continuación, el código relevante se resalta:
printPersons (lista, (persona p) -> p.getgender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25);
Aquí también puede usar una interfaz de función estándar para reemplazar la interfaz de verificación, simplificando aún más el código.
Solución 6: Use interfaces funcionales estándar en expresiones lambda
Echemos un vistazo a la interfaz del verificación:
Interface Checkperson {Test Boolean (Persona P); }Esta es una interfaz muy simple. Debido a que solo hay un método abstracto, también es una interfaz funcional. Este método abstracto solo acepta un parámetro y devuelve un valor booleano. Esta interfaz abstracta es tan simple que consideraremos si es necesario definir dicha interfaz en la aplicación. En este momento, puede considerar el uso de interfaces funcionales estándar definidas por JDK, y puede encontrar estas interfaces en el paquete java.util.function.
En este ejemplo, podemos usar la interfaz de predicado <t> para reemplazar el verificación. Hay un método de prueba booleana (T T) en esta interfaz:
Predicado de interfaz <T> {prueba booleana (t t); }La interfaz de predicado <t> es una interfaz genérica. Una clase genérica (o una interfaz genérica) especifica uno o más parámetros de tipo usando un par de soportes de ángulo (<>). Solo hay un parámetro de tipo en esta interfaz. Cuando declara o instancia una clase genérica utilizando una clase concreta, obtiene una clase parametrizada. Por ejemplo, el predicado de clase parametrizado <Oll> es así:
interfaz predicado <Oll> {prueba booleana (persona t); }En esta clase parametrizada, existe un método que es consistente con los parámetros y los valores de retorno del método de prueba. Por lo tanto, puede usar la interfaz de predicado <t> para reemplazar la interfaz de verificación como se demuestra en el siguiente método:
public static void printPersonSwithPredicate (List <Oll> Roster, Predicate <Oll> tester) {for (persona p: lista) {if (tester.test (p)) {p.printperson (); }}}Luego use el siguiente código para filtrar a los miembros del servicio militar como en el Plan 3:
printPersonSwithPredicate (lista, p -> p.getgender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25);
¿Ha notado que cuando se usa predicado <Oll> Como tipo de parámetro, no se especifica ningún tipo de parámetro explícito? Este no es el único lugar donde se aplican las expresiones lambda. El siguiente esquema introducirá un mayor uso de expresiones lambda.
Solución 7: Use expresiones lambda en toda la aplicación
Echemos un vistazo al método printPersonsonswithPredicate y consideremos si puede usar expresiones lambda aquí:
public static void printPersonSwithPredicate (List <Oll> Roster, Predicate <Oll> tester) {for (persona p: lista) {if (tester.test (p)) {p.printperson (); }}}En este método, cada instancia de persona en la lista se verifica utilizando el probador de instancia de predicado. Si la instancia de la persona cumple con los criterios de verificación definidos en el probador, se activará el método Printperson de la instancia de la persona.
Además de activar el método Printperson, las instancias de persona que cumplen con el estándar del probador también pueden ejecutar otros métodos. Puede considerar usar una expresión de Lambda para especificar el método que se ejecutará (creo que esta característica es buena, lo que resuelve el problema de que los métodos en Java no pueden pasar como objetos). Ahora necesita una expresión de lambda similar al método Printperson: una expresión de Lambda que solo requiere un parámetro y devuelve vacío. Recuerde una cosa: para usar expresiones lambda, primero debe implementar una interfaz funcional. En este ejemplo, se necesita una interfaz funcional, que contiene solo un método abstracto. Este método abstracto tiene un parámetro de tipo de persona y vuelve a vaciar. Puede echar un vistazo al consumidor de interfaz funcional estándar <t> proporcionado por JDK, que tiene un método abstracto, NUEVA, Aceptar (t t) solo cumple con este requisito. En el siguiente código, use una instancia del consumidor <T> para llamar al método de aceptación en lugar de P.Printperson ()::
Public static void Processpersons (List <Oll> Roster, Predicate <Oll> Tester, Consumer <Oll> Block) {for (Person P: Roster) {if (tester.test (p)) {block.accept (p); }}}En consecuencia, puede usar el siguiente código para filtrar a los miembros de la era del servicio militar:
ProcessPersons (lista, p -> p.getgender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25, p -> p.printperson ());
Si queremos hacer cosas que no solo impriman la información de los miembros, sino más cosas, como verificar la membresía, obtener información de contacto de los miembros, etc. En este punto, necesitamos una interfaz funcional con un método de valor de retorno. La función de interfaz funcional estándar de JDK <t, r> tiene un método como este rplic (t t). El siguiente método obtiene datos del mapeador de parámetros y realiza el comportamiento especificado por el bloque de parámetros en estos datos:
Public static void ProcessperSonSonSwithFunction (List <Oll> Roster, Predicate <Oll> tester, function <persona, string> mapper, consumidor <string> block) {for (persona p: roster) {if (tester.test (p)) {string data = mapper.apply (p); block.accept (datos); }}}El siguiente código obtiene la información de correo electrónico de todos los miembros de la era del servicio militar en la lista y la imprime:
ProcessPersonSwithFunction (Roster, P -> p.getgender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25, p -> p.getemailaddress (), correo electrónico -> system.out.println (correo electrónico));
Solución 8: Use genéricos con más frecuencia
Revisemos el método processpersonswithithition. La siguiente es una versión genérica de este método. El nuevo método requiere más tolerancia en los tipos de parámetros:
Public static <x, y> void ProcessElements (ITerable <x> fuente, predicado <x> tester, function <x, y> mapper, consumo <y> block) {for (x p: fuente) {if (tester.test (p)) {y data = mapper.apply (p); block.accept (datos); }}}Para imprimir la información de los miembros para el servicio militar a la edad correcta, puede llamar al método de procesos como el siguiente:
ProcessElements (Roster, P -> p.getGender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25, p -> p.getemailaddress (), correo electrónico -> system.println (correo electrónico));
Durante el proceso de llamadas de método, se realiza el siguiente comportamiento:
Obtenga información de objetos de una colección, en este ejemplo, obtenga información de objetos de persona de la lista de instancias de recopilación.
Filtrar objetos que pueden coincidir con el probador de instancia de predicado. En este ejemplo, el objeto predicado es una expresión de lambda que especifica las condiciones para filtrar el servicio militar a la edad correcta.
El objeto filtrado se entrega a un mapeador de objeto de función para su procesamiento, y el mapeador coincidirá con un valor con este objeto. En este ejemplo, el mapeador de objeto de función es una expresión de Lambda que devuelve la dirección de correo electrónico de cada miembro.
Especifica un comportamiento del bloque de objeto del consumidor para el valor coincidente por el mapeador. En este ejemplo, el objeto de consumo es una expresión lambda, que es la función de imprimir una cadena, que es la dirección de correo electrónico del miembro devuelta por el mapeador de instancia de función.
Solución 9: Use la operación de agregación utilizando la expresión de Lambda como parámetro
El siguiente código utiliza la operación de agregación para imprimir las direcciones de correo electrónico de los miembros de la edad militar en la colección de la lista:
roster.stream () .filter (p -> p.getgender () == Person.sex.male && p.getage ()> = 18 && p.getage () <= 25) .map (p -> p.getemailaddress ()) .forEach (correo electrónico -> system.out.println (correo electrónico));
Analice el proceso de ejecución del código anterior y organice la siguiente tabla:
Comportamiento | Operación de agregación |
Obtener el objeto | Stream <E> Stream () |
Filtrar objetos que coinciden con los criterios especificados de la instancia de predicado | Stream <T> Filtro (predicado <? Super t> predictar) |
Obtenga el valor coincidente del objeto a través de una instancia de función | <r> stream <r> map (function <? Super t,? Extiende r> mapper) |
Ejecutar el comportamiento especificado por la instancia del consumidor | Void foreach (consumidor <? Super t> acción) |
Las operaciones de filtro, mapa y foreach en la tabla son operaciones agregadas. Los elementos procesados por la operación de agregación provienen de la transmisión, no directamente de la colección (es decir, porque el primer método llamado en este programa de ejemplo es Stream ()). Una transmisión es una secuencia de datos. A diferencia de las colecciones, Stream no almacena datos con una estructura específica. En cambio, Stream obtiene datos de una fuente específica, como de una colección, a través de una tubería. Pipeline es una secuencia de operación de flujo, en este ejemplo Filter-Map-Foreach. Además, las operaciones de agregación generalmente usan expresiones Lambda como parámetros, lo que también nos brinda mucho espacio personalizado.