¿Cómo defino un método que toma un lambda como parámetro en Java 8?

363

En Java 8, los métodos se pueden crear como expresiones Lambda y se pueden pasar por referencia (con un poco de trabajo bajo el capó). Hay muchos ejemplos en línea con lambdas que se crean y usan con métodos, pero no hay ejemplos de cómo hacer que un método tome una lambda como parámetro. ¿Cuál es la sintaxis para eso?

MyClass.method((a, b) -> a+b);


class MyClass{
  //How do I define this method?
  static int method(Lambda l){
    return l(5, 10);
  }
}
Mario
fuente
29
Buena pregunta. Y tienes razón: ninguno de los tutoriales contiene esa parte.
Martin

Respuestas:

247

Las lambdas son puramente una construcción de sitio de llamada: el destinatario de la lambda no necesita saber que una Lambda está involucrada, sino que acepta una interfaz con el método apropiado.

En otras palabras, usted define o utiliza una interfaz funcional (es decir, una interfaz con un único método) que acepta y devuelve exactamente lo que desea.

Para esto, Java 8 viene con un conjunto de tipos de interfaz de uso común en java.util.function(gracias a Maurice Naftalin por la pista sobre JavaDoc).

Para este caso de uso específico no java.util.function.IntBinaryOperatorcon un solo int applyAsInt(int left, int right)método , por lo que podría escribir su methodsiguiente manera:

static int method(IntBinaryOperator op){
    return op.applyAsInt(5, 10);
}

Pero también puede definir su propia interfaz y usarla así:

public interface TwoArgIntOperator {
    public int op(int a, int b);
}

//elsewhere:
static int method(TwoArgIntOperator operator) {
    return operator.op(5, 10);
}

El uso de su propia interfaz tiene la ventaja de que puede tener nombres que indican más claramente la intención.

Joachim Sauer
fuente
55
¿Habrá interfaces integradas para usar, o debo crear una interfaz para cada lambda que quiera tomar?
Marius
Un buen compromiso con la reutilización frente al dilema del nombre descriptivo sería extender la interfaz integrada sin anular el método que especifica. Eso le da su nombre descriptivo con solo una línea de código adicional.
Will Byrne
No lo entiendo ¿Puede pasar lambda para cualquier cosa y funcionará? ¿Qué pasa si pasa (int a, int b, int c)por TwoArgIntOperator. Lo que sucede si TwoArgIntOperatortiene dos métodos con la misma firma. Esta respuesta es confusa.
Tomáš Zato - Restablece a Mónica el
77
@ TomášZato: si usa una lambda con argumentos que no coinciden, el compilador se quejará. Y las interfaces con dos métodos (no predeterminados) no serán utilizables como lambdas, ya que solo se pueden usar interfaces funcionales .
Joachim Sauer
Una pregunta básica: creo que todavía no entiendo el aspecto de pasar un método como parámetro sin los parámetros de ese método. Si está pasando TwoArgIntOperator como parámetro y necesita pasar el parámetro de ese método por separado, ¿no se ve feo? ¿Hay alguna manera de pasar el cuerpo de ejecución completo junto con el parámetro? Como en su ejemplo, una forma de evitar codificar "5" y "10".
instanceOfObject
63

Para usar la expresión de Lambda, debe crear su propia interfaz funcional o usar la interfaz funcional de Java para operaciones que requieren dos enteros y devolver como valor. IntBinaryOperator

Usando la interfaz funcional definida por el usuario

interface TwoArgInterface {

    public int operation(int a, int b);
}

public class MyClass {

    public static void main(String javalatte[]) {
        // this is lambda expression
        TwoArgInterface plusOperation = (a, b) -> a + b;
        System.out.println("Sum of 10,34 : " + plusOperation.operation(10, 34));

    }
}

Usando la interfaz funcional Java

import java.util.function.IntBinaryOperator;

public class MyClass1 {

    static void main(String javalatte[]) {
        // this is lambda expression
        IntBinaryOperator plusOperation = (a, b) -> a + b;
        System.out.println("Sum of 10,34 : " + plusOperation.applyAsInt(10, 34));

    }
}

Otro ejemplo que he creado está aquí.

