Comprensión profunda de expresiones Java Lambda, funciones anónimas, cierres

prefacio

Para las expresiones Lambda, siempre he sabido lo que no sé por qué. Para averiguar qué son las expresiones Lambda, así como el uso y la función de las expresiones Lambda, este artículo nació como una nota de estudio y lo compartí. Bienvenido a corregir e intercambiar.

que es lambda

Seamos más serios, Google Translate ingresa a Lambda para la traducción:

¿Pues nada? De ninguna manera, búsqueda de la Enciclopedia Baidu:

Como se muestra en la figura, para la programación, debemos centrarnos en las expresiones lambda.

¿Qué son las expresiones lambda?

Busque expresiones Lambdba para ver:

Hay imágenes y textos, así como videos, lo cual es muy cómodo ahora. La explicación de la expresión Lambda extraída es la siguiente:

La expresión lambda (expresión lambda) es una función anónima . La expresión lambda se nombra según el cálculo lambda en matemáticas , que corresponde directamente a la abstracción lambda (abstracción lambda). Es una función anónima, es decir, una función sin nombre de función . Las expresiones lambda pueden representar cierres (observe la diferencia con el sentido tradicional de las matemáticas).

Fuente: Expresiones Lambda

De la descripción anterior, hay varias palabras clave además de la expresión Lambda: función anónima, cálculo lambda y cierre. ¿Qué significa todo esto? Sigamos explorando.

Nota: λ es la undécima letra del alfabeto griego, la mayúscula correspondiente es Λ y el nombre en inglés es Lambda

¿Qué es el cálculo lambda?

Parece que el cálculo lambda es más crítico, porque es el origen, y comprender esto nos ayuda a comprender las expresiones lambda en esencia.

λ-calculus (inglés: lambda calculus, λ-calculus) es un conjunto de sistemas formales desarrollados a partir de la lógica matemática, que utiliza reglas de sustitución y vinculación de variables para estudiar cómo se definen abstractamente las funciones , cómo se aplican las funciones y la recursividad . Fue publicado por primera vez por el matemático Alonzo Church en la década de 1930. Como modelo informático ampliamente utilizado, el cálculo lambda puede definir claramente qué es una función computable, y cualquier función computable puede expresarse y evaluarse de esta forma, y ​​puede simular el proceso informático de una máquina de Turing de una sola cinta; aunque Por lo tanto, el lambda el cálculo enfatiza la aplicación de reglas de transformación en lugar de las máquinas específicas que las implementan.

Fuente: Lambda Calculus_Wikipedia

Como se mencionó anteriormente, el cálculo Lambda es un sistema formal. ¿Qué es un sistema formal? En lógica y matemáticas, un sistema formal se compone de dos partes, un lenguaje formal más un conjunto de reglas de inferencia o reglas de transformación.

En matemáticas , lógica e informática , un lenguaje formal es un lenguaje definido por fórmulas matemáticas o procesables por máquina precisas .

Al igual que el lenguaje en lingüística , los lenguajes formales generalmente tienen dos aspectos: la sintaxis y la semántica . La rama de las matemáticas y la informática que estudia la sintaxis de los lenguajes se denomina teoría formal del lenguaje , que estudia la sintaxis de un lenguaje sin trabajar su semántica. En la teoría del lenguaje formal, un lenguaje formal es una colección de ciertas cadenas de longitud finita en un alfabeto . Un lenguaje formal puede contener un número infinito de cadenas.

Para un cálculo, se deben definir dos cosas: una gramática , que describe cómo escribir expresiones legales en el cálculo (correspondiente a un lenguaje formal en un sistema formal); y un conjunto de reglas que permiten manipular expresiones simbólicamente (correspondiente a Un conjunto de reglas de inferencia o reglas de transformación en un sistema formal).

Lo anterior proviene de: traducción al chino de la serie de cálculo Lambda de Good Math/Bad Math

composición

El cálculo lambda consta de 3 elementos: variable ( nombre ) , función (función) y aplicación (aplicación) :

nombre

sintaxis

ejemplo

explicar

variables ( nombre )

<nombre>

X

Una variable llamada "x"

función

λ<parámetros>.<cuerpo>

λx.x

función con argumento 'x' y cuerpo 'x'

solicitud

<función><variable o función>

(λx.x)a

Función de llamada "λx.x" con argumento "a"

La letra griega λ (pronunciada: Lambda) y un punto (.). λ y el punto se utilizan para describir (definir) funciones anónimas. Las funciones comienzan con lambda y variables, seguidas de un punto y luego el cuerpo de la función. λ no tiene ningún significado especial, solo dice que la función comienza desde aquí. Después de λ, la letra antes del punto se llama variable, la parte antes del punto se llama cabeza y la expresión después del punto se llama cuerpo.

Pregunta : ¿Por qué λ?

Respuesta : oportunidad. Quizá al principio Church dibujó un símbolo superior allí arriba, como este: (ŷ xy) ab. En el manuscrito, escribió (⋀y.xy) ab. Finalmente, el trabajador de composición tipográfica lo cambió a este (λy.xy) ab.

Fuente: Cálculo Lambda escrito por científicos cognitivos para Xiaobai

La función más básica es la función de identidad: λx.x que es equivalente a f(x) = X. La primera "x" es el argumento de la función y la segunda es el cuerpo de la función.

Variables libres y restringidas

  • En la función λx.x, "x" se denomina variable ligada porque se encuentra tanto en el cuerpo de la función como en un parámetro.
  • En λx.y, "y" se llama variable libre porque nunca se declaró de antemano.

Debido a que las funciones pueden ser parte de otras funciones, una variable puede estar ligada y libre al mismo tiempo.

expresión

El concepto central del cálculo Lambda es "expresión" ("expresión") . Una expresión puede ser simplemente una variable (nombre) o una función (función) o una aplicación (aplicación).

Una expresión de cálculo lambda se define de la siguiente manera:

<expresión> := <nombre>|<función>|<aplicación> 
<función> := λ <nombre> <expresión> <aplicación> 
:= <expresión><expresión> 

Forma de traducción 
<expresión> := <carácter identificador >|<función>|<aplicación>        
<función> := λ<identificador> .<expresión> < 
aplicación> := <expresión><expresión>

Zurra

Hay un truco en el cálculo Lambda: si observa la definición anterior, verá que una función (expresión Lambda) toma solo un parámetro. Esto parece una gran limitación: ¿cómo puede implementar la suma con un solo argumento?

No hay nada de malo en eso, porque las funciones son valores. Puede escribir una función que tome un parámetro y esa función devuelva una función que tome un parámetro, por lo que puede escribir una función que tome dos parámetros, esencialmente lo mismo. Esto se llama Currying, llamado así por el gran lógico Haskell Curry.

Por ejemplo, queremos escribir una función para lograr x + y. Estamos más acostumbrados a escribir algo como: lambda xy .plus xy. La forma de escribir una función de un solo parámetro es: escribimos una función con un solo parámetro y dejamos que devuelva otra función con un solo parámetro. Entonces x + y se convierte en una función de un argumento x que devuelve otra función que agrega x a su propio argumento:

lambda x. ( lambda y. más xy )

Ahora que sabemos que agregar funciones de múltiples argumentos en realidad no agrega nada, sino que simplifica la sintaxis, entonces usaré funciones de múltiples argumentos cuando sea conveniente a medida que avance.

algoritmo

El cálculo lambda tiene sólo dos leyes reales: llamadas Alfa y Beta . Alpha también se conoce como "transformación" y Beta también se conoce como "estado".

conversión alfa

Alpha es una operación de cambio de nombre; básicamente dice que el nombre de la variable no es importante: dada una expresión arbitraria en el cálculo de Lambda, podemos modificar el nombre de un parámetro de función siempre que también modifiquemos todas las referencias libres a él en el cuerpo de la funcion

Entonces, por ejemplo, si hay una expresión como esta:

lambda x. si (= x 0) entonces 1 sino x ^ 2

Podemos transformar x en y usando la transformación alfa (escrito alfa [x / y]), por lo que tenemos:

lambda y. si (= y 0) entonces 1 sino y ^ 2

Esto no cambia el significado de la expresión en lo más mínimo. Pero, como veremos más adelante, esto es importante porque nos permite implementar cosas como la recursividad .

protocolo beta

La belleza de la especificación Beta es que esta regla permite que el cálculo de Lambda realice cualquier cálculo que pueda realizar una máquina.

