Lambda Specification, Part A: Functional Interfaces

Navigation: Overview - Part A - Part B - Part C - Part D - Part E - Part F - Part G - Part H - Part J
Sections: 9.8 - 8.4.2 - 8.4.4 - 8.4.5 - 8.4.6 - 9.6.3.8
Version 0.6.1. Copyright © 2012 Oracle America, Inc. Legal Notice.

Summary

A functional interface is an interface that has just one abstract method, and thus represents a single function contract. (In some cases, this "single" method may take the form of multiple abstract methods with override-equivalent signatures inherited from superinterfaces; in this case, the inherited methods logically represent a single method.)

In addition to the usual process of creating an interface instance by declaring and instantiating a class, instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

The function descriptor of a functional interface I is a method type—type parameters, formal parameter types, return types, and thrown types—that can be used to legally override the abstract method(s) of I.

A functional interface type may be a functional interface, a parameterization of a functional interface, or an intersection type involving a functional interface.

9.8 Functional Interfaces [New]

A functional interface is an interface that has just one abstract method, and thus represents a single function contract. (In some cases, this "single" method may take the form of multiple abstract methods with override-equivalent signatures (8.4.2) inherited from superinterfaces; in this case, the inherited methods logically represent a single method.)

More precisely, for interface I, let M be the set of abstract methods that are members of I but that do not have the same signature as any public instance method of the class Object. Then I is a functional interface if there exists a method m in M for which the following conditions hold: [jsr335-9.8-10]

In addition to the usual process of creating an interface instance by declaring (8.1) and instantiating (15.9) a class, instances of functional interfaces can be created with lambda expressions (15.27), method references (15.28), or constructor references.

The function descriptor of a functional interface I is a method type (8.2) that can be used to legally override (8.4.8) the abstract method(s) of I.

Let M be the set of abstract methods defined above for I. The descriptor of I consists of the following: [jsr335-9.8-20]

A functional interface type is one of the following: [jsr335-9.8-30]

The function descriptor of a parameterized functional interface, F<A1...An>, where A1...An are type arguments (4.5.1), is derived as follows. Let P1...Pn be the type parameters of F; types T1...Tn are derived from the type arguments according to the following rules (for 1 ≤ i ≤ n): [jsr335-9.8-40]

If F<T1...Tn> is a well-formed type, then the descriptor of F<A1...An> is the result of applying substitution [P1:=T1, ..., Pn:=Tn] to the descriptor of interface F. Otherwise, the descriptor of F<A1...An> is undefined. [jsr335-9.8-40]

The function descriptor of an intersection that is a functional interface type is the same as the function descriptor of the functional interface or parameterization of a functional interface that is an element of the intersection. [jsr335-9.8-50]

"A closure is an object that supports exactly one method: apply." - Guy Steele
Functional interface examples:
interface Runnable { void run(); }
  // Functional

interface Foo { boolean equals(Object obj); }
 // Not functional; equals is already an implicit member

interface Bar extends Foo { int compare(String o1, String o2); }
 // Functional; Bar has one abstract non-Object method

interface Comparator<T> {
 boolean equals(Object obj);
 int compare(T o1, T o2);
}
 // Functional; Comparator has one abstract non-Object method

interface Foo {
  int m();
  Object clone();
}
  // Not functional; method Object.clone is not public

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<String> arg); }
interface Z extends X, Y {}
  // Functional: two methods, but they have the same signature

interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {}
  // Functional: Y.m is a subsignature & return-type-substitutable

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}
  // Not functional: No method has a subsignature of all abstract methods

interface X { int m(Iterable<String> arg, Class c); }
interface Y { int m(Iterable arg, Class<?> c); }
interface Z extends X, Y {}
  // Not functional: No method has a subsignature of all abstract methods

interface X { long m(); }
interface Y { int m(); }
interface Z extends X, Y {}
  // Compiler error: no method is return type substitutable

interface Foo<T> { void m(T arg); }
interface Bar<T> { void m(T arg); }
interface FooBar<X, Y> extends Foo<X>, Bar<Y> {}
  // Compiler error: different signatures, same erasure

interface Foo<T, N extends Number> {
  void m(T arg);
  void m(N arg);
}
interface Bar extends Foo<String, Integer> {}
interface Baz extends Foo<Integer, Integer> {}
  // Foo is _not_ functional: different signatures for m
  // Bar is _not_ functional: different signatures for m
  // Baz is functional: same signature for m