pardeep131085
fuente
El enlace a la IntBinaryOperatordocumentación está muerto.
Hendrikto
1
Este es el mejor ejemplo de lambda que he encontrado hasta ahora, y fue el único que realmente me hizo 'entenderlo' finalmente.
JimmySmithJR
1
Sooooooo ... básicamente un delegado, pero se supone que no debemos llamarlo así?
Ryan Lundy
37

Para funciones que no tienen más de 2 parámetros, puede pasarlos sin definir su propia interfaz. Por ejemplo,

class Klass {
  static List<String> foo(Integer a, String b) { ... }
}

class MyClass{

  static List<String> method(BiFunction<Integer, String, List<String>> fn){
    return fn.apply(5, "FooBar");
  }
}

List<String> lStr = MyClass.method((a, b) -> Klass.foo((Integer) a, (String) b));

En BiFunction<Integer, String, List<String>>, Integery Stringson sus parámetros, y List<String>es su tipo de retorno.

Para una función con un solo parámetro, puede usar Function<T, R>, donde Tes su tipo de parámetro y Res su tipo de valor de retorno. Consulte esta página para ver todas las interfaces que ya están disponibles en Java.

David Wu
fuente
15

Hay una versión pública accesible desde la Web de JavaDocs Java 8 habilitados para Lambda, enlazada desde http://lambdafaq.org/lambda-resources . (Obviamente, esto debería ser un comentario sobre la respuesta de Joachim Sauer, pero no puedo ingresar a mi cuenta SO con los puntos de reputación que necesito para agregar un comentario.) El sitio lambdafaq (lo mantengo) responde esto y muchos otros Java -lambda preguntas.

Nota: esta respuesta se escribió antes de que la documentación de Java 8 GA estuviera disponible públicamente . Sin embargo, me he quedado en su lugar, porque las preguntas frecuentes de Lambda aún pueden ser útiles para las personas que aprenden sobre las características introducidas en Java 8.

Maurice Naftalin
fuente
2
¡Gracias por el enlace y el hecho de que mantiene ese sitio! Me tomé la libertad de agregar enlaces a su JavaDoc público a mi respuesta.
Joachim Sauer
1
Como nota al margen: Parece que estás construyendo para Lambdas lo que Angelika Langer ha construido para Generics . ¡Gracias por eso, Java necesita esos recursos!
Joachim Sauer
1
Si bien este enlace puede responder la pregunta, es mejor incluir aquí las partes esenciales de la respuesta y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden volverse inválidas si la página vinculada cambia. - De la opinión
ClearLogic
@ClearLogic Sí, de acuerdo. AFAIR No quería agregar nada a las respuestas existentes, sino solo señalar dónde había publicado una copia de la documentación de la API, que en ese momento no era de otro modo fácilmente accesible.
Maurice Naftalin
7

Para mí, la solución que tiene más sentido es definir una Callbackinterfaz:

interface Callback {
    void call();
}

y luego usarlo como parámetro en la función que desea llamar:

void somewhereInYourCode() {
    method(() -> {
        // You've passed a lambda!
        // method() is done, do whatever you want here.
    });
}

void method(Callback callback) {
    // Do what you have to do
    // ...

    // Don't forget to notify the caller once you're done
    callback.call();
}

Solo una precisión

Una lambda no es una interfaz especial, clase o cualquier otra cosa que pueda declarar usted mismo. Lambdaes solo el nombre dado a la () -> {}sintaxis especial, que permite una mejor legibilidad al pasar interfaces de un solo método como parámetro. Fue diseñado para reemplazar esto:

method(new Callback() {
    @Override
    public void call() {
        // Classic interface implementation, lot of useless boilerplate code.
        // method() is done, do whatever you want here.
    }
});

Entonces, en el ejemplo anterior, noCallback es una lambda, es solo una interfaz normal; es el nombre de la sintaxis de acceso directo que puede usar para implementarlo.lambda

flawyte
fuente
6

Para cualquiera que esté buscando en Google esto, un buen método sería usarlo java.util.function.BiConsumer. ex:

