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

Sar*_*ips 12 java-8 functional-interface

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

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

我(合理地恕我直言)尝试替换

    context.addRoutes(()->from("file:data/inbox?noop=true").to("file:data/outbox"));
Run Code Online (Sandbox Code Playgroud)

但那是无效的.

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

为什么lambdas仅限于接口?

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

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

Bri*_*etz 20

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

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

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

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

此外,如果你有一个单抽象方法抽象类并希望能够使用lambdas来创建它们,那么有一个简单的路径来实现它 - 定义一个以函数接口作为参数的工厂方法.(我们ThreadLocal在Java 8中添加了一个工厂方法来执行此操作.)

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


Hol*_*ger 7

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

例如,lambda表达式不会从最终实现的类型继承成员.所以在你的情况下,即使RouteBuilder是一个功能接口它也不会工作,因为你的lambda表达式不会继承from你想要调用的方法.类似地,lambda表达式之外的含义thissuper它们相同,并且不引用将在之后表示函数的RouteBuilder实例(即实例).

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

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


Tag*_*eev 5

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

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

并像这样使用它:

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