Search code examples
java-8functional-interface

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


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.


Solution

  • 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+%.