Import java.util.function.Consumer
public Class Main {
    public static void runLambda(BiConsumer<Integer, Integer> lambda) {
        lambda.accept(102, 54)
    }

    public static void main(String[] args) {
        runLambda((int1, int2) -> System.out.println(int1 + " + " + int2 + " = " + (int1 + int2)));
    }

El outprint sería: 166

Big_Bad_E
fuente
1
En lugar de Consumer<Pair<A,B>>usar BiConsumer<A,B>para este caso. ( docs )
nobar
No sabía que existía, debería examinar el paquete de funciones la próxima vez.
Big_Bad_E
5

La expresión lambda se puede pasar como argumento. Para pasar una expresión lambda como argumento, el tipo de parámetro (que recibe la expresión lambda como argumento) debe ser de tipo de interfaz funcional.

Si hay una interfaz funcional,

interface IMyFunc {
   boolean test(int num);
}

Y hay un método de filtro que agrega el int en la lista solo si es mayor que 5. Observe aquí que el método de filtro tiene una interfaz funcional IMyFunc como uno de los parámetros. En ese caso, la expresión lambda se puede pasar como argumento para el parámetro del método.

public class LambdaDemo {
    public static List<Integer> filter(IMyFunc testNum, List<Integer> listItems) {
        List<Integer> result = new ArrayList<Integer>();
        for(Integer item: listItems) {
            if(testNum.test(item)) {
                result.add(item);
            }
        }
        return result;
    }
    public static void main(String[] args) {
        List<Integer> myList = new ArrayList<Integer>();
        myList.add(1);
        myList.add(4);
        myList.add(6);
        myList.add(7);
        // calling filter method with a lambda expression
        // as one of the param
        Collection<Integer> values = filter(n -> n > 5, myList);

        System.out.println("Filtered values " + values);
    }
}
infoj
fuente
2

Puede usar interfaces funcionales como se mencionó anteriormente. a continuación se presentan algunos de los ejemplos

Function<Integer, Integer> f1 = num->(num*2+1);
System.out.println(f1.apply(10));

Predicate<Integer> f2= num->(num > 10);
System.out.println(f2.test(10));
System.out.println(f2.test(11));

Supplier<Integer> f3= ()-> 100;
System.out.println(f3.get());

Espero eso ayude

ank
fuente
1

Bueno, eso es facil. El propósito de la expresión lambda es implementar la interfaz funcional. Es la interfaz con un solo método. Aquí hay un artículo impresionante sobre interfaces funcionales predefinidas y heredadas.

De todos modos, si desea implementar su propia interfaz funcional, hágalo. Solo por simple ejemplo:

public interface MyFunctionalInterface {
    String makeIt(String s);
}

Entonces, hagamos una clase, donde crearemos un método, que acepte el tipo de MyFunctionalInterface :

public class Main {

    static void printIt(String s, MyFunctionalInterface f) {
        System.out.println(f.makeIt(s));
    }

    public static void main(String[] args) {

    }
}

Lo último que debe hacer es pasar la implementación de MyFunctionalInterface al método que hemos definido:

public class Main {

    static void printIt(String s, MyFunctionalInterface f) {
        System.out.println(f.makeIt(s));
    }

    public static void main(String[] args) {
        printIt("Java", s -> s + " is Awesome");
    }
}

¡Eso es!

SanchelliosProg
fuente
1

Lambda no es un objeto sino una interfaz funcional. Se pueden definir tantas interfaces funcionales como puedan utilizando @FuntionalInterface como anotación.

@FuntionalInterface
public interface SumLambdaExpression {
     public int do(int a, int b);
}

public class MyClass {
     public static void main(String [] args) {
          SumLambdaExpression s = (a,b)->a+b;
          lambdaArgFunction(s);
     }

     public static void lambdaArgFunction(SumLambdaExpression s) {
          System.out.println("Output : "+s.do(2,5));
     }
}

La salida será la siguiente

Output : 7

El concepto básico de una expresión Lambda es definir su propia lógica pero argumentos ya definidos. Entonces, en el código anterior, puede cambiar la definición de la función do de la adición a cualquier otra definición, pero sus argumentos están limitados a 2.

raja emani
fuente
1

Básicamente, para pasar una expresión lamda como parámetro, necesitamos un tipo en el que podamos contenerla. Así como un valor entero que tenemos en primitivo int o clase Integer. Java no tiene un tipo separado para la expresión lamda, sino que usa una interfaz como tipo para contener el argumento. Pero esa interfaz debería ser una interfaz funcional .

Vamshi
fuente
0

Haz lo siguiente ..

Has declarado method(lambda l) Todo lo que quieres hacer es crear una interfaz con el nombre lambday declarar un método abstracto

public int add(int a,int b);  

El nombre del método no importa aquí.

Así que cuando u Llamada MyClass.method( (a,b)->a+b) Esta aplicación (a,b)->a+bse inyectará a su método de interfaz complemento .So cada vez que se llama a l.addque va a tomar esta aplicación y realizar la suma de ae b y return l.add(2,3)volverá 5. - Básicamente esto es lo que hace lambda ...

Arasn
fuente
-1

A continuación se detalla cómo C # maneja este problema (pero expresado como código Java). Algo como esto podría manejar casi todas sus necesidades:

import static org.util.function.Functions.*;

public class Test {

    public static void main(String[] args)
    {
        Test.invoke((a, b) -> a + b);       
    }

    public static void invoke(Func2<Integer, Integer, Integer> func)
    {
        System.out.println(func.apply(5, 6));
    }
}

package org.util.function;

public interface Functions {

    //Actions:
    public interface Action {
        public void apply();
    }

    public interface Action1<T1> {
        public void apply(T1 arg1);
    }

    public interface Action2<T1, T2> {
        public void apply(T1 arg1, T2 arg2);
    }

    public interface Action3<T1, T2, T3> {
        public void apply(T1 arg1, T2 arg2, T3 arg3);
    }

    public interface Action4<T1, T2, T3, T4> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    }

    public interface Action5<T1, T2, T3, T4, T5> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    }

    public interface Action6<T1, T2, T3, T4, T5, T6> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    }

    public interface Action7<T1, T2, T3, T4, T5, T6, T7> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    }

    public interface Action8<T1, T2, T3, T4, T5, T6, T7, T8> {
        public void apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    }

    //Functions:
    public interface Func<TResult> {
        public TResult apply();
    }

    public interface Func1<T1, TResult> {
        public TResult apply(T1 arg1);
    }

    public interface Func2<T1, T2, TResult> {
        public TResult apply(T1 arg1, T2 arg2);
    }

    public interface Func3<T1, T2, T3, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3);
    }

    public interface Func4<T1, T2, T3, T4, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
    }

    public interface Func5<T1, T2, T3, T4, T5, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);
    }

    public interface Func6<T1, T2, T3, T4, T5, T6, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6);
    }

    public interface Func7<T1, T2, T3, T4, T5, T6, T7, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7);
    }

    public interface Func8<T1, T2, T3, T4, T5, T6, T7, T8, TResult> {
        public TResult apply(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8);
    }
}
craigrs84
fuente
-2

