Why isn't @FunctionalInterface used on all the interfaces in the JDK that qualify?
Asked Answered
B

3

19

Java 8 gave us many fun ways to use functional interfaces and with them a new annotation: @FunctionalInterface. Its job is to tell the compiler to yell at us if we fail to stick to the rules of a functional interface (only one abstract method that needs overriding please).

There are 43 interfaces in the java.util.function package with this annotation. A search of jdk.1.8.0/src for @FunctionalInterface only turns up 57 hits. Why are the other interfaces (such as AutoCloseable) that could have added @FunctionalInterface still missing it?

There is a bit of a vague hint in the annotations documentation:

"An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface"

Is there any good reason NOT to intend that an interface I've designed (that may simply happen to be a functional interface) not be used as one? Is leaving it off an indication of anything besides not realizing it could have been added?

Isn't adding abstract methods to any published interface going to screw anyone implementing it, functional or not? I feel cynical assuming they just didn't bother to hunt them all down but what other explanation is there?

Update: After looking over "Should 'Comparable' be a 'Functional interface'?" I find I still have nagging questions. When a Single Method Interface and a Functional Interface are structurally identical what's left to be different? Is the difference simply the names? Comparable and Comparator are close enough to the same semantically. Turns out they are different structurally though so still not the best example...

Is there a case when an SMI is structurally fine to use as a Functional Interface but still discouraged over the semantic meaning of the name of the interface and the method? Or perhaps the contract implied by the Javadocs?

Bergh answered 27/1, 2015 at 8:36 Comment(9)
Every @FunctionalInterface is an SMI, but not every SMI is a @FunctionalInterface. I don't see the argument of using AutoCloseable in a lambda.Sabin
possible duplicate of Should 'Comparable<T>' be a 'Functional interface'?Quartis
Short answer: it is.Quartis
@BoristheSpider could you point us to a good definition of SMI? Google is not feeling helpful at the moment.Bergh
You defined it yourself - it's the language definition of a type useable as a lambda. A "Single Method Interface".Sabin
@FunctionalInterface is a statement of intent by the interface author, not a statement of eligibility. If you declare this intent, tools will act on it (the compiler will give errors if there are too many methods, javadoc will carry the intent into the spec.)Atronna
@BrianGoetz we've already established that it's presence conveys intent. The question is about when an author should or should not intend that an interface be used functionally. I write interfaces all the time. If someone goes and uses one of mine that I never imagined would be used functionally why should I or anyone care? I've identified three possible things to care about in an interface: structure, names, and javadocs. I don't think the 4th is the mind of it's creator. I'm trying to identify some objective thing that makes a SMI inappropriate for use as a functional interface.Bergh
@BrianGoetz What was the rationale for allowing use of all SAM/SMI in lambdas/method references, and not only those marked as FunctionalInterface? I think it's this disconnect that also causes the confusion about the FunctionalInterface annotation. At least for me, it seems like the annotation is meant to prevent the use of an interface not annotated in an unintended way, yet it does not. And people wonder, how bad is it if I still use it in a lamda?Anthracoid
@DidierA. Existing Java libraries are full of SAM interfaces. Requiring a declaration-site opt in would mean, among other things, that these libraries would have to be updated before people could use them as lambda targets. And, for no good reason; these all can be used with single-method anonymous classes, and not allowing lambdas here would just be restrictive for its own sake. As to "how bad would it be", the answer is "not bad at all."Atronna
H
25

Well, an annotation documenting an intention would be useless if you assume that there is always that intention given.

You named the example AutoCloseable which is obviously not intended to be implemented as a function as there’s Runnable which is much more convenient for a function with a ()->void signature. It’s intended that a class implementing AutoCloseable manages an external resource which anonymous classes implemented via lambda expression don’t do.

A clearer example is Comparable, an interface not only not intended to be implemented as a lambda expression, it’s impossible to implement it correctly using a lambda expression.


Possible reasons for not marking an interface with @FunctionalInterface by example:

  • The interface has programming language semantics, e.g. AutoClosable or Iterable (that’s unlikely to happen for your own interfaces)
  • It’s not expected that the interface has arbitrary implementations and/or is more an identifier than the actual implementation, e.g. java.net.ProtocolFamily, or java.lang.reflect.GenericArrayType (Note that the latter would also inherit a default implementation for getTypeName() being useless for lambda implementations as relying on toString())
  • The instances of this interface should have an identity, e.g. java.net.ProtocolFamily, java.nio.file.WatchEvent.Modifier, etc. Note that these are typically implemented by an enum

    Another example is java.time.chrono.Era which happens to have only a single abstract method but its specification says “Instances of Era may be compared using the == operator.”

  • The interface is intended to alter the behavior of an operation for which an implementation of the interface without inheriting/implementing anything else makes no sense, e.g. java.rmi.server.Unreferenced
  • It’s an abstraction of common operations of classes which should have more than just these operations, e.g. java.io.Closeable, java.io.Flushable, java.lang.Readable
  • The expected inheritance is part of the contract and forbids lambda expression implementations, e.g. in java.awt: ActiveEvent should be implemented by an AWTEvent, PrinterGraphics by a Graphics, the same applies to java.awt.print.PrinterGraphics (hey, two interfaces for exactly the same thing…), wheras javax.print.FlavorException should be implemented by a javax.print.PrintException subclass
  • I don’t know whether the various event listener interfaces aren’t marked with @FunctionalInterface for symmetry with other multi-method event listener that can’t be functional interfaces, but actually event listeners are good candidates for lambda expressions. If you want remove a listener at a later time, you have to store the instance but that’s not different to, e.g. inner class listener implementations.
  • The library maintainer has a large codebase with more than 200 candidate types and not the resources to discuss for every interface whether it should be annotated and hence focuses on the primary candidates for being used in a functional context. I’m sure, that, e.g. java.io.ObjectInputValidation, java.lang.reflect.InvocationHandler, juc RejectedExecutionHandler & ThreadFactory wouldn’t be bad as @FunctionalInterface but I have no idea whether, e.g. java.security.spec.ECField makes a good candidate. The more general the library is, the more likely users of the library will be able to answer that question for a particular interface they are interested in but it would be unfair to insist on the library maintainer to answer it for all interfaces.

    In this context it makes more sense to see the presence of a @FunctionalInterface as a message that an interface is definitely intended to be usable together with lambda expressions than to treat the absence of the annotation as an indicator for it’s being not intended to be used this way. This is exactly like the compiler handles it, you can implement every single abstract method interface using a lambda expression, but when the annotation is present it will ensure that you can use this interface in this way.