interface Executor { <T> T execute(Action<T> a); }
  // Functional

interface X { <T> T execute(Action<T> a); }
interface Y { <S> S execute(Action<S> a); }
interface Exec extends X, Y {}
  // Functional: signatures are "the same"

interface X { <T> T execute(Action<T> a); }
interface Y { <S,T> S execute(Action<S> a); }
interface Exec extends X, Y {}
  // Compiler error: different signatures, same erasure
Function descriptor examples:
interface X { void m() throws IOException; }
interface Y { void m() throws EOFException; }
interface Z { void m() throws ClassNotFoundException; }
interface XY extends X, Y {}
interface XYZ extends X, Y, Z {}

// XY has descriptor ()->void throws EOFException
// XYZ has descriptor ()->void (throws nothing)

interface A {
  List<String> foo(List<String> arg) throws IOException, SQLTransientException;
}
interface B {
  List foo(List<String> arg) throws EOFException, SQLException, TimeoutException;
}
interface C {
  List foo(List arg) throws Exception;
}
interface D extends A, B {}
interface E extends A, B, C {}

// D has descriptor (List<String>)->List<String> throws EOFException, SQLTransientException
// E has descriptor (List)->List throws EOFException, SQLTransientException

interface G1 {
  <E extends Exception> Object m() throws E;
}
interface G2 {
  <F extends Exception> String m() throws Exception;
}
interface G extends G1, G2 {}

// G has descriptor <F extends Exception> ()->String throws F
Discussion and motivation:
  1. The definition of functional interface excludes methods in an interface that are also public methods in Object. This is to allow functional treatment of an interface type like Comparator that declares multiple abstract methods of which only one is really "new"; the other method is an explicit declaration of an abstract method that would otherwise be implicitly declared, and will be automatically implemented by any subclass.

    Note that non-public methods of Object—like clone()—are not automatically implemented by every subclass of the interface, because the inherited implementation is protected, while the interface method must be public. The only way to implement such an interface would be to override the non-public Object method, making it public.

  2. Functional interfaces can be generic: Predicate<T>, for example.

    A previous, more general approach made the "functional" property one of types (specific parameterizations) rather than interfaces. Each distinct parameterization was examined to determine if it had a single abstract method; parameterizations were allowed to "merge" otherwise-distinct methods. The extra complexity introduced by this strategy, however, was not deemed worthwhile. If an interface could only qualify as functional under a certain parameterization, a corresponding functional interface can be declared by extending the interface, using that particular parameterization as the supertype.

    Under the current definition, a functional interface may be parameterized in a way that produces distinct abstract methods—that is, multiple methods that cannot be legally overridden with a single declaration. For example:

    interface I { Object m(); }
    interface J<S> { S m(); }
    interface K<T> { T m(); }
    interface Functional<S,T> extends I, J<S>, K<T> {}
    

    Interface Functional is functional—I.m is return-type-substitutable for the other two—but Functional<String,Integer> clearly cannot be implemented with a single method. This situation is acceptable, however: when functional interfaces are implicitly implemented, the overriding effectively occurs generically; instantiation of type parameters only occurs at the use site. In other words, if a lambda expression represents a Functional<String,Integer>, its treatment is similar to that of the following:

    class FunctionalImpl<S,T> implements Functional<S,T> { ... }
    Functional<String,Integer> f = new FunctionalImpl<String,Integer>();
    
  3. A design goal for functional interfaces is to interact cleanly with raw types in abstract method declarations without unnecessarily encouraging or introducing their use.

    The subsignature definition in the JLS allows an erased signature to override an unerased version (note that the "signature" consists of a name, type parameters, and parameter types, but not return or throws types). It does not allow piecemeal erasure—either the entire signature must be erased, or the two signatures must be the same. Thus, if the set M used in the functional interface definition contains one method that has a subsignature of all the others, then the set contains at most two unique signatures: an erased version, and a non-erased version. If there is an erased version, that is the signature we use. Then, for simplicity, the set of candidate return types is restricted to those that appear on an erased-signature method.

    When there are multiple methods with different unerased signatures, the interface is not functional. We do not attempt to unify them via erasure. For example, it would be possible to treat the following as a functional interface, but we prefer not to, since there's a clear problem with the interface's design, and we don't want to introduce raw types into a program.

     interface A { void f(List<String> ls); }
     interface B { void f(List<Integer> li); }
     interface C extends A,B {}
     C c1 = (List l) -> ...;
     C c2 = l -> ...;
    

    When some return types are erased and others are not, we try to choose the non-erased type, if possible:

    LinkedList foo()
    List<?> foo()
    LinkedList<String> foo()
    LinkedList<?> foo()
    
  4. The descriptor is defined nondeterministically: while the signatures in M are "the same", they may be syntactically different (HashMap.Entry and Map.Entry, for example); the return type may be a subtype of every other return type, but there may be other return types that are also subtypes (List<?> and List<? extends Object>, for example); and the order of thrown types is unspecified.

    These distinctions are subtle, but they can sometimes be important. However, function descriptors will not be used in a way in which the nondeterminism matters. It may affect generated code, but that is mostly implementation-dependent anyway.

    Note that the current JLS similarly defines the return type and throws clause of a "most specific method" nondeterministically when there are multiple abstract methods (15.12.2.5).

  5. Function descriptors are allowed to be generic. There is no syntax in this specification for generic lambda expressions (see 15.27). However, generic methods and constructors can instantiate such functional interfaces (see 15.28.1).

  6. The goal driving the definition of a descriptor's thrown exception types is to support the invariant that a method with the resulting throws clause could override each abstract method of the functional interface. Per 8.4.6, this means the descriptor cannot throw "more" exceptions than any single method in the set M. So we look for as many exception types as possible that are "covered" by every method's throws clause.
  7. When a functional interface is wildcard-parameterized, there are many different instantiations that could satisfy the wildcard and produce different descriptors: e.g., each of Predicate<Integer> (descriptor Integer -> boolean), Predicate<Number> (descriptor Number -> boolean), and Predicate<Object> (descriptor Object -> boolean) is a Predicate<? super Integer>. Sometimes, we can tell from context (e.g., the parameter types of a lambda expression) which is intended (see 15.27.3). Other times, we're left to arbitrarily pick one; in these circumstances, we use an ad hoc but straightforward approach to decide what type should be used in place of each wildcard.
  8. In special circumstances, it is useful to treat an intersection type as a functional interface type, so this is supported. Typically, this will look like an intersection of one functional interface and one or more marker interfaces: Runnable & Serializable.

    In practice, such types can be used in casts (15.16) that force a lambda expression to conform to a certain type. As a special case, when one of the interfaces in the intersection is Serializable, special runtime support for serialization will be triggered (seee 15.27.4).

