为什么我们不能在 lambda 表达式中使用默认方法?

Why can we not use default methods in lambda expressions?

我正在阅读 this tutorial on Java 8 作者展示代码的地方:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

然后说

Default methods cannot be accessed from within lambda expressions. The following code does not compile:

Formula formula = (a) -> sqrt( a * 100);

但是他没有解释为什么不可能。我运行代码,它给出了一个错误,

incompatible types: Formula is not a functional interface`

那为什么不可以或者报错是什么意思呢?该接口满足功能接口具有一个抽象方法的要求。

这不完全正确。 lambda 表达式中可以使用默认方法。

interface Value {
    int get();

    default int getDouble() {
        return get() * 2;
    }
}

public static void main(String[] args) {
    List<Value> list = Arrays.asList(
            () -> 1,
            () -> 2
        );
    int maxDoubled = list.stream()
        .mapToInt(val -> val.getDouble())
        .max()
        .orElse(0);
    System.out.println(maxDoubled);
}

按预期打印 4 并在 lambda 表达式中使用默认方法 (.mapToInt(val -> val.getDouble()))

你文章的作者在这里试图做什么

Formula formula = (a) -> sqrt( a * 100);

是直接通过lambda表达式定义一个Formula作为函数式接口。

效果很好,在上面的示例代码中,Value value = () -> 5 或使用 Formula 作为接口,例如

Formula formula = (a) -> 2 * a * a + 1;

但是

Formula formula = (a) -> sqrt( a * 100);

失败是因为它试图访问 (this.)sqrt 方法但它不能。 根据规范,Lambda 从周围环境继承其范围,这意味着 this 在 lambda 内部与直接在其外部指的是同一事物。而且外面没有sqrt方法

我个人对此的解释:在 lambda 表达式内部,lambda 将是什么具体的功能接口并不是很清楚 "converted"。比较

interface NotRunnable {
    void notRun();
}

private final Runnable r = () -> {
    System.out.println("Hello");
};

private final NotRunnable r2 = r::run;

同一个 lambda 表达式可以 "cast" 多种类型。我认为它好像 lambda 没有类型。它是一种特殊的无类型函数,可用于具有正确参数的任何接口。但是这个限制意味着你不能使用 future 类型的方法,因为你不知道它。

默认方法只能通过对象引用访问,如果你想访问默认方法,你将有一个函数接口的对象引用,在 lambda 表达式方法体中你将没有,所以无法访问它。

你收到一个错误 incompatible types: Formula is not a functional interface 因为你没有提供 @FunctionalInterface 注释,如果你提供了你会得到 'method undefined' 错误,编译器会强制你创建一个方法在 class.

@FunctionalInterface 您的接口必须只有一个抽象方法,但它缺少注释。

但是静态方法没有这样的限制,因为我们可以像下面这样不使用对象引用来访问它。

@FunctionalInterface
public interface Formula {

    double calculate(int a);

    static double sqrt(int a) {
        return Math.sqrt(a);
    }
}

public class Lambda {

    public static void main(String[] args) {
    Formula formula = (a) -> Formula.sqrt(a);
        System.out.println(formula.calculate(100));
    }

}

这或多或少是一个范围问题。 From the JLS

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).

在您尝试的示例中

Formula formula = (a) -> sqrt( a * 100);

范围不包含名称 sqrt.

的声明

JLS 中也暗示了这一点

Practically speaking, it is unusual for a lambda expression to need to talk about itself (either to call itself recursively or to invoke its other methods), while it is more common to want to use names to refer to things in the enclosing class that would otherwise be shadowed (this, toString()). If it is necessary for a lambda expression to refer to itself (as if via this), a method reference or an anonymous inner class should be used instead.

我认为它可以实现。他们选择不允许。

Lambda 表达式与匿名 classes 的工作方式完全不同,因为 this 表示与表达式周围范围内相同的东西。

比如这样编译

class Main {

    public static void main(String[] args) {
        new Main().foo();
    }

    void foo() {
        System.out.println(this);
        Runnable r = () -> {
            System.out.println(this);
        };
        r.run();
    }
}

它会打印类似

的内容
Main@f6f4d33
Main@f6f4d33

也就是说this是一个Main,而不是lambda表达式创建的对象。

所以你不能在你的 lambda 表达式中使用 sqrt 因为 this 引用的类型不是 Formula 或者子类型,并且它没有 sqrt 方法。

Formula 是函数式接口,代码

Formula f = a -> a;

为我编译和运行没有任何问题。

虽然你不能为此使用 lambda 表达式,但你可以使用匿名 class 来实现,如下所示:

Formula f = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    }
};

这对讨论没什么帮助,但我还是觉得很有趣。

另一种看待问题的方法是从自引用 lambda 的角度来考虑它。

例如:

Formula formula = (a) -> formula.sqrt(a * 100);

这似乎是有道理的,因为在执行 lambda 时,formula 引用必须已经被初始化(即无法做到 formula.apply()直到 formula 被正确初始化,在这种情况下,从 lambda 的主体,apply 的主体应该可以引用相同的变量)。

然而这也不起作用。有趣的是,一开始它曾经是可能的。您可以看到 Maurice Naftalin 在他的 Lambda FAQ Web Site. But for some reason the support for this feature was ultimately removed.

中记录了它

在 lambda 邮件列表的讨论中已经提到了这个问题的其他答案中给出的一些建议。