47

我正在阅读有关 Java 8 的本教程,作者在其中展示了代码:

interface Formula {
    double calculate(int a);

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

然后说

无法从 lambda 表达式中访问默认方法。以下代码无法编译:

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

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

不兼容的类型:公式不是功能接口`

那么为什么不可能或错误的含义是什么?该接口满足了具有一个抽象方法的功能接口的要求。

4

5 回答 5

30

这或多或少是范围的问题。来自 JLS

与出现在匿名类声明中的代码不同,出现在 lambda 主体中的名称thissuper关键字的含义以及引用声明的可访问性与周围上下文中的相同(除了 lambda 参数引入了新名称)。

在您尝试的示例中

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

范围不包含名称的声明sqrt

JLS 中也暗示了这一点

实际上,lambda 表达式需要谈论自己是不寻常的(递归地调用自己或调用它的其他方法),而更常见的是希望使用名称来引用封闭类中的事物否则被遮蔽(this, toString())。如果 lambda 表达式需要引用自身(就像 via 一样this),则应使用方法引用或匿名内部类。

我认为它可能已经实施。他们选择不允许。

于 2015-10-13T17:37:59.777 回答
14

Lambda 表达式的工作方式与匿名类完全不同,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是 a Main,而不是 lambda 表达式创建的对象。

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

Formula虽然是一个功能接口,但代码

Formula f = a -> a;

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

尽管您不能为此使用 lambda 表达式,但您可以使用匿名类来执行此操作,如下所示:

Formula f = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    }
};
于 2015-10-13T17:38:07.397 回答
9

这并不完全正确。默认方法可以在 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 表达式定义 a Formula,它用作功能接口。

在上面的示例代码中,Value value = () -> 5或者Formula作为接口,例如

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

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

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

我对此的个人解释:在 lambda 表达式中,并不清楚 lambda 将被“转换”的具体功能接口。相比

interface NotRunnable {
    void notRun();
}

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

private final NotRunnable r2 = r::run;

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

于 2015-10-13T17:21:03.390 回答
2

这对讨论没有什么好处,但无论如何我发现它很有趣。

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

例如:

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

看起来这应该是有道理的,因为当 lambda 被执行时,formula引用必须已经被初始化(即在正确初始化formula.apply()之前没有办法做formula,在这种情况下,从lambda,的主体apply,应该可以引用相同的变量)。

但是,这也不起作用。有趣的是,一开始它曾经是可能的。您可以看到 Maurice Naftalin 在他的Lambda FAQ 网站中记录了它。但由于某种原因,最终取消了对该功能的支持。

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

于 2015-10-15T03:04:19.187 回答
1

默认方法只能通过对象引用访问,如果您想访问默认方法,您将拥有功能接口的对象引用,在 lambda 表达式方法主体中您将没有,因此无法访问它。

你得到一个错误incompatible types: Formula is not a functional interface是因为你没有提供@FunctionalInterface注释,如果你提供了你会得到'方法未定义'错误,编译器会强制你在类中创建一个方法。

@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));
    }

}
于 2015-10-13T17:25:52.717 回答