Hillis answered 27/1, 2015 at 8:44 Comment(9)
This. Certainly not "future expansion".Sabin
Yet Comparator can. Is this due to the default methods in Comparator?Bergh
@CandiedOrange Comparator takes two types T and compares them - it's a ToIntBiFunction<T,T>. Comparable compares itself to some other type - a lambda has (almost) no sense of self so this makes it impossible to implement as a lambda.Sabin
@CandiedOrange: the Comparable contract requires symmetry, i.e. a.compareTo(b) == -b.compareTo(a), that’s impossible to guaranty for a lambda expression; you can’t even declare the lambda to implement Comparable<T> with T being the class of the lambda as the class of the lambda is off specification.Hillis
@BoristheSpider Ah, so there is a structural difference here. When there is none what's left to argue against use as a functional interface? Names and javadocs?Bergh
@CandiedOrange: not being intended as a functional interface does not prevent you from implementing that interface via lambda expression or method reference. I already did this with AutoClosable and Iterable though I’m aware that that’s not in the scope of the interface’s creators intentions. There’s always an intended use and the actual use and you have to be careful about not moving too far away from the intended use, however, it would be strange if we all were always on the path foreseen by the core developers…Hillis
@Hillis spoken like a true hacker. My hats off to you. This is why I bristled at the idea of blindly following "intent". I'd rather know why I should care. : )Bergh
There is a structural difference between AutoCloseable and Runnable: checked exceptions. Callable allows checked exceptions.Bergh
Re: update, very well argued. Tons of examples. The inner class listener implementations is a good point. Studying the new functional interfaces had me thinking so structurally and so eager to reuse code that I was neglecting confusion that could be caused if I used an interface that was signalling semantic meanings beyond structural ones. Thanks for setting me straight.Bergh
C
2

Planned expansion. Just because an interface matches the requirements of an SMI now doesn't mean that expansion isn't needed later.

Cinnamon answered 27/1, 2015 at 8:43 Comment(4)
Adding abstract methods to an interface is breaking compatibility anyway. It doubt that there are plans to do this.Hillis
Namely, the set of all functional interfaces is a proper subset of the set of all SAM types. The distinction is that of intent, not of structure.Supercharger
@Hillis what you say is true for separately published libraries (such as the JDK, Guava, Apache Commons, etc), but not true of all codebases. There are plenty of codebases out there that are willing to make changes that might be binary- or source-incompatible, and can get away with it because they have a closed source base and recompile the entire thing from scratch. At which point this answer applies perfectly well.Atronna
@Brian Goetz: but in that case you can also add @FunctionalInterface to the interface and remove it in a later version without breaking anything. But this does not answer the question which is about the JDK APIHillis
M
0

In java 8, functional interface is an interface having exactly one abstract method called functional method to which the lambda expression’s parameter and return types are matched.

The java.util.function contains general purpose functional interfaces used by JDK and also available for end users. While they are not the complete set of funtional interfaces to which lambda expressions might be applicable, but they provide enough to cover common requirements. You are free to create your own functional interfaces whenever existing set are not enough.

There are many such interfaces available which deserves to be designated as functional interface but java.util.function package already provides functional interfaces for our almost all purposes.

For example look into following code.

public interface Comparable<T> {
   public int compareTo(T o);
}

@FunctionalInterface
public interface ToIntFunction<T> {
   int applyAsInt(T value);
}

public static void main(String[] args){
   ToIntFunction<String> f = str -> Integer.parseInt(str);
   Comparable<String> c = str -> Integer.parseInt(str);
}

Comparable can also take an object and derive some int type value but there is a more general dedicated interface ToIntFunction is provided to perform this task. There is no such hard rule that all the deserving interfaces should be annotated with @FunctionalInterface but to gain the advantage of lambda feature, the interface should fulfill all criterias defined by FunctionalInterface.

Meshwork answered 28/1, 2018 at 12:8 Comment(1)
It's not really about which one is more general than the other – it may be useful to have more specific functional interface for specific purposes. It is thus about the purpose itself. It makes no sense to implement Comparable with a lambda expression, and your example just shows an invalid implementation by trying to do that.Maximilien

© 2022 - 2024 — McMap. All rights reserved.