Buscar..


Introducción

Las expresiones Lambda proporcionan una forma clara y concisa de implementar una interfaz de un solo método utilizando una expresión. Le permiten reducir la cantidad de código que tiene que crear y mantener. Aunque son similares a las clases anónimas, no tienen información de tipo por sí mismas. La inferencia de tipos debe suceder.

Las referencias de métodos implementan interfaces funcionales utilizando métodos existentes en lugar de expresiones. Pertenecen a la familia lambda también.

Sintaxis

  • () -> {return expresión; } // Zero-arity con el cuerpo de la función para devolver un valor.
  • () -> expresión // taquigrafía para la declaración anterior; No hay punto y coma para las expresiones.
  • () -> {function-body} // Efecto lateral en la expresión lambda para realizar operaciones.
  • parámetroName -> expresión // expresión lambda de una aridad. En las expresiones lambda con un solo argumento, se puede eliminar el paréntesis.
  • (Escriba ParameterName, Escriba secondParameterName, ...) -> expresión // lambda evaluando una expresión con los parámetros listados a la izquierda
  • (nombre de parámetro, segundo Nombre de parámetro, ...) -> expresión // Taquigrafía que elimina los tipos de parámetros para los nombres de parámetros. Solo se puede usar en contextos que pueden ser deducidos por el compilador donde el tamaño de lista de parámetros dado coincide con uno (y solo uno) del tamaño de las interfaces funcionales esperadas.

Usando expresiones Lambda para ordenar una colección

Listas de clasificación

Antes de Java 8, era necesario implementar la interfaz java.util.Comparator con una clase anónima (o nombrada) al ordenar una lista 1 :

Java SE 1.2
List<Person> people = ...
Collections.sort(
    people,
    new Comparator<Person>() {
        public int compare(Person p1, Person p2){
            return p1.getFirstName().compareTo(p2.getFirstName());
        }
    }
);

A partir de Java 8, la clase anónima se puede reemplazar con una expresión lambda. Tenga en cuenta que los tipos para los parámetros p1 y p2 se pueden omitir, ya que el compilador los deducirá automáticamente:

Collections.sort(
    people, 
    (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);

El ejemplo se puede simplificar utilizando Comparator.comparing y referencias de métodos expresadas con el símbolo :: (dos puntos).

Collections.sort(
    people,
    Comparator.comparing(Person::getFirstName)
);

Una importación estática nos permite expresar esto de manera más concisa, pero es discutible si esto mejora la legibilidad general:

import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));

Los comparadores construidos de esta manera también se pueden encadenar juntos. Por ejemplo, después de comparar personas por su primer nombre, si hay personas con el mismo nombre, el método thenComparing también se compara por apellido:

sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));

1: tenga en cuenta que Collections.sort (...) solo funciona en colecciones que son subtipos de List . Las API de Set y Collection no implican ningún orden de los elementos.

Clasificando mapas

Puede ordenar las entradas de un HashMap por valor de una manera similar. (Tenga en cuenta que debe utilizarse un LinkedHashMap como destino. Las claves en un HashMap normal no están ordenadas).

Map<String, Integer> map = new HashMap();  // ... or any other Map class
// populate the map
map = map.entrySet()
    .stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue())
    .collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
                              (k, v) -> k, LinkedHashMap::new));

Introducción a las lambdas de Java.

Interfaces funcionales

Lambdas solo puede operar en una interfaz funcional, que es una interfaz con solo un método abstracto. Las interfaces funcionales pueden tener cualquier número de métodos default o static . (Por esta razón, a veces se les conoce como interfaces de método abstracto único o interfaces SAM).

interface Foo1 {
    void bar();
}

interface Foo2 {
    int bar(boolean baz);
}

interface Foo3 {
    String bar(Object baz, int mink);
}

interface Foo4 {
    default String bar() { // default so not counted
        return "baz";
    }
    void quux();
}

Al declarar una interfaz funcional, se puede agregar la anotación @FunctionalInterface . Esto no tiene ningún efecto especial, pero se generará un error de compilación si esta anotación se aplica a una interfaz que no es funcional, por lo que actúa como un recordatorio de que la interfaz no debe cambiarse.

