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

时间:2015-10-13 17:06:12

标签: java lambda interface java-8 default-method

我正在阅读作家展示代码的this tutorial on Java 8

interface Formula {
    double calculate(int a);

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

然后说

  

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

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

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

  

不兼容的类型:公式不是功能界面`

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

5 个答案:

答案 0 :(得分:25)

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

  

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

在您尝试的例子中

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

范围不包含名称sqrt的声明。

这也在JLS中暗示

  

实际上,lambda表达式需要不常见   谈论自己(要么递归地调用自己,要么调用它   其他方法),而更常见的是想要使用名称来引用   在封闭类中的东西,否则将被遮蔽   (thistoString())。 如果有必要使用lambda表达式   引用自身(好像通过this),方法引用或匿名   应该使用内部类。

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

答案 1 :(得分:12)

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

换句话说,thisMain,而不是lambda表达式创建的对象。

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

Formula是一个功能界面,代码

Formula f = a -> a;

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

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

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

答案 2 :(得分: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);
}

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

您的文章作者试图在此处做什么

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

是直接通过lambda表达式定义Formula,它作为功能接口。

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

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

但是

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

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

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

interface NotRunnable {
    void notRun();
}

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

private final NotRunnable r2 = r::run;

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

答案 3 :(得分:1)

这对讨论几乎没有什么影响,但无论如何我发现它很有趣。

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

例如:

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

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

然而,这也不起作用。有趣的是,它在开始时曾经是可能的。你可以看到Maurice Naftalin在他的Lambda FAQ Web Site中记录了它。但出于某种原因,对此功能的支持是ultimately removed

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

答案 4 :(得分:-1)

默认方法只能通过对象引用访问,如果要访问默认方法,你有一个功能接口的对象引用,在lambda表达式方法体中你不会这样,所以无法访问它。

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

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

}