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

Why can't @FunctionalInterface be applied to a SAM abstract base class

我刚开始学习 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 注释只能应用于接口(足够公平)并且到目前为止据我所知,抽象 classes 没有等效的注释。 RouteBuilder 当然是一个抽象 class.

为什么 lambda 仅限于接口?

接口和 class 之间的本质区别是什么 "functional class" unsafe/unpredictable/unreasonable?

我能理解如果有一些限定符,比如抽象方法必须是public,但我不知道为什么上面的不合理。

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

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

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

这很难验证,好吧,在大多数情况下,不满足约束条件,因为这就是首先使用 abstract class 而不是 interface 的原因。如果 lambda 表达式被定义为仅替换内部 classes 并且因此不允许共享和重用实例,它可能会起作用,但这不是 lambda 表达式,即使它们被用作很多情况下简单的内部 class 替换,没有考虑函数式编程…

这是 JSR-335 专家组中最困难且争论最广泛的决定之一。一方面,单一抽象方法抽象 class 可能是 lambda 的合理转换目标似乎是完全合理的。而且,如果您的心智模型是 "lambdas are just compact anonymous classes",那么这将是一个完全合理的想法。

但是,如果你拉动这个字符串一段时间,你会发现它给你带来了很多复杂性和限制——为了少数用例。

最糟糕的事情之一就是 lambda 体内名称的含义,作为一种特殊情况,this 的含义。在 inner class 的正文中,有一个非常复杂的查找规则 ("comb lookup"),因为内部 class 中的名称可以引用成员可以从词法环境中捕获超类型 的。 (例如,许多错误和谜题围绕着在内部 class 主体中使用 this 而不是 Outer.this。)如果我们允许将 lambda 转换为抽象 SAM classes,我们会有两个糟糕的选择;用内部 classes 可怕的名称查找复杂性污染所有 lambda,或者允许转换为抽象 class 目标但限制访问,使 lambda 主体不能引用基 class 的成员] (这会导致其自身的混乱。)我们得到的结果规则非常清晰:除了 lambda 参数形式之外,lambda 体内的名称(包括 this,这只是一个名称)的确切含义他们的意思是立即在 lambda 主体之外。

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

此外,如果您有一个单一的抽象方法抽象 class 并且希望能够使用 lambda 来创建它们,那么有一个简单的方法可以实现这一点——定义一个工厂方法,它接受一个功能接口作为参数。 (我们在 Java 8 中为 ThreadLocal 添加了一个工厂方法来执行此操作。)

在我们对现有代码库及其对单一抽象方法接口和抽象 classes 的使用进行分析后,"lambdas are just convenient syntax for objects" 世界观的棺材上的最后一颗钉子来了。我们发现只有很小一部分是基于抽象 classes。让所有 lambda 都承担一种只会让不到 1% 的用户受益的方法的复杂性和性能问题似乎很愚蠢。因此,我们做出 "brave" 决定取消此用例,以便获得为其他 99+% 带来的好处。

除了其他精彩的答案之外,应该提到的是,如果您真的需要经常创建这样的 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")));