@FunctionalInterface
interface Foo5 {
    void bar();
}

@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}

@FunctionalInterface
interface Foo6 {
    void bar();
    boolean equals(Object obj); // overrides one of Object's method so not counted
}

A la inversa, esta no es una interfaz funcional, ya que tiene más de un método abstracto :

interface BadFoo {
    void bar();
    void quux(); // <-- Second method prevents lambda: which one should 
                 // be considered as lambda?
}

Esto tampoco es una interfaz funcional, ya que no tiene ningún método:

interface BlankFoo2 { }

Tome nota de lo siguiente. Supongamos que tienes

interface Parent { public int parentMethod(); }

y

interface Child extends Parent { public int ChildMethod(); }

Entonces, Child no puede ser una interfaz funcional ya que tiene dos métodos especificados.

Java 8 también proporciona una serie de interfaces funcionales genéricas en el paquete java.util.function . Por ejemplo, la interfaz integrada Predicate<T> envuelve un solo método que ingresa un valor de tipo T y devuelve un valor boolean .


Expresiones lambda

La estructura básica de una expresión Lambda es:

FunctionalInterface fi = () -> System.out.println ("Hello");

fi entonces tendrá una instancia de singleton de una clase, similar a una clase anónima, que implementa FunctionalInterface y donde la definición de un método es { System.out.println("Hello"); } . En otras palabras, lo anterior es mayormente equivalente a:

FunctionalInterface fi = new FunctionalInterface() {
    @Override
    public void theOneMethod() {
        System.out.println("Hello");
    }
};

La lambda solo es "mayormente equivalente" a la clase anónima porque en lambda, el significado de expresiones como this , super o toString() referencia a la clase dentro de la cual se realiza la asignación, no al objeto recién creado.

No puede especificar el nombre del método cuando usa un lambda, pero no debería necesitarlo, ya que una interfaz funcional solo debe tener un método abstracto, por lo que Java anula ese método.

En los casos en que el tipo de la lambda no es seguro, (por ejemplo, los métodos sobrecargados) puede agregar una conversión a la lambda para decirle al compilador cuál debería ser su tipo, así:

Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true

Si el método único de la interfaz funcional toma parámetros, los nombres formales locales de estos deberían aparecer entre los paréntesis de la lambda. No es necesario declarar el tipo de parámetro o retorno, ya que estos se toman de la interfaz (aunque no es un error declarar los tipos de parámetros si lo desea). Por lo tanto, estos dos ejemplos son equivalentes:

Foo2 longFoo = new Foo2() {
    @Override
    public int bar(boolean baz) {
        return baz ? 1 : 0;
    }
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };

Los paréntesis alrededor del argumento se pueden omitir si la función solo tiene un argumento:

Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay

Devoluciones implícitas

Si el código colocado dentro de un lambda es una expresión de Java en lugar de una declaración , se trata como un método que devuelve el valor de la expresión. Por lo tanto, los siguientes dos son equivalentes:

IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };

Acceso a variables locales (cierres de valor)

Dado que las lambdas son una abreviatura sintáctica para las clases anónimas, siguen las mismas reglas para acceder a las variables locales en el ámbito adjunto; Las variables deben ser tratadas como final y no modificadas dentro de la lambda.

IntUnaryOperator makeAdder(int amount) {
    return (x) -> (x + amount); // Legal even though amount will go out of scope
                                // because amount is not modified
}

IntUnaryOperator makeAccumulator(int value) {
    return (x) -> { value += x; return value; }; // Will not compile
}

Si es necesario envolver una variable cambiante de esta manera, se debe usar un objeto normal que mantenga una copia de la variable. Lea más en Java Closures con expresiones lambda.


Aceptando Lambdas

Debido a que una lambda es una implementación de una interfaz, no se necesita hacer nada especial para que un método acepte una lambda: cualquier función que tome una interfaz funcional también puede aceptar una lambda.

public void passMeALambda(Foo1 f) {
    f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));

El tipo de expresión lambda