Beta básicamente significa que si tiene una aplicación de función, puede reemplazar la parte del cuerpo de la función que está relacionada con el identificador de función correspondiente reemplazando el identificador con el valor del parámetro. Suena complicado, pero es fácil de usar.

Supongamos que tenemos una expresión de aplicación de función: “ (lambda x . x + 1) 3 “. La llamada especificación Beta es que podemos implementar la aplicación de la función reemplazando el cuerpo de la función (es decir, "x + 1") y reemplazando el parámetro referenciado "x" con el valor "3". Entonces, el resultado del protocolo Beta es " 3 + 1”.

Un ejemplo un poco más complicado: (lambda y . (lambda x . x + y)) q。esta es una expresión interesante, porque el resultado de aplicar esta expresión Lambda es otra expresión Lambda: es decir, es una función que crea una función. En este momento, el protocolo Beta necesita reemplazar todos los parámetros de referencia "y" con el identificador "q". Entonces, el resultado es “ lambda x . x + q “.

Fuente: My Favorite Lambda Calculus - Apertura · weblog de cgnail

Cierre o encuadernación completa

Un término es cerrado si no tiene variables libres; de lo contrario, está abierto.

Una expresión de cálculo lambda está cerrada si no contiene variables libres; de lo contrario, está abierta.

Fuente: http://www.cs.yale.edu/homes/hudak/CS201S08/lambda.pdf

Se dice que un identificador está vinculado (restringido) si es un parámetro de una expresión lambda cerrada; un identificador es libre si no está vinculado en ningún contexto envolvente.