8.4.2 Method Signature [Modified]

Compare JLS 8.4.2

Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types. [jls-8.4.2-100]

Two method or constructor declarations M and N have the same argument types if all of the following conditions hold: [jls-8.4.2-110]

...

8.4.4 Generic Methods [Addendum]

See JLS 8.4.4

Two methods or constructors M and N have the same type parameters if both of the following are true: [jsr335-8.4.4-10]

Where two methods or constructors M and N have the same type parameters, a type mentioned in N can be adapted to the type parameters of M by applying θ, as defined above, to the type. [jsr335-8.4.4-20]

Discussion and motivation:
  1. This concept of type parameter adaptation is implicit in JLS 7 but never expressed outright. Being explicit about it allows us to more precisely and correctly identify the return type and throws clause of a function descriptor.

8.4.5 Method Return Type [Modified]

Compare JLS 8.4.5

A method declaration d1 with return type R1 is return-type-substitutable for another method d2 with return type R2, if and only if the following conditions hold: [jls-8.4.5-210]

Discussion and motivation:
  1. This takes advantage of the definition of type parameter adaptation, above, and can be viewed as a bug fix for the specification.

8.4.6 Method Throws [Modified]

Compare JLS 8.4.6

...

If the unerased throws clause of m does not contain a supertype of each exception type in the throws clause of n (adapted, if necessary, to the type parameters of m), a compile-time unchecked warning occurs. [jls-8.4.8.3-210-C]

...

Discussion and motivation:
  1. This takes advantage of the definition of type parameter adaptation, above, and can be viewed as a bug fix for the specification.

9.6.3.8 FunctionalInterface [New]

The annotation type FunctionalInterface is used to indicate that an interface is meant to be a functional interface (9.8).

If an interface is annotated with the annotation @FunctionalInterface but is not, in fact, a functional interface, a compile-time error occurs. [jsr335-9.6.3.8-10]

This facilitates early detection of inappropriate method declarations appearing in or inherited by an interface that is meant to be functional.

Because some interfaces are functional incidentally, it is not necessary or desirable that all functional interfaces be annotated with the @FunctionalInterface annotation.