Una expresión lambda, por sí misma, no tiene un tipo específico. Si bien es cierto que los tipos y la cantidad de parámetros, junto con el tipo de un valor de retorno pueden transmitir cierta información de tipo, dicha información solo restringirá a qué tipos se puede asignar. La lambda recibe un tipo cuando se asigna a un tipo de interfaz funcional de una de las siguientes maneras:

  • Asignación directa a un tipo funcional, por ejemplo, myPredicate = s -> s.isEmpty()
  • Pasándolo como un parámetro que tiene un tipo funcional, por ejemplo, stream.filter(s -> s.isEmpty())
  • Devolviéndolo desde una función que devuelve un tipo funcional, por ejemplo, return s -> s.isEmpty()
  • Convertirlo en un tipo funcional, por ejemplo (Predicate<String>) s -> s.isEmpty()

Hasta que se realice una asignación de este tipo a un tipo funcional, la lambda no tiene un tipo definido. Para ilustrar, considere la expresión lambda o -> o.isEmpty() . La misma expresión lambda se puede asignar a muchos tipos funcionales diferentes:

Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();

Ahora que están asignados, los ejemplos que se muestran son de tipos completamente diferentes, aunque las expresiones lambda tienen el mismo aspecto y no pueden asignarse entre sí.

Referencias del método

Las referencias de método permiten que los métodos estáticos o de instancia predefinidos que se adhieran a una interfaz funcional compatible se pasen como argumentos en lugar de una expresión lambda anónima.

Supongamos que tenemos un modelo:

class Person {
    private final String name;
    private final String surname;

    public Person(String name, String surname){
        this.name = name;
        this.surname = surname;
    }

    public String getName(){ return name; }
    public String getSurname(){ return surname; }
}

List<Person> people = getSomePeople();

Referencia del método de instancia (a una instancia arbitraria)

people.stream().map(Person::getName)

La lambda equivalente:

people.stream().map(person -> person.getName())

En este ejemplo, se está pasando una referencia de método al método de instancia getName() de tipo Person . Como se sabe que es del tipo de colección, se invocará el método en la instancia (conocido más adelante).


Referencia del método de instancia (a una instancia específica)

people.forEach(System.out::println);

Dado que System.out es una instancia de PrintStream , una referencia de método a esta instancia específica se pasa como un argumento.

La lambda equivalente:

people.forEach(person -> System.out.println(person));

Referencia de método estático

También para transformar flujos podemos aplicar referencias a métodos estáticos:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)

Este ejemplo pasa una referencia al método static valueOf() en el tipo String . Por lo tanto, el objeto de instancia en la colección se pasa como un argumento a valueOf() .

La lambda equivalente:

 numbers.stream().map(num -> String.valueOf(num))

Referencia a un constructor

List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)

Lea Recopilar elementos de un flujo en una colección para ver cómo recopilar elementos para recopilar.

El constructor de argumento de cadena única del tipo Integer se está utilizando aquí, para construir un entero dado la cadena proporcionada como el argumento. En este caso, siempre que la cadena represente un número, el flujo se asignará a enteros. La lambda equivalente:

strings.stream().map(s -> new Integer(s));

Hoja de trucos

Formato de referencia del método Código Equivalente lambda
Método estático TypeName::method (args) -> TypeName.method(args)
Método no estático (en caso de * ) instance::method (args) -> instance.method(args)
Método no estático (sin instancia) TypeName::method (instance, args) -> instance.method(args)
Constructor ** TypeName::new (args) -> new TypeName(args)
Constructor de arrays TypeName[]::new (int size) -> new TypeName[size]

* instance puede ser cualquier expresión que evalúe una referencia a una instancia, por ejemplo, getInstance()::method , this::method

** Si TypeName es una clase interna no estática, la referencia del constructor solo es válida dentro del alcance de una instancia de clase externa

Implementando multiples interfaces

A veces es posible que desee tener una expresión lambda implementando más de una interfaz. Esto es principalmente útil con interfaces de marcadores (como java.io.Serializable ) ya que no agregan métodos abstractos.

