10

How to pass a method as a parameter using lambdas is discussed here:
Java Pass Method as Parameter

In other languages, namely C++, it is possible to bind a function to it's parameters using Lambdas - discussed here:
Bind Vs Lambda?

Is it possible, in Java, to bind a method using lambdas?

If so, how would you accomplish this?

Edit >>>>

An example, by request, of what I am generally trying to do:

Be warned, there is pseudo code here.

public class DataView {

    private static ArrayList<Float> rectData = new ArrayList<Float>();
    private static ArrayList<Float> textData = new ArrayList<Float>();

    DataView(){

        //Pseudo Code:
        boundFunction mybind  = boundFunction(functionA, 5, 10);
        boundFunction mybind2 = boundFunction(functionB, 10, 12);

        iter(mybind);
        iter(mybind2);

    }

    //Method with pseudo parameter
    private void iter(functionSignature){
        for(Float i : textData){

            //Pseudo call to pseudo parameter
            functionSignature();

        }
    }

    private void functionA(int a, int b){
        //dostuff

    }

    private void functionB(int a, int b){
        //do other stuff

    }

}

Bare in mind, I'm not looking for 'another way to accomplish this functionality' - this example is to illustrate a general way in which I would like to use functions as parameters, and to bind parameters to those functions.

Edit>>>

Attempt using anonymous classes:

public class DataView {

    private class Bound{ 
        public void run(){}

    }

    private static ArrayList<Float> rectData = new ArrayList<Float>();
    private static ArrayList<Float> textData = new ArrayList<Float>();

    DataView(){

        Bound mybind = new Bound(){
            public void run(){
                functionA(5,10);
            }
        };

        Bound mybind2 = new Bound(){
            public void run(){
                functionB(5,10);
            }
        };

        iter(mybind);
        iter(mybind2);

    }

    private void iter(Bound function){
        for(Float i : textData){

            function.run();

        }
    }

    private void functionA(int a, int b){
        //dostuff

    }

    private void functionB(int a, int b){
        //do other stuff

    }
}
4

3 回答 3

8

As said in other answers, Java requires an actual functional interface type to represent a function. This also applies to a bind operation which requires even two of such interfaces, one to represent the unbound function and one for the bound function:

public class DataView {
    interface NoArgFunction {
        void func();
    }
    interface TwoIntFunction {
        void func(int a, int b);
    }
    static NoArgFunction bind(TwoIntFunction f, int first, int second) {
        return () -> f.func(first, second);
    }

    private static ArrayList<Float> rectData = new ArrayList<Float>();
    private static ArrayList<Float> textData = new ArrayList<Float>();

    DataView(){
        NoArgFunction mybind  = bind(this::functionA, 5, 10);
        NoArgFunction mybind2 = bind(this::functionB, 10, 12);

        iter(mybind);
        iter(mybind2);
    }
    private void iter(NoArgFunction noArg){
        for(Float i : textData){
            noArg.func();
        }
    }
    private void functionA(int a, int b){
        //dostuff
    }
    private void functionB(int a, int b){
        //do other stuff
    }
}

As a fun fact, on the bytecode level, there is a capability to combine a method with arguments without the intermediate step, which is exactly what is being used by the lambda expression () -> f.func(first, second), which gets compiled into a synthetic method holding the lambda body and having two parameters for first and second which will be used at runtime to construct the NoArgFunction instance binding the current values of first and second.

But you can’t use this in Java source code. The only supported binding for existing methods is the one seen at the method references this::functionA and this::functionB, both binding the current this instance to the methods functionA and functionB. Well, using a lambda expression like
NoArgFunction mybind = () -> functionA(5, 10); in the first place without a bind method does short-cut the process…</p>

So the bottom line is that there’s not much sense in trying to fight against the language trying to model a feature that isn’t intrinsically supported. Using a lambda expression instead of binding, together with a target type predefined for this purpose makes the code much simpler:

public class DataView {
    private static ArrayList<Float> rectData = new ArrayList<Float>();
    private static ArrayList<Float> textData = new ArrayList<Float>();

    DataView(){
        iter(x -> functionA(5, 10));
        iter(x -> functionB(10, 12));
    }
    private void iter(Consumer<Float> function){
        textData.forEach(function);
    }
    private void functionA(int a, int b){
        //dostuff
    }
    private void functionB(int a, int b){
        //do other stuff
    }
}
于 2016-07-21T14:38:36.993 回答
1

Looking at the code behind the second link:

auto dice = [&]() { return distribution(engine); };

The Java equivalent would be:

Supplier<...> dice = () -> distribution.apply(engine);

Where:

  • distribution is a java.util.function.Function.
  • ... is the return type of the call.

For different combinations of in- and output types, there are different functional interfaces available in java.util.function. You can also define you're own functional interface.

A lambda can be assigned to a variable with a functional interface type, as long as it matches the in- and output types defined by the interface's method.


Besides the lack of operator overloading and an auto equivalent, in Java. The types of the captured variables are all ways either reference types (all objects), or primitives.

You can change a captured reference type variable by calling (non-const, there is no such thing in Java) methods on it. Like apply is called on distribution in the example.

All captured variables must be final, or effectively final. That means you can not assign new values to the captured variables. I.e. distribution = someVal, is not allowed, for both reference and primitive types.

于 2016-07-20T18:38:45.750 回答
1

In your example, you could do this:

public class DataView {

    private static ArrayList<Float> textData = new ArrayList<Float>();

    DataView(){
        Runnable mybind  = () -> functionA(5, 10);
        Runnable mybind2 = () -> functionB(10, 12);

        iter(mybind);
        iter(mybind2);

    }

    //Method with pseudo parameter
    private void iter(Runnable r){
        for(Float i : textData){
            r.run();
        }
    }

    private void functionA(int a, int b){
        //dostuff
    }

    private void functionB(int a, int b){
        //do other stuff
    }
}

I've used Runnable because it's a ready out-of-the-box thing. But if you have some sort of complicated signature, you can easily declare a functional interface: that is, an interface with just one method (doesn't matter what the method is called if you're going to use lambdas). Then iter would accept a parameter of that interface type, and the lambdas will generally look like (par1, par2) -> callYourFunction(par1, someValue, par2, someOtherValue). Those “values” are kind of “bound”, that is, copied much like you'd do with [=] in C++. But that's only as far as local variables are concerned (and they have to be effectively final anyway, as @Jorn mentions). But you can access fields of the enclosing class absolutely freely (meaning you can even change them). That's technically because this is captured by value, just like locals, and therefore you can modify the object this points to.

于 2016-07-20T18:56:21.917 回答