λx.xy: En esta expresión, y es libre, porque no es un parámetro de ninguna expresión Lambda cerrada; y x está acotado (restringido, porque es un parámetro de la expresión cerrada xy de la definición de la función.

λxy.xy: tanto x como y están vinculados (restringidos) en esta expresión porque son parámetros en la definición de la función.

λy .(λx.xyz): En el cálculo interno λx.xyz , y y z son libres, y x está ligado (restringido). En la expresión completa, x e y están ligados (restringidos): x está ligado (restringido) por el cálculo interno e y está ligado (restringido) por el cálculo restante. Pero z sigue siendo libre.

Al evaluar una expresión de cálculo lambda, no puede hacer referencia a ningún identificador (variable) no vinculado (restringido), porque no hay forma de saber el valor de las variables libres a menos que haya otra forma de proporcionar el valor de estas variables libres. Sin embargo, cuando ignoramos el contexto y nos enfocamos en las subexpresiones de una expresión compleja, se permiten variables libres, y es muy importante averiguar qué variables en la subexpresión son libres.

evaluar

La evaluación se realiza mediante una sola regla de transformación (sustitución de variable, a menudo llamada β-Reducción , β-transformación), que es esencialmente una sustitución de alcance léxico.

Al evaluar la expresión (λx.x)a, reemplazamos todas las apariciones de "x" en el cuerpo de la función con "a".

  • (λx.x)a se evalúa como: a
  • (λx.y)a se evalúa como: y

Incluso puedes crear funciones de orden superior:

  • (λx.(λy.x))a se evalúa como: λy.a

Si bien el cálculo lambda tradicionalmente solo ha admitido funciones de un solo argumento, podemos crear funciones de múltiples argumentos utilizando una técnica llamada currying .

  • (λx.λy.λz.xyz) es equivalente a f(x, y, z) = ((xy) z)

A veces λxy.<cuerpo> se usa indistintamente con: λx.λy.<cuerpo>

Fuente: Learn X in Y Minutes: Scenic Programming Language Tours

¿Qué es una función anónima?

¿Qué es una función anónima? Esto es muy cierto, una función anónima es una función sin nombre. En cuanto a lo que es una función, el autor no se extenderá. Tenga en cuenta que aquí el autor analiza las funciones anónimas en el lenguaje de programación Java.

¿Qué te recuerdan las funciones anónimas ? Clase anónima , ¿verdad? Una clase anónima es una clase sin nombre . Una función anónima es una función que no tiene nombre y no pertenece a ninguna clase.

Java siempre ha sido un lenguaje de programación orientado a objetos. Esto significa que todo en la programación de Java gira en torno a objetos (excepto ciertos tipos primitivos por simplicidad). Antes de Java 8, la función era parte de la clase, necesitamos usar clase/objeto para llamar a la función. En Java 8 y versiones posteriores, hay una función que no pertenece a ninguna clase, definitivamente te dará curiosidad, ¿no viola esto la idea de programación orientada a objetos? Si buscamos en otros lenguajes de programación como C++, C#, JavaScript; encontraremos que se llaman lenguajes de programación funcionales porque podemos escribir funciones y usarlas cuando las necesitemos. Algunos de estos lenguajes soportan tanto la programación orientada a objetos como la programación funcional . ¿ Qué es la programación funcional? Se recomienda leer el artículo "Un estudio preliminar de la programación funcional" de Ruan Yifeng para conocerlo.

Para comprender en profundidad las funciones anónimas, se recomienda comprender primero las clases anónimas. Una clase anónima también es esencialmente una expresión. La sintaxis de una expresión de clase anónima es similar a una llamada de constructor, excepto que la definición de clase se incluye en el bloque de código.

Las expresiones de clase anónimas incluyen lo siguiente:

  • nuevo operador;
  • el nombre de la interfaz a implementar o la clase a extender;
  • Los paréntesis que encierran los parámetros del constructor, al igual que las expresiones de creación de instancias de clase ordinarias. Nota: si no hay un constructor al implementar una interfaz, se requieren un par de corchetes vacíos.
  • Un cuerpo que es un cuerpo de declaración de clase. Más específicamente, en un cuerpo, se permiten declaraciones de métodos, pero no sentencias.

Debido a que una definición de clase anónima es una expresión, debe ser parte de la instrucción. Esta clase anónima explica por qué hay un punto y coma después de la llave de cierre.

Clases anónimas Las clases anónimas pueden capturar variables al igual que las clases locales, tienen el mismo acceso a las variables locales del ámbito adjunto:

  • Una clase anónima puede acceder a los miembros de su clase envolvente.
  • Una clase anónima no puede acceder a las variables locales en su ámbito adjunto que no se declaran finales o efectivamente finales (Efectivamente final).
  • Al igual que con las clases anidadas, las declaraciones de tipos (como variables) en clases anónimas ocultan cualquier otra declaración del mismo nombre en el ámbito adjunto.

Las variables locales a las que acceden las clases internas locales y las clases internas anónimas en Java deben ser modificadas por final para garantizar la coherencia de los datos entre las clases internas y las clases externas. Pero a partir de Java 8, podemos agregar el modificador final sin agregar el modificador final, que es agregado por defecto por el sistema, por supuesto, esto no está permitido en versiones anteriores a Java 8. Java se refiere a esta característica como una característica final efectiva.

Las clases anónimas también tienen las mismas restricciones sobre sus miembros que las clases locales:

  • No puede declarar bloques de inicialización estáticos o interfaces de miembros en una clase anónima.
  • Las clases anónimas pueden tener miembros estáticos, siempre que sean variables constantes.

Lo siguiente se puede declarar en una clase anónima:

  • campo
  • Métodos adicionales (incluso si no implementan ningún método del supertipo)
  • Inicializadores de instancia
  • clase local (clase local)

Sin embargo, los constructores no se pueden declarar en clases anónimas.

¿Por qué gastar tanto tiempo introduciendo clases anónimas, por ejemplo:

public class HelloTest { 

    private String text = "world"; 

    interfaz pública IHello { 

        void sayHello(); 
        
    } 

    private void hola() { 

        IHello hola = new IHello() { 

            @Override 
            public void decirHola() { 
                System.out.print("Hola" + texto); 
            } 
            
        }; 

    } 

    public static void main(String[] args) { 

    } 
}

El código anterior define una interfaz IHello, que contiene solo un método abstracto void sayHello(). Esto se escribe usando una clase anónima, y ​​el compilador le pedirá que lo reemplace con una expresión Lambda.

Después del reemplazo, se convierte en:

public class HelloTest { 

    private String text = "world"; 

    interfaz pública IHello { 

        void sayHello(); 

    } 

    private void hola() { 

        Hola hola = () -> System.out.print("Hola " + texto); 

    } 

    public static void main(String[] args) { 

    } 
}

Pero si la interfaz IHello contiene dos o más métodos abstractos, el compilador no le pedirá que los reemplace con expresiones Lambda.¿Por qué?

public class HelloTest { 

    private String text = "world"; 

    interfaz pública IHello { 

        void sayHello(); 

        void tiempodeimpresión(); 
    } 

    private void hola() { 

        IHello hola = new IHello() { 

            @Override 
            public void decirHola() { 
                System.out.print("Hola" + texto); 
            } 

            @Override 
            public void printTime() { 

            } 
        }; 

    } 

    public static void main(String[] args) { 

    } 
}

Porque, Java estipula que si una interfaz tiene uno y solo un método abstracto, entonces la interfaz se denomina interfaz funcional/interfaz funcional (interfaz funcional) , como Comparable, Runnable, EventListener, Comparator, etc. En Java8 y versiones posteriores, las interfaces de función pueden usar expresiones Lambda en lugar de expresiones de clase anónimas, lo que significa que las interfaces de función se pueden implementar usando funciones anónimas en lugar de clases anónimas , que es una de las características de las expresiones Lambda. Por lo tanto, comprender las clases anónimas nos ayuda a comprender las funciones anónimas en esencia.

Pero, ¿por qué llamamos a este tipo de interfaz una interfaz funcional ? ¿Por qué no llamarlo interfaz de método único?

Esta es una buena pregunta, si sabes algo sobre programación funcional, sabes que puedes pasar código, es decir, funciones, al igual que puedes pasar datos u objetos a métodos. Estas interfaces tienen solo un método abstracto que se utiliza para pasar código como los lenguajes de programación funcional pasan funciones, por eso se llaman interfaces funcionales.

Un ejemplo sencillo:

Runnable runnable = new Runnable(){ 
    @Override 
    public void run(){ 
        System.out.println("Ejecutando sin Lambda"); 
    } 
}; 
nuevo subproceso (ejecutable). inicio (); 

// Ejecutando con Lambda 
new Thread(() -> System.out.println("Ejecutando sin Lambda")).start();

Mirando de cerca, estamos usando estas interfaces para pasar código al constructor Thread, y para la clase Thread, lo importante es el código en el método de ejecución. Además, podemos reemplazar fácilmente la implementación del método de ejecución, estas interfaces son en realidad interfaces de estrategia, porque esta es la implementación del patrón de estrategia, donde el código que compone la estrategia se inyecta en el código que ejecuta la estrategia en tiempo de ejecución.

Una interfaz con un único método abstracto se utiliza como función, por lo que se denomina interfaz funcional y no tiene nada de malo.

¿Qué sucede si se declaran varios métodos abstractos en la interfaz? ¿Todavía se puede usar así? La respuesta es no. Debido a que debe implementar todos los métodos abstractos, la expresión Lambda informará un error en este momento. Para evitar que se declaren múltiples métodos abstractos en una interfaz funcional, Java 8 proporciona una anotación @FunctionalInterface para declarar una interfaz funcional, no es obligatorio usarla, pero es una buena práctica usarla con una interfaz funcional para evitar errores accidentales. añadiendo otro método. El código de ejemplo es el siguiente.

/** 
* Si una interfaz tiene uno y solo un método abstracto, esta interfaz se denomina interfaz funcional/interfaz funcional (interfaz funcional) 
* En Java8 y versiones posteriores, las interfaces funcionales pueden usar expresiones Lambda en lugar de expresiones de clase anónimas En otras palabras , las interfaces funcionales se pueden implementar mediante funciones anónimas en lugar de clases internas anónimas. 
*/ 
@FunctionalInterface 
interfaz pública IHelloInner { 

    void sayHello(); 

}

Use la anotación @FunctionalInterface para decorar antes de la interfaz. Al intentar agregar un método abstracto, se producirá un error de compilación:

El tipo de destino de esta expresión debe ser una interfaz funcional

Sin embargo, las interfaces funcionales pueden agregar métodos predeterminados y métodos estáticos.

/** 
 * Si una interfaz tiene uno y solo un método abstracto, esta interfaz se denomina interfaz funcional/interfaz funcional (interfaz funcional) 
 * En Java8 y versiones posteriores, las interfaces funcionales pueden usar expresiones Lambda en lugar de expresiones de clase anónimas En otras palabras , las interfaces funcionales se pueden implementar mediante funciones anónimas en lugar de clases internas anónimas. 
 * Se puede agregar cualquier cantidad de métodos predeterminados y métodos estáticos 
 */ 
@FunctionalInterface 
public interface IHelloInner { 

    /** 
     * La interfaz funcional/la interfaz funcional puede agregar métodos estáticos 
     */ 
    static void staticFunction() { 

    } 

    static void staticFunction2() { 

    } 

    / * * 
     * Interfaz de función/interfaz de función puede método predeterminado 
     */ 
    default void defaultFunction() { 

    } 

        default void defaultFunction2() { 

        } 

        /** 
         * método abstracto 
         */ 
        void sayHello();

    }

Piénselo, ¿por qué las interfaces no funcionales no pueden admitir expresiones Lambda?

Nota: En Java, hay dos tipos de clases internas, clases locales (clases locales) y clases anónimas. Por lo tanto, una clase anónima debe ser una clase interna, por lo que el término clase interna anónima se refiere a una clase que es una clase interna anónima . Por lo tanto, las clases anónimas y las clases internas anónimas significan lo mismo, no te preocupes por eso. La clase local es una clase interna definida en el cuerpo del método.Si la clase local no tiene nombre, también es una clase anónima. Pero a su vez, una clase anónima no es necesariamente una clase local, porque no está necesariamente en el cuerpo del método.

Hay dos tipos especiales de clases internas: clases locales y clases anónimas .

Fuente: https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

Hay dos tipos adicionales de clases internas. Puede declarar una clase interna dentro del cuerpo de un método. Estas clases se conocen como clases locales . También puede declarar una clase interna dentro del cuerpo de un método sin nombrar la clase. Estas clases se conocen como clases anónimas .

Fuente: https://docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html

Las interfaces funcionales son uno de los conceptos más importantes de Java 8, ya que impulsan las expresiones Lambda, pero muchos desarrolladores se esfuerzan mucho por entenderlo sin comprender primero qué hacen las interfaces funcionales en Java 8, y dedican tiempo a aprender las expresiones Lambda Formula y Stream API. A menos que sepa qué es una interfaz funcional y cómo se relacionan Lambdas con ella, no podrá utilizar las potentes funciones de Java 8, como Lambda Expressions y Streams API.

Sin el conocimiento de la interfaz funcional , puede que no sea posible entender en qué parte del código puede usar lambda y será difícil escribir las expresiones lambda deseadas, por lo que es muy importante tener una buena comprensión de la interfaz funcional en Java 8. Podemos ver que las interfaces funcionales de Java 8 y las expresiones Lambda nos ayudan a escribir código más pequeño y conciso al eliminar una gran cantidad de código repetitivo.

private void hello() { 
    //Clase interna anónima 
	IHello hello = new IHello() { 
            @Override 
            public void sayHello() { 
                System.out.print("Hello " + text); 
            } 
            
        }; 
    //Función anónima (expresión lambda fórmula) 
    IHola hola = () -> System.out.print("Hola " + texto); 

}

Tenga en cuenta que para acceder a variables de miembros de clases externas, las funciones anónimas (expresiones Lambda) no son diferentes de las funciones ordinarias, pero al acceder a las variables locales dentro de una función, las variables locales deben ser de tipo final (no modificables).

Diferencia entre clase anónima y función anónima.

clase anónima

Funciones anónimas (expresiones lambda)

clase interna sin nombre

funcion sin nombre

Puede implementar una interfaz que contenga cualquier método abstracto.

Puede implementar una interfaz que contenga un solo método abstracto (interfaz funcional)

Puede extender clases abstractas o concretas.

No puede extender clases abstractas o concretas.

Las clases anónimas pueden tener variables de instancia y variables locales de método

Las funciones anónimas solo pueden tener variables locales.

Se pueden instanciar clases anónimas

función anónima

Dentro de una clase interna anónima, "esto" siempre se refiere al objeto de clase anónimo actual, no al objeto externo

Dentro de una función anónima, "esto" siempre se refiere al objeto de clase externo actual, el objeto OuterClass

Esta es la mejor opción si estamos tratando con múltiples métodos.

Esta es la mejor opción si se trata de interfaces

En tiempo de compilación, se generará un archivo .class separado

En tiempo de compilación, no se generan archivos .class separados. simplemente cámbielo como un método privado de la clase externa

La asignación de memoria es bajo demanda cada vez que creamos un objeto

Reside en la memoria permanente de la JVM

Veamos el código de bytes compilado de clases anónimas y funciones anónimas:

public class HelloTest { 

    private String text = "world"; 

    interfaz pública IHello { 

        void sayHello(); 

    } 

    private void hola() { 

        IHello helloAnonymousClass = new IHello() { 

            @Override 
            public void sayHello() { 
                System.out.print("Hola holaAnonymousClass " + texto); 
            } 

        }; 

        IHola holaFunciónAnónima = () -> System.out.print("Hola holaFunciónAnónima" + texto); 


    } 

    public static void main(String[] args) { 

    } 
}

Código de bytes compilado:

// versión de clase 52.0 (52) 
// indicadores de acceso 0x21 
public class com/nxg/app/HelloTest { 

  // indicadores de acceso 0x609 
  public static abstract INNERCLASS com/nxg/app/HelloTest$IHello com/nxg/app/HelloTest IHello 
  / / access flags 0x0 
  INNERCLASS com/nxg/app/HelloTest$1 null null 
  // access flags 0x19 
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup 

  // access flags 0x2 
  private Ljava/lang /Cadena; texto 

  // banderas de acceso 0x1 
  public <init>()V 
    ALOAD 0 
    INVOKESPECIAL java/lang/Object.<init> ()V 
    ALOAD 0 
    LDC " mundo" 
    PUTFIELD com/nxg/app/HelloTest.text : Ljava/lang/String;
    RETURN 
    MAXSTACK = 2 
    MAXLOCALS = 1 

  // banderas de acceso 0x2 
  privado hola()V 
    NUEVO com/nxg/app/HelloTest$1 
    DUP 
    ALOAD 0 
    INVOKESPECIAL com/nxg/app/HelloTest$1.<init> (Lcom/nxg/app/HelloTest ;)V 
    ASTORE 1 
    ALOAD 0 
    INVOKEDYNAMIC sayHello(Lcom/nxg/app/HelloTest;)Lcom/nxg/app/HelloTest$IHello; [ 
      // tipo de manejo 0x6 : INVOKESTATIC 
      java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle ;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; 
      // argumentos: 
      ()V,
      // tipo de manejo 0x7: INVOKESPECIAL 
      com/nxg/app/HelloTest.lambda$hello$0()V, ( 
      )V 
    ] 
    ASTORE 2 
    RETURN 
    MAXSTACK = 3 
    MAXLOCALS = 3 

  // indicadores de acceso 0x9 
  public static main([Ljava/lang/ String;)V 
    RETURN 
    MAXSTACK = 0 
    MAXLOCALS = 1 

  // banderas de acceso 0x1002 
    LDC "Hola holaAnonymousFunction" 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
  lambda sintética privada $ hola $ 0 () V
    GETSTATIC java/lang/System.out: Ljava/io/PrintStream; 
    NUEVO java/lang/StringBuilder 
    DUP 
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    A CARGAR 0
    ALOAD 0 
    GETFIELD com/nxg/app/HelloTest.text : Ljava/lang/String; 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; 
    INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V 
    RETURN 
    MAXSTACK = 3 
    MAXLOCALS = 1 

  // banderas de acceso 0x1008 
  acceso sintético estático$000(Lcom/nxg/app/HelloTest;)Ljava/lang/String; 
    GETFIELD com/nxg/app/HelloTest.text: Ljava/lang/String; 
    ARETURN 
    MAXSTACK = 1 
    MAXLOCALS = 1 
}

En la función hola, use una clase anónima y finalmente cree un objeto de la clase anónima.

NUEVO com/nxg/app/HelloTest$1 
DUP 
ALOAD 0 
INVOKESPECIAL com/nxg/app/HelloTest$1.<init> (Lcom/nxg/app/HelloTest;)V 
ASTORE 1

Con las expresiones Lambda, se genera una función llamada lambda$hello$0(), y una función anónima es, de hecho, una función en esencia.

INVOKEDYNAMIC sayHello(Lcom/nxg/app/HelloTest;)Lcom/nxg/app/HelloTest$IHello; [ 
    // manejar tipo 0x6 : INVOKESTATIC 
    java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; 
    // argumentos: 
    ()V, 
    // maneja el tipo 0x7: INVOKESPECIAL 
    com/nxg/app/HelloTest.lambda$hello$0()V, 
    ()V 
] 
ASTORE 2

Las clases anónimas corresponden a clases y objetos, y las funciones anónimas corresponden a funciones.La diferencia esencial entre las dos es al menos: la diferencia entre clases, objetos y funciones.

que es un cierre

Es realmente difícil tener una respuesta precisa, autorizada y confiable.

Closure se traduce de la palabra inglesa closure, que es una palabra que no es fácil de traducir. En el campo informático, tiene tres significados completamente diferentes: en el principio de compilación, es un paso en el procesamiento de producciones gramaticales; en geometría computacional , es Representa un polígono convexo (traducido como un casco convexo) que encierra un conjunto plano de puntos; mientras que en el campo de los lenguajes de programación, representa una función.

El concepto de cierre apareció por primera vez en "The Computer Journal" en 1964. PJ Landin propuso los conceptos de expresión aplicativa y cierre en el artículo "La evaluación mecánica de las expresiones".

En la década de 1960, el lenguaje de programación principal era un lenguaje de programación funcional basado en el cálculo lambda, por lo que esta definición de cierre inicial usaba muchos términos funcionales. Una descripción menos precisa es "una expresión lambda con una secuencia de información". En los lenguajes funcionales, las expresiones lambda son funciones. Simplemente podemos entender que un cierre es en realidad solo una función vinculada a un entorno de ejecución. Esta función no es una simple expresión impresa en un libro. La diferencia entre un cierre y una función ordinaria es que lleva el entorno de ejecución, al igual que las personas. necesidad de traer su propio equipo de absorción de oxígeno en los extranjeros, esta función también tiene un ambiente para vivir en el programa. En esta definición clásica de cierre, un cierre consta de dos partes. parte de entorno y parte de expresión.

El contenido anterior proviene de: la columna de front-end de reaprendizaje de Cheng Shaofei (invierno)

Nota: Según la combinación de Cheng Shaofei (invierno), combinada con otra información recopilada por el autor. Se puede determinar que el concepto de clausura en los lenguajes de programación es diferente al que existe en el campo de las matemáticas. Para obtener más información, consulte este artículo "[Cierres] ¿Realmente entiende los cierres y las expresiones lambda" y la pregunta y respuesta en stackoverflow ¿ Cuál es la diferencia entre un 'cierre' y un 'lambda'?

Hay muchas personas diferentes que han definido cierres, y aquí están las listas para que todos las vean:

  • es una función que hace referencia a una variable libre . Esta función generalmente se define en otra función externa y se refiere a una variable en la función externa. -- < <wikipedia> >
  • es un objeto invocable que registra cierta información del ámbito en el que se creó . -- <<Ideas de programación Java>>
  • Es un bloque de código anónimo que puede aceptar parámetros y devolver un valor de retorno, y también puede referirse y usar variables definidas en el dominio visible que lo rodea . -- Groovy ['ɡru:vi]
  • es una expresión que tiene variables libres y el contexto en el que se vinculan esas variables .
  • Los cierres le permiten encapsular algún comportamiento , pasarlo como un objeto , y todavía tiene acceso al contexto original cuando fue declarado por primera vez .
  • Una expresión (generalmente una función) que tiene variables y un entorno vinculado a esas variables , de modo que esas variables también forman parte de la expresión.
  • Los cierres son bloques de código que pueden contener variables libres (sin vincular) ; estas variables no están definidas en este bloque de código ni en ningún contexto global, sino en el entorno en el que se define el bloque de código.

El contenido anterior está extraído de:

Cierre JAVA - chenjunbiao - 博客园

Con tantas definiciones, es realmente justo decir que el público tiene razón, y la suegra tiene razón, ¡y es demasiado abstracto para entenderlo! Sin embargo, hay palabras clave en cada una de estas definiciones: variable, función, contexto, etc. Los cierres tienen aplicaciones importantes en funciones de devolución de llamada, programación funcional y expresiones Lambda, y los cierres existen en muchos lenguajes populares, como JavaScript, C++ y C#. Para evitar repetir las opiniones de otros, adhiriéndose al concepto de que el oficial es justicia, echemos un vistazo al documento histórico de JDK8 (JDK8 publicado por Oracle el 18/03/2014 agregó soporte para expresiones Lambda):

Nos centramos en las novedades del Artículo 126: 126 Expresiones lambda y métodos de extensión virtual , haga clic en el enlace para ver:

resumir

Agrega expresiones lambda (cierres) y características de soporte al lenguaje de programación y plataforma Java, incluidas referencias de métodos, inferencia de tipo mejorada y métodos de extensión virtual.

No hay una descripción especial.De acuerdo con la documentación oficial de JDK, parece que las expresiones Lambda son equivalentes a los cierres .

Buscando otro contenido en el documento, encontré: Cierres para el lenguaje de programación Java (BGGA) , así que haga clic y eche un vistazo.

¿Míralo? En el lenguaje de programación Java, lo siguiente es un cierre, por lo que ahora no es abstracto (Xiemeixiaogoutou.jpg).

Es broma, ¡no te lo tomes en serio! Tenga en cuenta que hay un enlace: Especificación de cierres BGGA: Cierres para el lenguaje de programación Java (v0.5) , el extracto es el siguiente:

Cierres para el Lenguaje de Programación Java (v0.5)

Gilad Bracha, Neal Gafter, James Gosling, Peter von der Ahé

Los lenguajes de programación modernos proporcionan una mezcla de primitivas para componer programas. En particular, Scheme, Smaltalk, Ruby y Scala tienen soporte de lenguaje directo para bloques de código de ejecución retrasada parametrizados, llamados lambda , funciones anónimas o cierres .. Estos proporcionan una forma natural de expresar algunos tipos de abstracciones que actualmente son bastante difíciles de expresar en Java. Para la programación en funciones pequeñas y anónimas, se puede abstraer un algoritmo sobre un fragmento de código; es decir, permiten extraer más fácilmente las partes comunes de dos piezas de código casi idénticas. Para la programación en grandes funciones anónimas, las API admiten funciones que expresan un algoritmo abstraído sobre algún aspecto computacional del algoritmo. Por ejemplo, permiten escribir métodos que actúan como construcciones de control definidas por la biblioteca .

Cierres para el Lenguaje de Programación Java (v0.5)

Gerald Bracha, Neil Garft, James Gosling, Peter von der Acher

Los lenguajes de programación modernos proporcionan primitivas mixtas para escribir programas. En particular, Scheme, Smalltalk, Ruby y Scala tienen soporte de lenguaje directo para bloques parametrizados de código de ejecución diferida conocidos como lambdas , funciones anónimas o cierres , que proporcionan una forma natural de expresar ciertas abstracciones que actualmente son difíciles de expresar en Java. La programación con pequeñas funciones anónimas permite abstraer un algoritmo sobre una pieza de código; es decir, permiten extraer más fácilmente las partes comunes de dos piezas de código casi idénticas. Para la programación a gran escala, las funciones anónimas admiten API que expresan algoritmos que abstraen algunos aspectos computacionales del algoritmo. Por ejemplo, permiten escribir métodos que actúan como estructuras de control definidas por la biblioteca .

Bueno, tal vez necesite una conclusión (tranquilidad), en el lenguaje de programación Java:

Las expresiones lambda son funciones y cierres anónimos

Se ve así, es un bloque de código (bloques de código) :

Tenga en cuenta que esta conclusión no la hace el autor, se compila a partir de la documentación oficial de Java. Si siente que esta conclusión no es consistente con lo que tiene en mente, es normal. Porque cuando el concepto de cierre se aplica del campo de datos al campo de programación, el concepto de cierre ha cambiado debido a la transmisión de persona a persona. Como programador de Android, sigue la documentación oficial de Java y prepárate. Por supuesto, si tiene mejor información, es realmente deseable, bienvenido a comunicarse.

¿Todavía no entiendes? De hecho, es difícil de entender, porque incluso el oficial de Java no dio una definición precisa (si la hay, corríjame).Después de todo, Java no admitía expresiones Lambda al principio, y se agregó más tarde tomando prestada la programación funcional. ideas de otros idiomas.

¿Has notado? El autor no usó el signo igual (=) para indicar la relación entre expresiones lambda, funciones anónimas ( Anonymous Functions ) y cierres (Closures) , sino que usó un "sí" , ¿por qué? Porque tienen diferentes funciones (ángulos de pensamiento).

Permítanme explicar con un ejemplo posiblemente inapropiado:

Eres un programador, el amante de tu esposa y el padre de tu hijo.

Aquí es incorrecto usar la expresión programador = amante = padre, porque programadores, amantes y padres no son lo mismo, aunque son una de tus identidades, se expresan desde diferentes ángulos (

Es como tener una explicación muy interesante: la diferencia entre una clase abstracta y una interfaz, lo que representa una clase abstracta y lo que puede hacer una interfaz.

De manera similar , las expresiones lambda, las funciones anónimas (Anonymous Functions ) y los cierres (Closures) también son productos (nombres) que se establecen desde diferentes perspectivas y se precipitan para diferentes funciones.

El concepto de cierre no se ha definido claramente, y todos pueden tener su propio entendimiento en sus corazones. Si solo observa la semántica del cierre en sí, parece que tiene el significado de cerrar y encerrar, y la palabra inglesa correspondiente closures también significa cerrar. Entonces tenemos que pensar en lo que está cerrado (rodeado)? ¿Por qué cerrar (envolver)?

En primer lugar, un cierre es una función anónima y una función es un bloque de código independiente definido en una clase para lograr una determinada función. Pero no tratamos las funciones ordinarias como cierres, lo que demuestra que existen otras restricciones. Tratamos de enumerar estas condiciones:

  • Condición 1: debe cumplir con la sintaxis de expresión de Lambda
  • Condición 2: debe ser una función anónima
  • Condición 3: debe ser una implementación de una interfaz funcional
  • Condición 3: debe ser posible hacer referencia a las variables miembro de la clase externa y las variables locales en el cuerpo de la función donde se encuentra
  • Condición 4: Debe ser posible inferir el tipo del parámetro y el tipo del valor devuelto según el contexto (entorno)

En Java, estas condiciones son indispensables.En resumen, el autor entiende que: en Java, las expresiones Lambda se utilizan para representar los cierres , que existen en el código en forma de funciones anónimas .

¿Qué es exactamente cerrado (encerrado)?

El cierre cierra (encierra) las variables externas a las que se hace referencia en la expresión Lambda (es decir, las variables no declaradas por la propia expresión Lambda o llamadas variables libres).

¿Por qué cerrar (envolver)?

Porque solo al cerrar (rodear) estas variables externas, la expresión Lambda puede evitar que las variables externas se lean incorrectamente debido a la falla del alcance del entorno externo. Las expresiones lambda no se ejecutarán correctamente si no sabemos de dónde proviene la variable externa y qué es. El cierre (entorno) tiene como objetivo hacer que la expresión Lambda y las variables externas a las que se refiere (o variables libres, que provienen del contexto, es decir, el entorno de ejecución donde se encuentra la Lambda), formen un todo cerrado completo. .

Entonces, ¿qué son los cierres? ¿ Los cierres son funciones anónimas vinculadas al contexto (entorno de ejecución) utilizando la sintaxis de expresión Lambda? El autor no está seguro, solo eche un vistazo, lo más importante es tener su propio pensamiento.

Por supuesto, para evitar copiar lo que otros dicen, el autor decidió profundizar en la comprensión de las expresiones Lambda, las funciones anónimas y los cierres mediante el uso de expresiones Lambda. ¡Exploremos juntos!

expresiones lambda

Debido a que el autor está estudiando las expresiones Lambda en Java 8, primero echemos un vistazo a los documentos oficiales.

Java8 ha agregado muchas características nuevas. Las características que pueden estar involucradas en este artículo son principalmente las siguientes:

  • Expresiones lambda : le permiten tratar la funcionalidad como parámetros de método o codificar como datos. Las expresiones lambda le permiten expresar instancias de interfaces de método único (llamadas interfaces funcionales) de manera más compacta.
  • Referencia de método : la referencia de método proporciona una sintaxis muy útil para referirse directamente a un método o constructor de una clase u objeto Java existente (instancia). En combinación con lambda, la referencia de método puede hacer que la estructura del lenguaje sea más compacta y concisa, lo que reduce el código redundante.
  • Método predeterminado : un método predeterminado es un método que tiene una implementación en una interfaz.
  • Nuevas herramientas : nuevas herramientas de compilación, como: motor Nashorn jjs, analizador de dependencia de clases jdeps.
  • Stream API : la API Stream recientemente agregada (java.util.stream) brinda un verdadero estilo de programación funcional a Java.
  • API de fecha y hora : manejo mejorado de fechas y horas.
  • Clase opcional : la clase opcional ha sido parte de la biblioteca de clases de Java 8 para manejar excepciones de puntero nulo.
  • Nashorn, motor JavaScript : Java 8 proporciona un nuevo motor Nashorn javascript que nos permite ejecutar aplicaciones javascript específicas en JVM.

Para obtener más funciones nuevas, consulte el sitio web oficial: Novedades de JDK 8

Expresiones lambdas. que te recuerda Expresiones regulares, correcto. Las expresiones regulares generalmente se usan para recuperar y reemplazar texto que coincide con un determinado patrón (regla). ¿ Cuál es el papel de la expresión Lambda ? Ya hay respuestas antes, que se utilizan para representar cierres . Verá, una expresión Lambda es esencialmente una expresión.

En Java, una expresión lambda es una expresión que representa una instancia de una interfaz funcional ( Interfaz funcional ) .

Al igual que otros tipos en Java, las expresiones Lambda también se escriben y su tipo es un tipo de interfaz funcional . Para inferir el tipo, el compilador mira el lado izquierdo de la asignación en la expresión lambda .

Es importante tener en cuenta que una expresión Lambda en sí misma no contiene información sobre la interfaz funcional que está implementando. Esta información se deduce del contexto en el que se utiliza la expresión.

Una expresión es una combinación de números, operadores, símbolos de agrupación de números (paréntesis), variables libres y variables de restricción, etc., obtenida mediante un método de arreglo significativo que puede obtener un valor. A las variables restringidas se les ha asignado un valor en la expresión, mientras que a las variables libres se les puede asignar un valor fuera de la expresión.

Primer vistazo a las expresiones lambda

Como dice el refrán: ¿Nunca has comido cerdo ni has visto correr a un cerdo? Si es una mula o un caballo que sale a pasear, lo sabrás, ¿verdad? Echa un vistazo al código a continuación:

Sin usar expresiones lambda:

button.addActionListener(new ActionListener(){ 
    public void actionPerformed(ActionEvent actionEvent){ 
        System.out.println("Acción detectada"); 
    } 
});

Utilice expresiones lambda:

button.addActionListener( actionEvent -> { 
    System.out.println("Acción detectada"); 
});

Un ejemplo más obvio.

Sin usar expresiones lambda:

Runnable runnable = new Runnable(){ 
    @Override 
    public void run(){ 
        System.out.println("Ejecutando sin Lambda"); 
    } 
};

Utilice expresiones lambda:

Ejecutable ejecutable = ()->System.out.println("Ejecutando desde Lambda");

Se puede ver que las clases internas anónimas se reemplazan por expresiones Lambda. En comparación, se encuentra que después de usar expresiones Lambda, no solo el código se vuelve más simple, sino que también se reduce mucho la cantidad de código, pero se mejora la legibilidad. El autor Tómalo con pinzas (aunque, al menos, la jerarquía del código es más agradable).

En el primer ejemplo, la expresión Lamba se pasa al método addActionListener como parámetro; en el segundo ejemplo, la expresión Lamba se usa como una función (expresión). Entonces, ¿cree que la legibilidad del código mejora después de usar expresiones Lambda?

De hecho, si usa la clase anónima anterior, el IDE avanzado definitivamente le pedirá que reemplace la expresión Lambda.Piénselo, ¿por qué? Veamos cómo lo solicita el IDE:

El nuevo IHello() anónimo se puede reemplazar con lambda

Información de inspección: informa todas las clases anónimas que se pueden reemplazar con expresiones lambda.

Tenga en cuenta que si una clase anónima se convierte en una lambda sin estado, el tiempo de ejecución de Java puede reutilizar el mismo objeto lambda durante las invocaciones posteriores. Por otro lado, cuando se usa una clase anónima, cada vez se crean objetos separados. Por lo tanto, la aplicación de la solución rápida puede provocar el cambio de semántica en casos excepcionales, por ejemplo, cuando se utilizan instancias de clases anónimas como claves HashMap.

La sintaxis de Lambda no es compatible con Java 1.7 o JVM anteriores.

El nuevo IHello() anónimo puede ser reemplazado por lambda

Información de inspección: informa todas las clases anónimas que se pueden reemplazar con expresiones lambda.

Tenga en cuenta que si convierte una clase anónima en una lambda sin estado, el tiempo de ejecución de Java puede reutilizar el mismo objeto lambda durante invocaciones posteriores. Por otro lado, cuando se usan clases anónimas, se crean objetos separados cada vez. Por lo tanto, en casos excepcionales, la aplicación de una solución rápida puede generar un cambio en la semántica, como cuando se usan instancias de clases anónimas como claves HashMap.

Java 1.7 o JVM anteriores no admiten la sintaxis de Lambda.

Modificar el código de verificación de HelloTest antes:

privado void hola() { 

    IHello helloAnonymousClass = new IHello() { 

        @Override 
        public void sayHello() { 
            System.out.print(this + " Hello helloAnonymousClass " + text + "\n"); 
        } 

    }; 

    IHola holaFunciónAnónima = () -> { 
        System.out.print(this + " Hola holaFunciónAnónima " + texto + "\n"); 
    }; 

    holaClaseAnonima.sayHola(); 
    holaFunciónAnónima.sayHola(); 


}

El resultado de la operación es el siguiente:

com.nxg.app.HelloTest$1@34ce8af7 Hola, hola, mundo de AnonymousClass 
com.nxg.app.HelloTest@b684286 Hola, hola, mundo de AnonymousFunction 
com.nxg.app.HelloTest$1@880ec60 Hola, hola, mundo de AnonymousClass 
com.nxg.app.HelloTest@b684286 Hola, hola, mundo de AnonymousFunction

Se puede encontrar que cada vez que se llama a la función sayHello, una instancia de objeto diferente de la clase anónima HelloTest$1 llama a helloAnonymousClass. La función helloAnonymousFunction es llamada por la clase externa (clase cerrada). La causa raíz es como se mencionó anteriormente en el análisis del código de bytes compilado de HelloTest, por lo que no entraré en detalles aquí.

Sintaxis de expresiones lambda

Las expresiones lambda se parecen mucho a las declaraciones de funciones; de hecho, las expresiones lambda son funciones anónimas sin nombre.

Una sintaxis de expresión típica de Lambda se ve así:

(x, y) -> x + y //Esta función toma dos parámetros y devuelve su suma.

Como puede ver, los tipos de parámetros de x e y no están declarados, por lo que esta expresión Lambda se puede usar en varios lugares y los parámetros pueden coincidir con int, Integer o String simple. Dependiendo del contexto, agregará dos números enteros o concatenará dos cadenas.

Por ejemplo:

@FunctionalInterface 
interfaz Operador<T> { 
  T proceso(T a, T b); 
} 

Operador<Entero> addOperation = (a, b) -> a + b; 
System.out.println(addOperation.process(3, 3)); //Imprime 6 

Operator<String> appendOperation = (a, b) -> a + b; 
System.out.println(appendOperation.process("3", "3")); //Imprime 33 

Operador<Entero> multiplicarOperacion = (a, b) -> a * b; 
System.out.println(multiplyOperation.process(3, 3)); //Imprime 9

Otras sintaxis para expresiones lambda son:

ya sea 
 
(parámetros) -> expresión //1 
 
o 
 
(parámetros) -> { sentencias; } //2 
 
o 
 
() -> expresión //3

Las siguientes son las características importantes de las expresiones lambda:

  • Declaración de tipo opcional: no es necesario declarar el tipo de parámetro y el compilador puede identificar uniformemente el valor del parámetro.
  • Paréntesis de parámetros opcionales: un parámetro no necesita definir paréntesis, pero varios parámetros deben definir paréntesis.
  • Llaves opcionales: si el cuerpo contiene una declaración, no se requieren llaves.
  • Palabra clave de retorno opcional: si el cuerpo tiene solo un valor de retorno de expresión, el compilador devolverá automáticamente el valor, las llaves deben especificar que la expresión devuelve un valor.

Ejemplo de expresión lambda

Ejemplo simple de expresión Lambda:

// 1. No se requieren parámetros, el valor devuelto es 5   
() -> 5   
  
// 2. Recibe un parámetro (tipo de número) y devuelve su valor doble   
x -> 2 * x   
  
// 3. Acepta 2 parámetros ( número) y devuelve su diferencia   
(x, y) -> x – y   
  
// 4. Recibe 2 enteros int, devuelve su suma   
(int x, int y) -> x + y   
  
// 5. Acepta un objeto de cadena y lo imprime en la consola sin devolver ningún valor (parece que devuelve void)   
(String s) -> System.out.print(s)

Veamos cómo funciona en la práctica:

public class Java8Tester { 
    
    
   interface MathOperation { 
      int operation(int a, int b); 
   } 
    
   interface GreetingService { 
      void sayMessage(String message); 
   } 
    
   private int operar(int a, int b, MathOperation mathOperation){ 
      return mathOperation.operation(a, b); 
   } 
   
   public static void main(String args[]){ 
      Java8Tester tester = new Java8Tester(); 
        
      // declaración de tipo 
      suma de MathOperation = (int a, int b) -> a + b; 
        
      // sin declaración de tipo 
      resta de MathOperation = (a, b) -> a - b; 
        
      // declaración de retorno entre llaves 
      MathOperation multiplicación = (int a, int b) -> { return a * b; };
         
      // sin llaves y declaración de retorno 
      división MathOperation = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, adición)); 
      System.out.println("10 - 5 = " + tester.operate(10, 5, resta)); 
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplicación)); 
      System.out.println("10 / 5 = " + tester.operate(10, 5, division)); 
        
      // 不用括号
      GreetingService greetingService1 = mensaje -> 
      System.out.println("Hola " + mensaje); 
        
      // 用括号
      GreetingService greetingService2 = (mensaje) -> 
      System.out.println("Hola " + mensaje); 
         
      saludoServicio1.decirMensaje("Runoob");
      saludarServicio2.decirMensaje("Google"); 
   } 
}

Características de las expresiones lambda

Los parámetros de expresión lambda pueden tener cero, uno o más.

(x, y) -> x + y 
(x, y, z) -> x + y + z

El cuerpo de una expresión lambda puede contener cero, una o más declaraciones.

Si el cuerpo de la expresión Lambda tiene solo una declaración, las llaves no son obligatorias y el tipo de retorno de la expresión Lambda es el mismo que el de la expresión del cuerpo.

Cuando hay varias declaraciones en el cuerpo, esas declaraciones deben encerrarse entre llaves.

(parámetros) -> { sentencias; }

El tipo del parámetro puede declararse explícitamente o inferirse del contexto. Los parámetros múltiples deben estar encerrados entre paréntesis y separados por comas. Los paréntesis vacíos se utilizan para indicar parámetros vacíos.

() -> expresión

Cuando hay un solo parámetro, los paréntesis no se aplican si se puede inferir su tipo.

a -> devuelve a * a;

Las expresiones lambda no pueden tener una cláusula throws y el tipo de parámetro se deduce del contexto en el que se usa el parámetro y el cuerpo en el que se encuentra el parámetro.

Las expresiones lambda no pueden ser genéricas, es decir, no pueden declarar parámetros genéricos.

Ámbito variable para expresiones lambda

Las variables locales a las que acceden las clases locales (clases locales) y las clases anónimas en Java deben ser modificadas por final para garantizar la coherencia de los datos entre las clases internas y las clases externas. Pero a partir de Java 8, podemos agregar el modificador final sin agregar el modificador final, que es agregado por defecto por el sistema, por supuesto, esto no está permitido en versiones anteriores a Java 8. Java se refiere a esta característica como una característica final efectiva. Dado que la expresión Lambda es una función anónima, su ámbito variable es coherente con el de la clase anónima.

