Are there delegates in Java 8?

2019-02-06 12:36发布

问题:

Are there delegates in Java 8?

If not, how do we have lambda expressions in JDK 8 without delegates?

What are method references? Are they the same as delegates?

回答1:

There are no delegates in JDK 8. Under the hood lambdas are instances of functional interfaces (an interface with exactly one abstract method). Depending on where you are passing your lambda the compiler can figure out what interface it is implementing. For example the Collections.sort method accepts a Comparator instance as a second parameter. The Comparator happens to be a functional interface so the compiler will check if the lambda you are passing matches the abstract method in Comparator or not.

A Method reference is just a simplification. When your lambda is just calling an existing method you can use this new syntax to simplify the construct. The example from the linked tutorial shows it pretty well:

instead of:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

it's simpler with method reference:

Arrays.sort(rosterAsArray, Person::compareByAge);

Have a look on the lambdafaq.



回答2:

Thanks to Pacerier's comment on the question, here is a way to accomplish what a C# (one-function) delegate does, even in Java 7 or below.

// This defines the 'delegate'.
public interface IA {
    int f(int a);
}

public class MyClass {
    // f1 and f2 have the same signature as 'IA.f'.
    private int f1(int a) {
        return a + 1;
    }

    private int f2(int a) {
        return 2 * a;
    }

    // These wrappers are one way to return a 'delegate'.
    // Each wrapper creates one instance of an anonymous class.
    // Notice that we did not have to declare MyClass as implementing IA,
    // and that we can wrap different methods of MyClass into 'IA's.
    // Contrast this with 'MyClass implements IA', which would require
    // a method 'f' in 'MyClass', and would not provide a way to
    // delegate to different methods of 'MyClass'.
    public IA wrapF1() {
        return (new IA(){
            public int f(int a) {
                return f1(a);
            }
        });
    }

    public IA wrapF2() {
        return (new IA(){
            public int f(int a) {
                return f2(a);
            }
        });
    }

    // returns a 'delegate', either to 'f1' or 'f2'.
    public IA callMe(boolean useF2) {
        if (!useF2)
            return wrapF1();
        else
            return wrapF2();
    }

}

usage

...
// Create and use three 'delegates'.
// Technically, these are not quite the same as C# delegates,
// because we have to invoke a method on each.
// That is, we must do 'ia0.f(..)' instead of 'ia0(..)'.
// Nevertheless, it satisfies the design requirement.
MyClass c = new MyClass();
IA ia0 = c.wrapF1();
IA ia1 = c.callMe(false);
IA ia2 = c.callMe(true);
int result0 = ia0.f(13);
int result1 = ia1.f(13);
int result2 = ia2.f(13);

yields

result0: 14    <-- f1, so 13 + 1
result1: 14    <-- f1, so 13 + 1
result2: 26    <-- f2, so 2 * 13

NOTE: If you only need a single implementation per-class of a given 'delegate', then the simpler solution is to directly implement the interface on the class. Here is an example. The class already had f3, and now it is extended to implement IA:

public class MyClass2
        implements IA {
    private int baseValue;
    private int anotherValue;

    public MyClass2(int baseValue, int anotherValue) {
        this.baseValue = baseValue;
        this.anotherValue = anotherValue;
    }

    public int f3(int v1, int v2) {
        return 2 * v1 + v2;
    }

    public int f(int a) {
        return f3(baseValue + a, anotherValue);
    }
}

IA ia3 = new MyClass2(10, 3);
int result3 = ia3.f(13);   // = f3(10 + 13) = 2 * 23 + 3 = 49

In this case, it is no different than any other interface implementation. The point is that the design concept return a function that matches a specified signature can be satisfied, with a bit of extra coding, using Java interfaces. In the second, simpler, case, the interface is placed directly on the class. In the first, more general, case, the interface is placed on an anonymous instance of an anonymous inner class. For clarity and easy access, I isolate those 'delegate creators' in wrapper functions.

It is true that the result isn't quite the same as a C# delegate, because one has to do ia.f() rather than ia(). Nevertheless, the design goal has been met.


NOTE: In Java 8, the coding is simplified by use of lambdas. I'm not using Java 8, so I won't include the Java 8 implementation here. (Anyone is welcome to submit an edit that adds that implementation. I suggest showing new bodies below for my wrapF1() and wrapF2() above, as that would make it easy to compare Java 7 and Java 8 versions.)