Por ejemplo, desea crear un TreeSet con un Comparator personalizado y luego serializarlo y enviarlo a través de la red. El enfoque trivial:

TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));

no funciona ya que la lambda para el comparador no implementa Serializable . Puede solucionar este problema utilizando tipos de intersección y especificando explícitamente que este lambda debe ser serializable:

TreeSet<Long> ts = new TreeSet<>(
    (Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));

Si usa con frecuencia tipos de intersección (por ejemplo, si usa un marco como Apache Spark, donde casi todo tiene que ser serializable), puede crear interfaces vacías y usarlas en su código:

public interface SerializableComparator extends Comparator<Long>, Serializable {}

public class CustomTreeSet {
  public CustomTreeSet(SerializableComparator comparator) {}
}

De esta manera, se garantiza que el comparador pasado será serializable.

Lambdas y patrón de ejecución

Hay varios buenos ejemplos del uso de lambdas como FunctionalInterface en escenarios simples. Un caso de uso bastante común que puede ser mejorado por las lambdas es lo que se denomina patrón de ejecución. En este patrón, tiene un conjunto de código de configuración / desmontaje estándar que se necesita para múltiples escenarios que rodean el código específico de caso de uso. Algunos ejemplos comunes de esto son los archivos io, database io, try / catch blocks.

interface DataProcessor {
    void process( Connection connection ) throws SQLException;;
}

public void doProcessing( DataProcessor processor ) throws SQLException{
    try (Connection connection = DBUtil.getDatabaseConnection();) {
        processor.process(connection);
        connection.commit();
    } 
}

Luego, para llamar a este método con un lambda, podría verse así:

public static void updateMyDAO(MyVO vo) throws DatabaseException {
    doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}

Esto no se limita a las operaciones de E / S. Se puede aplicar a cualquier escenario en el que se apliquen tareas similares de configuración / desmontaje con pequeñas variaciones. El principal beneficio de este patrón es la reutilización del código y la aplicación de DRY (no se repita).

Usando lambda expresión con su propia interfaz funcional

Lambdas está diseñado para proporcionar código de implementación en línea para interfaces de un solo método y la capacidad de pasarlas como lo hemos estado haciendo con las variables normales. Los llamamos interfaz funcional.

Por ejemplo, escribir un Runnable en una clase anónima y comenzar un Thread se ve así:

//Old way
new Thread(
        new Runnable(){
            public void run(){
                System.out.println("run logic...");
            }
        }
).start();

//lambdas, from Java 8
new Thread(
        ()-> System.out.println("run logic...")
).start();

Ahora, en línea con lo anterior, digamos que tiene alguna interfaz personalizada:

interface TwoArgInterface {
    int operate(int a, int b);
}

¿Cómo utiliza lambda para implementar esta interfaz en su código? Igual que el ejemplo de Runnable que se muestra arriba. Vea el programa del conductor a continuación:

public class CustomLambda {
    public static void main(String[] args) {

        TwoArgInterface plusOperation = (a, b) -> a + b;
        TwoArgInterface divideOperation = (a,b)->{
            if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
            return a/b;
        };

        System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
        System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));

    }
}

