为什么不能将@FunctionalInterface应用于SAM抽象基类

时间:2015-07-17 18:08:19

标签: java-8 functional-interface

我刚刚开始学习Camel,我看到的第一件事是

    context.addRoutes(new RouteBuilder() {
        public void configure() {
            from("file:data/inbox?noop=true").to("file:data/outbox");
        }
    });

我(合理地恕我直言)尝试用

替换
    context.addRoutes(()->from("file:data/inbox?noop=true").to("file:data/outbox"));

但这无效。

当我挖掘时,我发现lambdas适用于功能接口(如果接口符合条件,则暗示)但@FunctionalInterface注释只能应用于接口(足够公平)并且就我而言可以告诉,抽象类没有等效的注释。当然,RouteBuilder是一个抽象类。

为什么lambdas仅限于接口?

界面和类之间的本质区别是什么?#34;功能类"不安全/不可预测/不合理?

我能理解是否有一些限定符,例如抽象方法必须公开,但我无法解释为什么上述不合理。

3 个答案:

答案 0 :(得分:19)

这是JSR-335专家组中最困难和最广泛争议的决定之一。一方面,单抽象方法抽象类可能是lambdas的合理转换目标似乎是完全合理的。并且,如果你的心理模型是" lambdas只是紧凑的匿名类",那么这将是一个完全合理的想法。

但是,如果你拉上这个字符串一段时间,你就会意识到它会带来很多复杂性和限制 - 为了少数用例。

其中最糟糕的事情之一就是lambda体内名称的含义,以及this的含义。在内部类的主体内,有一个非常复杂的查找规则("梳子查找")因为内部类中的名称可以引用超类型的成员或可以从词汇环境中捕获。 (例如,许多错误和益智游戏围绕在内部类体中使用this而不是Outer.this。)如果我们允许lambda转换来抽象SAM类,我们有两个错误的选择;使用内部类的可怕名称查找复杂性来污染所有lambda,或允许转换为抽象类目标但限制访问,使得lambda主体无法引用基类的成员(这会导致其自身的混淆。)我们得到的结果是非常干净的:除了lambda参数形式之外,lambda体内的名称(包括this,这只是一个名字)正是它们在lambda体外的意思。

将lambda转换为内部类的另一个问题是对象标识,以及随之而来的VM优化丢失。内部类创建表达式(例如,new Foo() { })保证具有唯一的对象标识。通过不对lambdas的对象标识做出如此强烈的承诺,我们释放VM以进行许多有用的优化,否则它们无法做到。 (因此,lambda链接和捕获已经比匿名类更快了 - 而且我们尚未应用优化的深层管道。)

此外,如果你有一个单抽象方法抽象类,并希望能够使用lambdas来创建它们,那么启用它是一个简单的途径 - 定义一个带有功能接口的工厂方法作为一个论点。 (我们在Java 8中为ThreadLocal添加了一个工厂方法来执行此操作。)

对于" lambdas的棺材中的最后钉子只是对象的方便语法"在我们对现有代码库及其对单抽象方法接口和抽象类的使用进行分析之后,我们看到了这个世界。我们发现只有很小的百分比基于抽象类。用一种方法的复杂性和性能问题给所有lambdas带来负担似乎很愚蠢,这种方法只能使用不到1%的用途。所以我们做了勇敢的"决定削减这个用例,以便获得其他99 +%的优势。

答案 1 :(得分:6)

Lambda表达式定义函数而非方法。它们之间显然存在技术关系,但它在概念视图中的不同以及它在源代码级别上的工作方式。

E.g。 lambda表达式不会从最终实现的类型继承成员。因此,在您的情况下,即使RouteBuilder是函数接口,它也不会起作用,因为您的lambda表达式不会继承您要调用的from方法。类似地,thissuper的含义与lambda表达式之外的含义相同,并且不引用将在之后表示函数的实例(即RouteBuilder实例)。

也就是说,扩展该功能以实现abstract类的行为是不合理的,这些类的行为类似于interface,但这会产生一些难以检查的约束。虽然很容易验证一个类只有一个abstract方法和一个可访问的无参数构造函数,但该类也应该没有任何可变状态,并且该类的实例的构造也应该是 - 效果自由,因此JVM自由缓存和重用lambda实例并在不同的创建站点之间共享它们对程序的行为没有影响。

这很难验证,在大多数情况下,不满足约束,因为这是首先使用abstract class而不是interface的原因。如果lambda表达式被定义为只是内部类的替换,那么它可以工作,因此不允许共享和重用实例,但这不是lambda表达式的内容,即使它们被用作简单的内部类替换。很多情况下,没有考虑函数式编程......

答案 2 :(得分:5)

除了其他精彩的答案之外,还应该提到的是,如果你真的需要经常创建这样的RouteBuilder个对象,你可以创建一个这样的辅助方法:

public static RouteBuilder fromConfigurator(Consumer<RouteBuilder> configurator) {
    return new RouteBuilder() {
        public void configure() {
            configurator.accept(this);
        }
    }
}

并像这样使用它:

context.addRoutes(fromConfigurator(
    rb->rb.from("file:data/inbox?noop=true").to("file:data/outbox")));