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 - 4.9 - 8.2 - 8.4.2 - 8.4.4 - 8.4.5 - 8.4.6 - 9.6.3.8
Version 0.9.3. Copyright © 2014 Oracle America, Inc. Legal Notice.

Summary

A functional interface is an interface that has just one abstract method (aside from the methods of Object), 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 or method references.

The function type 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.

The term functional interface type refers to a non-generic functional interface, a parameterization of a functional interface, a raw functional interface, or an intersection type inducing a functional interface.

9.8 Functional Interfaces [New]

A functional interface is an interface that has just one abstract method (aside from the methods of Object), 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) or method reference expressions (15.13).

The function type 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 function type of I consists of the following: [jsr335-9.8-20]

The term functional interface type refers to one of the following: [jsr335-9.8-30]

The function type of a parameterized functional interface, F<T1...Tn>, where T1...Tn are types and the corresponding type parameters of F are P1, ..., Pn, is derived by applying the substitution [P1:=T1, ..., Pn:=Tn] to the function type of interface F. [jsr335-9.8-38]

The function type of a parameterized functional interface, F<A1...An>, where one or more of A1...An is a wildcard, is the function type of the non-wildcard parameterization of F, F<T1...Tn> determined as follows. Let P1, ..., Pn be the type parameters of F and B1, ..., Bn be the corresponding bounds. For all i, 1 ≤ i ≤ n, Ti is derived according to the form of Ai: [jsr335-9.8-40]

The function type of a raw functional interface is the erasure of the functional interface's function type. [jsr335-9.8-45]

The function type of an intersection type that induces a notional functional interface (4.9) is the function type of the notional interface. [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 {}
  // Compiler error: 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 {}
  // Compiler error: 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 type 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 function type ()->void throws EOFException
// XYZ has function type ()->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 function type (List<String>)->List<String> throws EOFException, SQLTransientException
// E has function type (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 function type <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 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 if non-public methods of Object—like clone()—are declared in an interface, they 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(Class c); }
    interface J<S> { S m(Class<?> c); }
    interface K<T> { T m(Class<?> c); }
    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. However, other parameterizations of Functional can be implemented with a single method.

  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. While the name may suggest otherwise, a "function type" is not a type in the sense of 4.1. It is a collection of types that form a method signature, return type, and throws clause. This abuse of the "type" terminology is consistent with precedent—see 8.2.
  5. The function type of a functional interface 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 types 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).

  6. Function types 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.13.2).

  7. The goal driving the definition of a function type'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 function type 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.
  8. When a functional interface is wildcard-parameterized, there are many different instantiations that could satisfy the wildcard and produce different function types: e.g., each of Predicate<Integer> (function type Integer -> boolean), Predicate<Number> (function type Number -> boolean), and Predicate<Object> (function type 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 just choose the bounds. (Since this simple strategy can't guarantee that the resulting type will satisfy certain complex bounds, we give up in complex cases.)
  9. 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 (see 15.27.4).

4.9 Intersection Type [Modified]

Compare JLS 4.9

An intersection type takes the form T1 & ... & Tn (n > 0), where Ti (1 ≤ i ≤ n) are type expressions types. [jls-4.9-100]

Intersection types can be derived from type parameter bounds (4.4) and cast expressions (15.16); they also arise in the processes of capture conversion (5.1.10) and type inference (15.12.2.7) least upper bound computation (4.10.4). It is not possible to write an intersection type directly as part of a program in a place where a Type (4.1) is expected; no syntax supports this.

The values of an intersection type are those objects that are values of all of the types Ti for 1 ≤ i ≤ n.

Every intersection type induces a notional class or interface for the purpose of identifying the members of the intersection, as follows: [jsr335-4.9-301]

The members of an intersection type are the members of the class or interface it induces. [jls-4.9-300]

Discussion and motivation:

There are a number of problems with this definition of the members of an intersection (including no allowance for wildcards, extending an array type, and lack of explicit well-formedness rules), but those problems are set aside for now. Our only goal is to adjust the existing rules so that an intersection may be treated as a functional interface in certain circumstances.

8.2 Class Members [Modified]

Compare JLS 8.2

...

We use the phrase the type of a member to denote: [jls-8.2-200]

...

Discussion and motivation:

This is a bug fix, necessary to clarify what constitutes a function type.

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:

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 type.

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:

This is a bug fix for the specification. First, it adjusts the R1 = |R2| rule to more accurately describe longstanding behavior. Second, it takes advantage of the definition of type parameter adaptation (8.4.4).

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:

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.