`return 'solo regresa de la lambda, no del método externo

El método de return solo regresa de la lambda, no del método externo.

¡Cuidado, esto es diferente de Scala y Kotlin!

void threeTimes(IntConsumer r) {
  for (int i = 0; i < 3; i++) {
    r.accept(i);
  }
}

void demo() {
  threeTimes(i -> {
    System.out.println(i);
    return; // Return from lambda to threeTimes only!
  });
}

Esto puede llevar a un comportamiento inesperado al intentar escribir construcciones de lenguaje propias, como en las construcciones integradas, como for bucles de return comporta de manera diferente:

void demo2() {
  for (int i = 0; i < 3; i++) {
    System.out.println(i);
    return; // Return from 'demo2' entirely
  }
}

En Scala y Kotlin, demo y demo2 solo imprimirían 0 . Pero esto no es más consistente. El enfoque de Java es consistente con la refactorización y el uso de clases: el return en el código en la parte superior y el siguiente código se comporta de la misma manera:

void demo3() {
  threeTimes(new MyIntConsumer());
}

class MyIntConsumer implements IntConsumer {
  public void accept(int i) {
    System.out.println(i);
    return;
  }
}

Por lo tanto, el Java return es más coherente con los métodos de clase y refactorización, pero menos con el for y while las órdenes internas, éstas siguen siendo especial.

Debido a esto, los siguientes dos son equivalentes en Java:

IntStream.range(1, 4)
    .map(x -> x * x)
    .forEach(System.out::println);
IntStream.range(1, 4)
    .map(x -> { return x * x; })
    .forEach(System.out::println);

Además, el uso de try-with-resources es seguro en Java:

class Resource implements AutoCloseable {
  public void close() { System.out.println("close()"); }
}

void executeAround(Consumer<Resource> f) {
  try (Resource r = new Resource()) {
    System.out.print("before ");
    f.accept(r);
    System.out.print("after ");
  }
}

void demo4() {
  executeAround(r -> {
    System.out.print("accept() ");
    return; // Does not return from demo4, but frees the resource.
  });
}

se imprimirá before accept() after close() . En la semántica de Scala y Kotlin, el intento con recursos no se cerraría, pero se imprimiría before accept() solamente.

Cierres de Java con expresiones lambda.

Un cierre lambda se crea cuando una expresión lambda hace referencia a las variables de un ámbito de distribución (global o local). Las reglas para hacer esto son las mismas que para los métodos en línea y las clases anónimas.

Las variables locales de un ámbito que se usan dentro de un lambda tienen que ser final . Con Java 8 (la versión más antigua que admite lambdas), no necesitan ser declarados final en el contexto externo, sino que deben ser tratados de esa manera. Por ejemplo:

int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};

Esto es legal siempre que no se cambie el valor de la variable n . Si intenta cambiar la variable, dentro o fuera de la lambda, obtendrá el siguiente error de compilación:

"las variables locales referenciadas desde una expresión lambda deben ser finales o efectivamente finales ".

Por ejemplo:

int n = 0;
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};
n++; // Will generate an error.

Si es necesario usar una variable cambiante dentro de un lambda, el enfoque normal es declarar una copia final de la variable y usar la copia. Por ejemplo

int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++;      // Now will not generate an error
r.run();  // Will run with i = 0 because k was 0 when the lambda was created

Naturalmente, el cuerpo de la lambda no ve los cambios en la variable original.

Tenga en cuenta que Java no admite cierres verdaderos. Un lambda de Java no se puede crear de manera que le permita ver los cambios en el entorno en el que se creó la instancia. Si desea implementar un cierre que observe o realice cambios en su entorno, debe simularlo utilizando una clase regular. Por ejemplo:

// Does not compile ...
public IntUnaryOperator createAccumulator() {
    int value = 0;
    IntUnaryOperator accumulate = (x) -> { value += x; return value; };
    return accumulate;
}

El ejemplo anterior no se compilará por razones discutidas previamente. Podemos solucionar el error de compilación de la siguiente manera:

// Compiles, but is incorrect ...
public class AccumulatorGenerator {
    private int value = 0;

    public IntUnaryOperator createAccumulator() {
        IntUnaryOperator accumulate = (x) -> { value += x; return value; };
        return accumulate;
    }
}

El problema es que esto rompe el contrato de diseño para la interfaz IntUnaryOperator que establece que las instancias deben ser funcionales y sin estado. Si dicho cierre se pasa a funciones integradas que aceptan objetos funcionales, es probable que cause bloqueos o comportamientos erróneos. Los cierres que encapsulan el estado mutable deben implementarse como clases regulares. Por ejemplo.

// Correct ...
public class Accumulator {
   private int value = 0;

   public int accumulate(int x) {
      value += x;
      return value;
   }
}

Lambda - Ejemplo de oyente

Oyente anónimo de clase

Antes de Java 8, es muy común que se use una clase anónima para manejar el evento de clic de un JButton, como se muestra en el siguiente código. Este ejemplo muestra cómo implementar una escucha anónima dentro del alcance de btn.addActionListener .

JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button was pressed");
    }
});

Oyente lambda

Debido a que la interfaz ActionListener define solo un método actionPerformed() , es una interfaz funcional que significa que hay un lugar para usar expresiones Lambda para reemplazar el código repetitivo. El ejemplo anterior se puede reescribir usando expresiones Lambda de la siguiente manera:

JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
    System.out.println("Button was pressed");
});

Estilo tradicional al estilo Lambda.

Forma tradicional

interface MathOperation{
    boolean unaryOperation(int num);
}

public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = new MathOperation() {
            @Override
            public boolean unaryOperation(int num) {
                return num%2 == 0;
            }
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}

Estilo lambda

  1. Eliminar el nombre de la clase y el cuerpo de la interfaz funcional.
public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = (int num) -> {
            return num%2 == 0;
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}
  1. Declaración de tipo opcional
MathOperation isEven = (num) -> {
    return num%2 == 0;
};
  1. Paréntesis opcional alrededor del parámetro, si es un solo parámetro
MathOperation isEven = num -> {
    return num%2 == 0;
};
  1. Tirantes opcionales, si solo hay una línea en el cuerpo de la función
  2. Palabra clave de retorno opcional, si solo hay una línea en el cuerpo de la función
MathOperation isEven = num -> num%2 == 0;

Lambdas y utilización de memoria.

Como los lambdas de Java son cierres, pueden "capturar" los valores de las variables en el ámbito léxico adjunto. Si bien no todos los lambdas capturan nada, los lambdas simples como s -> s.length() no capturan nada y se llaman sin estado , la captura de los lambdas requiere un objeto temporal para contener las variables capturadas. En este fragmento de código, la lambda () -> j es una lambda de captura, y puede hacer que se asigne un objeto cuando se evalúa:

public static void main(String[] args) throws Exception {
    for (int i = 0; i < 1000000000; i++) {
        int j = i;
        doSomethingWithLambda(() -> j);
    }
}

Aunque podría no ser inmediatamente obvio, ya que la new palabra clave no aparece en ningún lugar del fragmento de código, este código puede crear 1,000,000,000 objetos separados para representar las instancias de la expresión () -> j lambda. Sin embargo, también se debe tener en cuenta que las versiones futuras de Java 1 pueden ser capaces de optimizar esto para que en el tiempo de ejecución las instancias de lambda se reutilizaran o se representaran de alguna otra manera.


1 - Por ejemplo, Java 9 introduce una fase de "enlace" opcional a la secuencia de compilación de Java que brindará la oportunidad de realizar optimizaciones globales como esta.

Usar expresiones y predicados lambda para obtener un determinado valor (es) de una lista

A partir de Java 8, puede utilizar expresiones y predicados lambda.

Ejemplo: Use una expresión lambda y un predicado para obtener un determinado valor de una lista. En este ejemplo, todas las personas se imprimirán con el hecho si tienen 18 años o más o no.

Clase de persona:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() { return age; }
    public String getName() { return name; }
}

El Predicado de la interfaz incorporada de los paquetes java.util.function.Predicate es una interfaz funcional con un método de boolean test(T t) .

Ejemplo de uso:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class LambdaExample {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jeroen", 20));
        personList.add(new Person("Jack", 5));
        personList.add(new Person("Lisa", 19));

        print(personList, p -> p.getAge() >= 18);
    }

    private static void print(List<Person> personList, Predicate<Person> checker) {
        for (Person person : personList) {
            if (checker.test(person)) {
                System.out.print(person + " matches your expression.");
            } else {
                System.out.println(person  + " doesn't match your expression.");
            }
        }
    }
}

The print(personList, p -> p.getAge() >= 18); el método toma una expresión lambda (porque el Predicado se usa como un parámetro) donde puede definir la expresión que se necesita. El método de prueba del verificador comprueba si esta expresión es correcta o no: checker.test(person) .

Puede cambiar esto fácilmente a otra cosa, por ejemplo, para print(personList, p -> p.getName().startsWith("J")); . Esto verificará si el nombre de la persona comienza con una "J".



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow