A method signature can be “mapped” to a functional interface in several ways, depending on whether the method is static or an instance method, and how its parameters and return type align with the functional interface’s abstract method. Here’s a breakdown of the different mappings:
1. Static Method Reference:
- Direct Parameter and Return Type Match: If a static method’s parameter types and return type exactly match the parameter types and return type of the functional interface’s abstract method (in the same order), then the method reference can be directly assigned to an instance of that functional interface.
interface StringToInt {
int convert(String s);
}
class StringConverter {
public static int stringLength(String str) {
return str.length();
}
}
StringToInt lengthFunc = StringConverter::stringLength; // Maps directly2. Instance Method Reference to an Existing Object:
- Parameter and Return Type Match (excluding the instance): If an instance method of a specific object has parameter types and a return type that match the parameter types and return type of the functional interface’s abstract method, then a method reference to that specific object’s method can be used. The object on which the method is called is implicitly provided when the functional interface’s method is invoked.
interface StringProcessor {
String process(String s);
}
class CaseChanger {
public String toUpperCase(String text) {
return text.toUpperCase();
}
}
CaseChanger changer = new CaseChanger();
StringProcessor upperCaseFunc = changer::toUpperCase; // Maps to changer.toUpperCase(s)3. Instance Method Reference to an Arbitrary Object of a Particular Type:
- Implicit First Parameter: If the functional interface’s abstract method has parameters that match the parameter types of an instance method (in the same order), and the first parameter of the functional interface’s method is of the same type as the class containing the instance method, then a method reference using the class name can be used. When the functional interface’s method is invoked, the first argument acts as the target object on which the instance method is called.
interface StringComparator {
int compare(String s1, String s2);
}
class String { // Assuming a custom String-like class for demonstration
private String value;
public String(String value) { this.value = value; }
public int compareToIgnoreCase(String other) {
return this.value.compareToIgnoreCase(other);
}
}
StringComparator caseInsensitiveCompare = String::compareToIgnoreCase; // Maps to s1.compareToIgnoreCase(s2)In this case, when caseInsensitiveCompare.compare("hello", "WORLD") is called, it’s equivalent to "hello".compareToIgnoreCase("WORLD")
4. Constructor Reference:
- Matching Constructor Parameters: If a constructor’s parameter types match the parameter types of the functional interface’s abstract method, then a constructor reference can be used. The return type of the functional interface should be the type of the class being constructed.
interface StringCreator {
String create(char[] chars);
}
class String { // Assuming standard java.lang.String
public String(char[] value) {
// ... constructor logic ...
}
}
StringCreator creator = String::new; // Maps to new String(chars)- No-Argument Constructor: If the functional interface’s abstract method takes no arguments and returns an instance of a class that has a no-argument constructor, a constructor reference to the no-argument constructor can be used.
interface Supplier<T> {
T get();
}
class MyClass {
public MyClass() {}
}
Supplier<MyClass> myClassSupplier = MyClass::new; // Maps to new MyClass()Key Considerations:
- Arity (Number of Parameters): The number of parameters in the method (or constructor) must align with the number of parameters in the abstract method of the functional interface. For instance method references to an arbitrary object, the instance itself is treated as the first implicit parameter.
- Type Compatibility: The parameter types and return type of the method (or the type of the constructed object for constructor references) must be compatible with the parameter types and return type of the functional interface’s abstract method (allowing for autoboxing/unboxing and widening conversions).
- Checked Exceptions: If the method being referenced throws checked exceptions, the abstract method of the functional interface must declare compatible checked exceptions or the functional interface must not declare any checked exceptions.
In essence, Java tries to find a way to adapt the method’s signature to fit the expected signature of the functional interface’s abstract method based on the context and the type of method reference used. The compiler performs this “mapping” during compilation.