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

cod*_*mer 42 java lambda interface java-8 default-method

在Java 8上阅读本教程,其中作者展示了代码:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后说

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

Formula formula = (a) -> sqrt( a * 100);
Run Code Online (Sandbox Code Playgroud)

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

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

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

Sot*_*lis 27

这或多或少是一个范围问题.来自JLS

与出现在匿名类的声明,名称的含义和代码this,并super在一个lambda身体出现关键词,与引用声明的可访问性一起,是同周围环境(除了拉姆达参数引入新的名称).

在你试图的例子中

Formula formula = (a) -> sqrt( a * 100);
Run Code Online (Sandbox Code Playgroud)

范围不包含名称声明sqrt.

这也在JLS中暗示过

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

我认为它本可以实施.他们选择不允许它.

  • @codegasmer _Scope_定义了在程序中使用名称/标识符的位置.它在JLS中定义,[这里](http://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.3).那里有例子. (3认同)

Pau*_*ton 13

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

它打印出类似的东西

Main@f6f4d33
Main@f6f4d33
Run Code Online (Sandbox Code Playgroud)

换句话说,this是一个Main,而不是lambda表达式创建的对象.

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

Formula 虽然是一个功能界面和代码

Formula f = a -> a;
Run Code Online (Sandbox Code Playgroud)

为我编译并运行没有任何问题.

虽然你不能使用lambda表达式,你可以使用匿名类来完成它,如下所示:

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


zap*_*apl 6

这不完全正确.可以在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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

Formula formula = (a) -> sqrt( a * 100);
Run Code Online (Sandbox Code Playgroud)

Formula通过lambda表达式直接定义a 作为函数接口.

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

Formula formula = (a) -> 2 * a * a + 1;
Run Code Online (Sandbox Code Playgroud)

Formula formula = (a) -> sqrt( a * 100);
Run Code Online (Sandbox Code Playgroud)

失败,因为它试图访问(this.)sqrt方法,但它不能.根据规范,Lambdas从它们的周围继承它们的范围,这意味着thislambda内部指的是与它直接相同的东西.sqrt外面没有方法.

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

interface NotRunnable {
    void notRun();
}

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

private final NotRunnable r2 = r::run;
Run Code Online (Sandbox Code Playgroud)

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

  • @zeroflagL 这可能不是 100% 准确,但对我来说是一种心理模型(这就是为什么我在那里写了“你可以这样想象”)。它也有点类似于 [`invokedynamic` 在幕后所做的](http://stackoverflow.com/questions/30002380/why-are-java8-lambdas-invoked-using-invokedynamic):实际上不是为 lambdas 创建类,而是动态调用方法。 (2认同)
  • 示例:https://gist.github.com/anonymous/e1d0e3d5f05688986018 - lambda表达式中的代码被编译为名为`private static void lambda $ new $ 0()`的静态方法.在这种情况下,那里的代码无法反映它自己的类型,因为它没有.相同的代码甚至用于两种类型,因此编译器无法在该点添加类型. (2认同)
  • @zeroflagL:实际上,将lambda表达式看作没有类型的东西(或者在将来的Java版本中可能具有真正功能类型的东西)转换为接口类型是完全有效的. (2认同)