15

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

    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 仅限于接口?

接口和使“功能类”不安全/不可预测/不合理的类之间的本质区别是什么?

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

4

3 回答 3

22

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

然而,如果你拉这个字符串一段时间,你会意识到它会拖累你很多复杂性和限制——为了少数用例。

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

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

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

在我们对现有代码库及其对单一抽象方法接口和抽象类的使用进行了分析之后,“lambdas 只是对象的便捷语法”世界观的棺材中的最后一颗钉子出现了。我们发现只有很小一部分是基于抽象类的。让所有 lambdas 负担一种方法的复杂性和性能问题似乎很愚蠢,这种方法只会使不到 1% 的使用受益。因此,我们做出了“勇敢”的决定,放弃了这个用例,以便为其他 99% 以上的用户获得好处。

于 2015-07-18T13:42:49.503 回答
8

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

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

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

这很难验证,而且在大多数情况下,约束并不满足,因为这就是首先使用 anabstract class而不是 an的原因interface。如果 lambda 表达式被定义为只是内部类的替代品,那么它就可以工作,因此不允许共享和重用实例,但这不是 lambda 表达式,即使它们被用作简单的内部类替代品很多情况下,不用考虑函数式编程……</p>

于 2015-07-17T19:12:09.330 回答
6

除了其他精彩的答案之外,应该提到的是,如果你真的需要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")));
于 2015-07-19T03:44:33.323 回答