Lectura adicional: Java Final y Efectivamente Final

Es posible que haya oído hablar del concepto de variables libres , pero en Java, el autor no encontró una introducción relevante, y es probable que sea un concepto único en otros lenguajes o campos de programación. Pero esto último no impide que entendamos las variables libres de la misma manera .

En Java, variables miembro:

  • Variables miembro de una clase: se denominan campos.
  • Variables dentro de métodos o bloques de código: se denominan variables locales.
  • Variables en las declaraciones de métodos: se denominan parámetros.

Se puede considerar que mientras la variable no esté declarada en la expresión Lambda, es una variable libre para la expresión Lambda .

No se permite un parámetro o variable local con el mismo nombre que una variable local en una expresión Lambda

Alcance variable

La diferencia entre las variables miembro y las variables locales:

Variables miembro:

1. Las variables miembro se definen en la clase y se puede acceder a ellas en toda la clase.

2. Las variables miembro se establecen con el establecimiento del objeto, desaparecen con la desaparición del objeto y existen en la memoria del montón donde se encuentra el objeto.

3. Las variables miembro tienen valores de inicialización predeterminados.

variable local:

1. Las variables locales solo se definen en el ámbito local, como: en funciones, declaraciones, etc., y solo son válidas en el área a la que pertenecen.

