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

淺唱寂寞╮ 提交于 2019-12-17 16:28:25

问题


I'm just starting to learn Camel and the first thing I see is

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

which I (reasonably IMHO) try to replace with

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

but that is not valid.

As I dig, I discover that lambdas apply to functional interfaces (which is be implied, if the interface qualifies) but that the @FunctionalInterface annotation can only be applied to interfaces (fair enough) and there is, as far as I can tell, no equivalent annotation for abstract classes. RouteBuilder is, of course, an abstract class.

Why are lambdas restricted to interfaces?

What is the essential difference between an interface and a class that makes a "functional class" unsafe/unpredictable/unreasonable?

I could understand if there was some qualifier, such as the abstract method had to be public, but I am at a loss to explain why the above is unreasonable.


回答1:


This was one of the most difficult and extensively debated decisions in the JSR-335 Expert Group. On the one hand, it seems entirely reasonable that a single-abstract-method abstract class could be a reasonable conversion target for lambdas. And, if your mental model is "lambdas are just compact anonymous classes", then this would have been a totally reasonable idea.

However, if you pull on this string for a while, you realize it drags with you a lot of complexity and constraints -- for the sake of a minority use case.

One of the worst thing that this drags with it is the meaning of names inside a lambda body, and as a special case, the meaning of this. Within the body of an inner class, there is a terribly complicated lookup rule ("comb lookup") because names inside an inner class could refer to members of a supertype or could be captured from the lexical environment. (For example, many bugs and puzzlers revolve around using this, rather than Outer.this, in inner class bodies.) If we allowed lambda conversion to abstract SAM classes, we'd have two bad choices; pollute all of lambdas with the terrible name lookup complexity of inner classes, or allow conversion to abstract class targets but restrict access such that the lambda body could not refer to members of the base class (which would cause its own sort of confusion.) The resulting rule we get is very clean: apart from the lambda parameter formals, names (including this, which is just a name) inside the lambda body mean exactly what they mean immediately outside the lambda body.

Another problem converting lambdas to inner classes drags with it is object identity, and the attendant loss of VM optimizations. An inner class creation expression (e.g., new Foo() { }) is guaranteed to have a unique object identity. By not committing so strongly to object identity for lambdas, we free the VM to make a lot of useful optimizations that it could otherwise not make. (As a result, lambda linkage and capture is already faster than for anonymous classes -- and there's still a deep pipeline of optimizations we have yet to apply.)

Further, if you have a single-abstract-method abstract class and want to be able to use lambdas to create them, there's an easy path to enabling this -- define a factory method that takes a functional interface as an argument. (We added a factory method for ThreadLocal in Java 8 that does this.)

The final nail in the coffin for the "lambdas are just convenient syntax for objects" view of the world came after we did an analysis of existing codebases and their use of single-abstract-method interfaces and abstract classes. We found that only a very small percentage were based on abstract classes. It seemed silly to burden all lambdas with the complexity and performance problems of an approach that was only going to benefit less than 1% of the uses. So we made the "brave" decision to cut loose this use case in order to reap the benefits that this enabled for the other 99+%.




回答2:


Lambda expressions define functions not methods. There is obviously a technical relationship between them, but it’s different in the conceptional view and how it works on the source code level.

E.g. a lambda expression does not inherit members from the type that it will eventually implement. So in your case, it won’t work even if RouteBuilder was a functional interface as your lambda expression will not inherit the from method you want to invoke. Similarly, the meaning of this and super are the same as outside the lambda expression and do not refer to the instance which will represent the function afterwards (i.e. the RouteBuilder instance).

That said, it would not be unreasonable to expand the feature to implement abstract classes which behave like interfaces but that imposes several constraints which are hard to check. While it is easy to verify that a class has exactly one abstract method and an accessible no-arg constructor, the class should also be free of any mutable state and the construction of an instance of that class should also be side-effect free, so that the freedom of the JVM to cache and re-use lambda instances and share them between different creation sites has no impact on the behavior of a program.

That’s hard to verify and well, in most cases, the constraints are not satisfied as that’s the reason for using an abstract class rather than an interface in the first place. It could work if lambda expressions were defined to be just a replacement for inner classes and hence the sharing and re-using of instances were not allowed, but that’s not what lambda expressions are, even if they are used as a simple inner class replacement in a lot of cases, without thinking about functional programming…




回答3:


In addition to other wonderful answers it should be mentioned that if you really need to create such RouteBuilder objects very often, you can create a helper method like this:

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

And use it like this:

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


来源:https://stackoverflow.com/questions/31482093/why-cant-functionalinterface-be-applied-to-a-sam-abstract-base-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!