Hay flexibilidad en el uso de lambda como parámetro. Permite la programación funcional en java. La sintaxis básica es

param -> método_cuerpo

Lo siguiente es una forma, puede definir un método que tome la interfaz funcional (se usa lambda) como parámetro. a. si desea definir un método declarado dentro de una interfaz funcional, por ejemplo, la interfaz funcional se proporciona como argumento / parámetro a un método llamado desdemain()

@FunctionalInterface
interface FInterface{
    int callMeLambda(String temp);
}


class ConcreteClass{

    void funcUsesAnonymousOrLambda(FInterface fi){
        System.out.println("===Executing method arg instantiated with Lambda==="));
    }

    public static void main(){
        // calls a method having FInterface as an argument.
        funcUsesAnonymousOrLambda(new FInterface() {

            int callMeLambda(String temp){ //define callMeLambda(){} here..
                return 0;
            }
        }
    }

/***********Can be replaced by Lambda below*********/
        funcUsesAnonymousOrLambda( (x) -> {
            return 0; //(1)
        }

    }

FInterface fi = (x) -> {return 0; };

funcUsesAnonymousOrLambda (fi);

Aquí arriba se puede ver cómo se puede reemplazar una expresión lambda con una interfaz.

Arriba se explica un uso particular de la expresión lambda, hay más. ref Java 8 lambda dentro de un lambda no puede modificar la variable del lambda externo

hola.nitish
fuente