2. Las variables locales existen en la memoria de la pila, y el alcance de la acción finaliza, y el espacio variable se liberará automáticamente.

3. No hay un valor de inicialización predeterminado para las variables locales

Los principios que se deben seguir al usar variables son: el principio de proximidad, primero búsquelo en el ámbito local y utilícelo si lo hay, luego búsquelo en la posición del miembro.

clase local (clase local)

Clase anónima (clase interna anónima)

Expresiones lambda (funciones/cierres anónimos)

Variables miembro

¿Es accesible (sí)

Si se puede modificar (lata no final)

¿Es accesible (sí)

Si se puede modificar (lata no final)

¿Es accesible (sí)

Si se puede modificar (lata no final)

variable local

¿Es accesible (sí)

¿Es posible modificar (no)

¿Es accesible (sí)

¿Es posible modificar (no)

¿Es accesible (sí)

¿Es posible modificar (no)

Pero convertirlo en una clase atómica para implementar

Para las variables locales, las anteriores a Java 8 deben ser modificadas por final para ser utilizadas por las clases locales (clases locales),

Acceso a clase anónima (clase interna anónima), expresión Lambda (función/cierre anónimo); de lo contrario, la compilación y la compilación informarán un error.

¿Por qué las clases locales y las clases anónimas pueden referirse a variables miembro de clases externas? Debido a que la clase interna contiene una referencia a la clase externa, e incluso si la variable se declara como privada, también se puede hacer referencia a ella. Por ejemplo:

/** 
 * Clase externa (clase cerrada) 
 */ 
public class OuterClass { 
    //variable miembro privada 
    private String privateText = "privateText"; 
    //variable estática 
    privada private static String privateStaticText = "privateStaticText"; 
    //variable estática pública 
    public static String publicStaticText = "publicStaticText"; 

    /** 
     * Si una interfaz tiene uno y solo un método abstracto, esta interfaz se denomina interfaz funcional/interfaz funcional (interfaz funcional) 
     * En Java8 y versiones posteriores, las interfaces de función pueden usar expresiones Lambda en lugar de expresiones de clase anónimas, es decir, se pueden implementar interfaces funcionales utilizando funciones anónimas en lugar de clases internas anónimas. 
     * Se puede agregar cualquier cantidad de métodos predeterminados y estáticos 
     */ 
    @FunctionalInterface 
    public interface IHelloInner { 

        /** 
         * Método abstracto 
         */ 
        void sayHello();

    } 

    /** 
     * Función miembro 
     * 
     * @param iHelloOuter variable local 
     */ 
    private void hello(IHelloOuter iHelloOuter) { 

        String localText = "localText "; 

        /** 
         * Clase local (clase local) 
         */ 
        class LocalClass implementa IHelloInner { 

            @Override 
            public void sayHello() { 
                //Puedes usar las propiedades de la clase externa 
                System.out.print("Hello LocalClass " + privateText); 
                System.out.print("Hello LocalClass " + privateStaticText); 
                System.out.print ("Hola LocalClass" + publicStaticText);
                System.out.print("Hola LocalClass" + localText); 
            } 
        } 

    } 
} 
//编译后的LocalClass持有OuterClass的引用
class OuterClass$1LocalClass implementa IHelloInner { 
    OuterClass$1LocalClass(OuterClass var1, String var2) { 
        this.this$0 = var1; 
        this.val$textolocal = var2; 
    } 

