Here is a "show me the code" approach to understanding the definition:
we shall look into OpenJDK javac
for how it checks validity of classes annotated with @FunctionalInterface
.
The most recent (as of July, 2022) implementation lies here:
com/sun/tools/javac/code/Types.java#L735-L791:
/**
* Compute the function descriptor associated with a given functional interface
*/
public FunctionDescriptor findDescriptorInternal(TypeSymbol origin,
CompoundScope membersCache) throws FunctionDescriptorLookupError {
// ...
}
Class Restriction
if (!origin.isInterface() || (origin.flags() & ANNOTATION) != 0 || origin.isSealed()) {
//t must be an interface
throw failure("not.a.functional.intf", origin);
}
Pretty simple: the class must be an interface and must not be a sealed one.
Member Restriction
for (Symbol sym : membersCache.getSymbols(new DescriptorFilter(origin))) { /* ... */ }
In this loop, javac
goes through the members of the origin
class, using a DescriptorFilter
to retrieve:
- Method members (of course)
&&
that are abstract
but not default
,
&&
and do not overwrite methods from Object
,
&&
with their top level declaration not a default
one.
If there is only one method matching all the above conditions, then surely it is a valid functional interface, and any lambda will overwrite that very method.
However, if there are multiple, javac
tries to merge them:
- If those methods all share the same name, related by override equivalence:
- then we filter them into a
abstracts
collection:
if (!abstracts.stream().filter(msym -> msym.owner.isSubClass(sym.enclClass(), Types.this))
.map(msym -> memberType(origin.type, msym))
.anyMatch(abstractMType -> isSubSignature(abstractMType, mtype))) {
abstracts.append(sym);
}
Methods are filtered out if:
- their enclosing class is super class of that of another previously matched method,
- and the signature of that previously matched method is subsignature of that of this method.
- Otherwise, the functional interface is not valid.
Having collected abstracts
, javac
then goes to mergeDescriptors
, which uses mergeAbstracts
, which I will just quote from its comments:
/**
* Merge multiple abstract methods. The preferred method is a method that is a subsignature
* of all the other signatures and whose return type is more specific {@see MostSpecificReturnCheck}.
* The resulting preferred method has a thrown clause that is the intersection of the merged
* methods' clauses.
*/
public Optional<Symbol> mergeAbstracts(List<Symbol> ambiguousInOrder, Type site, boolean sigCheck) {
// ...
}
Conclusion
- Functional interfaces must be interfaces :P , and must not be
sealed
or annotations.
- Methods are searched in the whole inheritance tree.
- Methods overlapping with those from
Object
are ignored.
default
methods are ignored, unless they are later overridden by sub-interfaces as non-default.
- Matching methods must all share the same name, related by override equivalence.
- Either there is only one method matching the above conditions, or matching methods can get "merged" by their class hierarchy, subsignature relations, return types and thrown clauses.