    public void sayHello() { 
        System.out.print("Hola LocalClass " + OuterClass.access$000(this.this$0)); 
        System.out.print("Hola LocalClass " + OuterClass.access$100()); 
        System.out.print("Hola LocalClass " + OuterClass.publicStaticText); 
        System.out.print("Hola LocalClass" + this.
    } 
}

Sin mencionar la expresión Lambda, que es la función miembro de la clase externa, se presentó cuando expliqué el cierre anteriormente, por lo que no entraré en detalles.

Entonces, si desea modificar las variables locales, ¿puede hacerlo? La respuesta es no. Si es necesario, se sugiere oficialmente que la variable final Efectivamente se cambie a una clase Atómica para su implementación.

vida útil variable

El alcance de una variable se refiere al rango en el que existe la variable, y solo dentro de este rango puede acceder el código del programa. Cuando se define una variable, se determina su alcance. El alcance de una variable determina el ciclo de vida de la variable, lo que indica que el ciclo de vida es diferente si el alcance es diferente.

El ciclo de vida de una variable se refiere al proceso desde que se crea una variable y se asigna espacio de memoria hasta que se destruye la variable y se borra el espacio de memoria que ocupa.

La imagen de arriba proviene de: Descripción general de las variables miembro del método Java y las variables locales

Se puede ver que las variables locales se liberan después de que la función devuelve el resultado. Pero si las expresiones Lambda utilizan variables locales, esto sin duda prolongará la vida útil de las variables locales. En realidad, esto no es un problema, al igual que la recursividad, la función llama a la función.

Referencias de métodos para expresiones lambda

Referencias de métodos

Utiliza expresiones lambda para crear métodos anónimos. A veces, sin embargo, una expresión lambda no hace más que llamar a un método existente. En esos casos, a menudo es más claro referirse al método existente por su nombre. Las referencias de métodos le permiten hacer esto; son expresiones lambda compactas y fáciles de leer para métodos que ya tienen un nombre.

referencia del método

Utiliza expresiones lambda para crear métodos anónimos. Sin embargo, a veces las expresiones lambda no hacen más que llamar a un método existente. En estos casos, suele ser más claro referirse a un método existente por su nombre. Las referencias a métodos le permiten hacer esto; son expresiones lambda compactas y fáciles de leer para métodos que ya tienen nombres.

La descripción de las referencias de métodos proviene de:

https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

La siguiente es una sintaxis de referencia de método en Java 8, hay cuatro referencias de método:

tipo

sintaxis

ejemplo

haciendo referencia a un método estático

ContainingClass :: staticMethodName

Persona::compareByAge
MethodReferencesExamples::appendStrings

Hacer referencia a un método de instancia de un objeto específico

que contiene el objeto :: nombre del método de instancia

myComparisonProvider::compareByName
myApp::appendStrings2

Se refiere a un método de instancia de cualquier objeto de un tipo particular.

TipoContenedor :: nombreMétodo

String::compareToIgnoreCase
String::concat

referencia al constructor

Nombre de clase :: nuevo

HashSet::nuevo

No hay mucho que decir sobre los detalles, los documentos oficiales están escritos con más detalle, se recomienda leer y aprender. Personalmente, aunque la referencia de método se refiere a un método en lugar de llamar a un método, la referencia de método se parece más a un azúcar sintáctico, lo que simplifica el uso de expresiones lambda y facilita el uso de funciones existentes.

Ventajas y funciones de las expresiones Lambda

Las expresiones lambda aportan muchos de los beneficios de la programación funcional a Java. Como la mayoría de los lenguajes OOP, Java se basa en clases y objetos, y solo trata las clases como sus ciudadanos de primera clase. Otras entidades de programación importantes, como las funciones, pasan a un segundo plano.

Pero en la programación funcional, podemos definir funciones, darles variables de referencia, pasarlas como parámetros de método, etc. JavaScript es un buen ejemplo de programación funcional, podemos pasar métodos de devolución de llamada a llamadas Ajax, etc.

Tenga en cuenta que antes de Java 8, podíamos hacer todo con clases anónimas que podían ejecutarse usando expresiones Lambda, pero usaban una sintaxis muy concisa para lograr el mismo resultado. Veamos una comparación de la implementación del mismo método usando ambas técnicas.

//Usar la expresión lambda 
Operator<Integer> addOperation = (a, b) -> a + b; 

//Usar la clase anónima 
Operator<Integer> addOperation = new Operator<Integer>() { 
  @Override 
  public Integer process(Integer a , Entero b) { 
    devuelve a + b; 
  } 
};

La orientación a objetos no es mala, pero aporta muchos detalles detallados al programa. Como muestra el ejemplo anterior, la parte real utilizada es el código en el método process(), y todos los códigos restantes son construcciones del lenguaje Java.

Las interfaces funcionales de Java 8 y las expresiones Lambda nos ayudan a escribir código más pequeño y conciso al eliminar una gran cantidad de código repetitivo. ¿eso es todo? En lugar de analizar las ventajas de las expresiones Lambda, es mejor analizar su función. En cuanto a las ventajas hay que comparar para sacar conclusiones, pero al fin y al cabo las expresiones lambda pertenecen a la idea de programación funcional, si quieres comparar se siente exagerado ¿no?

¿Qué hace la expresión Lambda ? O cuál es su significado (valor), ¿hay alguna diferencia en la programación de Java con y sin expresiones Lambda ? ¿Es solo para reducir la cantidad de código? Para comprender el valor de las expresiones Lambda , primero debe comprender qué es la programación funcional . Las respuestas a las preguntas anteriores se encuentran en el artículo "Una exploración preliminar de la programación funcional" de Ruan Yifeng , que depende completamente de su propia comprensión.

También hay documentos relacionados aquí para hablar sobre este tema: ¿ Por qué necesitamos Lambda Expression ? El contenido clave en el arreglo del autor es el siguiente:

  1. Líneas de código reducidas (reducir el número de líneas de código)

Aunque las clases anónimas se usan en todas partes, todavía tienen muchos problemas. El primer gran problema es la complejidad. Estas clases hacen que la jerarquía del código parezca desordenada y complicada, también conocida como problema vertical. Comparando las expresiones lambda con las clases anónimas, la cantidad de código se reduce mucho. Los estudiantes a los que les gusta usar el azúcar sintáctico definitivamente se enamorarán de él.

  1. Soporte de ejecución secuencial y paralela ( soporte de ejecución secuencial y paralela )

Esto requiere que se reflejen expresiones Lambda combinadas con FunctionalInterface Lib, forEach, stream() y referencias de métodos. Para obtener detalles, consulte la respuesta de Mingqi en Mouhu: ¿Cuál es el uso de las expresiones Lambda? ¿cómo utilizar? Tanto imágenes como textos, ejemplos vívidos.

  1. Pasar comportamientos a métodos

Simplemente entendidas, las expresiones Lambda nos permiten tratar la funcionalidad como parámetros de método o el código como datos. Las expresiones lambda pueden expresar instancias de interfaces de método único (llamadas interfaces funcionales) de manera más compacta.

  1. Mayor eficiencia con pereza

¿Como se dice esto? De todos modos, las expresiones lambda se refieren a funciones existentes a través de referencias de métodos, por lo que escribir código es más eficiente.

Fuente aquí: Interfaces funcionales de Java 8 - JournalDev

Principio de implementación de la expresión Lambda

En Java 8, las expresiones Lambda se implementan con invocardinámica. Específicamente, el compilador de Java utiliza la instrucción de invocación dinámica para generar un adaptador que implementa una interfaz funcional .

No introduciré mucho, puedes leerlo si estás interesado.

Zheng Yudi (Investigador sénior de Oracle, doctorado en informática) "Desmontaje en profundidad de la máquina virtual de Java" , puede leer los capítulos de forma gratuita:

08 | ¿Cómo implementa la JVM invocación dinámica? (superior)

09 | ¿Cómo implementa la JVM invocación dinámica? (Abajo)

O puedes leer esto:

Llamada de método dinámico de JVM: invocardinámica

escribir al final

Con respecto al concepto de cierres, después de leer muchos artículos introductorios sobre cierres, el autor siente presión. Debido a la comprensión de los cierres, en realidad hay mil Hamlets para mil lectores. Este artículo es puramente una nota para aprender expresiones Java Lambda, funciones anónimas y cierres. Puede haber errores y omisiones en la redacción del artículo, y las correcciones son bienvenidas. Los extractos también están marcados con la fuente. Al mismo tiempo, muchas gracias por su paciencia al leer el artículo completo. No es fácil insistir en escribir artículos originales y prácticos. Si este artículo le resulta útil, le invitamos a que le guste y comente en el artículo Su aliento es la motivación incesante del autor, gracias de nuevo.

Referencias

Documentación oficial de la especificación de la máquina virtual de Java

Hitos de JDK 8

OpenJDK: Cierres

Cierres (Expresiones Lambda) para el Lenguaje de Programación Java

JEP 126: Expresiones lambda y métodos de extensión virtual

Expresiones Lambda en Java

Interfaz funcional Java 8 y expresión Lambda

Expresiones Java Lambda

¿Qué es una interfaz funcional en Java 8? @Anotación funcional y ejemplos

Clases anónimas y expresiones Lambda en Java

Interfaces funcionales de Java 8

Supongo que te gusta

Origin blog.csdn.net/xiangang12202/article/details/122916258
Recomendado
Clasificación