I am having trouble understanding the syntax for a method reference, where there are two parameters a and b, and the reference is to a method of a on b.
For example I understand how
Arrays.sort(personArray, comparators::compareByName);
is equivalent to
Arrays.sort(personArray, (o1, o2) -> comparators.compareByName(o1, o2));
because in that case the lambda parameters match the method call parameters (o1, o2).
Howevever for this lambda
stream.sorted((o1, o2) -> o1.compareToIgnoreCase(o2));
my IDE tells me that is equivalent to:
stream.sorted(String::compareToIgnoreCase);
and I am not finding a rule for replacing that syntax: a.method(b) with a method reference.
For example, what if there are three or more parameters to the lambda? Is that legal? Does the first parameter become the method target, and the remaining become the parameters?
I think you're looking for JLS section 15.13.3, which includes:
If the form is ReferenceType :: [TypeArguments] Identifier, the body of the invocation method similarly has the effect of a method invocation expression for a compile-time declaration which is the compile-time declaration of the method reference expression. Run-time evaluation of the method invocation expression is as specified in §15.12.4.3, §15.12.4.4, and §15.12.4.5, where:
The invocation mode is derived from the compile-time declaration as specified in §15.12.3.
If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference.
If the compile-time declaration is an instance method, then the arguments to the method invocation expression (if any) are the second and subsequent formal parameters of the invocation method. Otherwise, the arguments to the method invocation expression are the formal parameters of the invocation method.
Note the last two bullets, basically.
For example, what if there are three or more parameters to the lambda? Is that legal? Does the first parameter become the method target, and the remaining become the parameters?
Yup :)
I would give a couple of examples here, for those who find Oracle documentation a bit hard to take in.
Imagine you need a reference to a Comparator instance:
.sorted(String::compareTo)
String::compareTo is identical to:
(String a, String b) -> a.compareTo(b);
Because, as Jon explained, a method reference will be transformed to a lambda that will expect 2 parameters. The actual arbitrary object passed in the stream as a first argument, and one more parameter(since Comparator expects int compare(T o1, T o2)).
Another case:
.map(Employee::getSalary)
In this case map expects: Function. Function requires implementation of R apply(T var1) - a method with 1 argument. In this case the only parameter that will be passed to the lambda is the actual arbitrary object - instance on Employee.
To sum up - depending on the compile time context, method reference to arbitrary object will always be "transformed" into a lambda that expects that object as a first parameter + any number of parameters that the target method requires in the same corresponding order.
Related
Consider the following article from the JLS (§15.13.1)
A method reference expression ending with Identifier is exact if it satisfies all of the following:
If the method reference expression has the form ReferenceType ::[TypeArguments] Identifier, then ReferenceType does not denote a raw type.
The type to search has exactly one member method with the name Identifier that is accessible to the class or interface in which the method reference expression appears.
This method is not variable arity (§8.4.1).
If this method is generic (§8.4.4), then the method reference expression provides
TypeArguments.
Consider the following code snippet:
class Scratch {
public static void main(String[] args) {
Scratch.funct(new ImplementingClass()::<Functional1>hitIt);
}
public static void funct(Functional1 a){}
public static void funct(Functional2 a){}
}
interface Functional1 {<T> T hitIt();}
interface Functional2 {<T> T hitIt();}
class ImplementingClass{
public <T> T hitIt(){return null;}
}
Clearly - this satisfies all the conditions being mentioned for a method reference to be exact.
Not sure why still the method reference is in-exact in this particular case? Am I missing something here from the clause?
Solution :
Based on inputs from #Sweeper #DidierL and #Holger here what I summarized:
Both the functional interfaces have the functionType <T> () -> T
the method reference …::<Functional1>hitIt substitutes T with Functional1, so the resulting functional signature is () -> Functional1 which does not match <T> () -> T.
First a warning: IANAJL (IANAL for Java 😉)
As far as I can tell, this should compile if you make the two interface methods non-generic, but it doesn’t. Let’s simplify the code as much as we can to reproduce the problem:
class Scratch {
public static void main(String[] args) {
Scratch.funct(ImplementingClass::<Void>hitIt);
}
public static void funct(Functional1 a){}
public static void funct(Functional2 a){}
}
interface Functional1 {Integer hitIt();}
interface Functional2 {String hitIt();}
class ImplementingClass{
public static <T> Integer hitIt(){return null;}
}
The simplifications:
the two interfaces now have non-generic methods
ImplementingClass.hitIt() is now static and has a concrete return type (non-generic)
Now let’s analyze the call to check if it should compile. I put links to the Java 8 specs but they are very similar in 17.
15.12.2.1. Identify Potentially Applicable Methods
A member method is potentially applicable to a method invocation if and only if all of the following are true:
[…]
If the member is a fixed arity method with arity n, the arity of the method invocation is equal to n, and for all i (1 ≤ i ≤ n), the i'th argument of the method invocation is potentially compatible, as defined below, with the type of the i'th parameter of the method.
[…]
An expression is potentially compatible with a target type according to the following rules:
[…]
A method reference expression (§15.13) is potentially compatible with a functional interface type if, where the type's function type arity is n, there exists at least one potentially applicable method for the method reference expression with arity n (§15.13.1), and one of the following is true:
The method reference expression has the form ReferenceType :: [TypeArguments] Identifier and at least one potentially applicable method is i) static and supports arity n, or ii) not static and supports arity n-1.
The method reference expression has some other form and at least one potentially applicable method is not static.
(this last bullet applies for the case of the question where the method reference uses a constructor invocation expression, i.e. a Primary)
At this point, we only check for the arity of the method reference, so both funct() methods are potentially applicable.
15.12.2.2. Phase 1: Identify Matching Arity Methods Applicable by Strict Invocation
An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms:
[…]
An inexact method reference expression (§15.13.1).
[…]
This is the only bullet point in this list that could potentially match, however, as pointed in the question we have an exact method reference expression here. Note that if you remove the <Void>, this makes it an inexact method reference, and both methods should be applicable as per the next section:
Let m be a potentially applicable method (§15.12.2.1) with arity n and formal parameter types F1 ... Fn, and let e1, ..., en be the actual argument expressions of the method invocation. Then:
[…]
If m is not a generic method, then m is applicable by strict invocation if, for 1 ≤ i ≤ n, either ei is compatible in a strict invocation context with Fi or ei is not pertinent to applicability.
However only the first funct() method declaration should be applicable by strict invocation. Strict invocation contexts are defined here, but basically they check if the type of the expression matches the type of the argument. Here the type of our argument, the method reference, is defined by section 15.13.2. Type of a Method Reference whose relevant part is:
A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of […] T.
[…]
A method reference expression is congruent with a function type if both of the following are true:
The function type identifies a single compile-time declaration corresponding to the reference.
One of the following is true:
The result of the function type is void.
The result of the function type is R, and the result of applying capture conversion (§5.1.10) to the return type of the invocation type (§15.12.2.6) of the chosen compile-time declaration is R' (where R is the target type that may be used to infer R'), and neither R nor R' is void, and R' is compatible with R in an assignment context.
Here R would be Integer for Functional1 and String for Functional2, while R' is Integer in both cases (since there is no capture conversion needed for ImplementingClass.hitIt()), so clearly the method reference is not congruent with Functional2 and by extension not compatible.
funct(Functional2) should thus not be considered for applicability by strict invocation, and since only funct(Functional1) remains it should be selected.
It should be noted that Javac must select both methods in Phase 1, because only one phase can apply, and Phase 2 only uses loose context instead of strict, which just allows boxing operations, and Phase 3 then includes varargs, which is not applicable either.
Except if we consider that Javac somehow considers the method reference as congruent with Functional2, the only reason I see for selecting both methods is if it considered the method reference as not pertinent for applicability as specified above, which I can only explain if the compiler considers it as an inexact method reference.
15.12.2.5. Choosing the Most Specific Method
This is where the compilation fails. We should note that there is nothing here that would make the compiler select one method over the other. The applicable rule is:
m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).
[…]
A type S is more specific than a type T for any expression if S <: T (§4.10).
This appears to work properly: change Functional2 to extend Functional1 and it will compile.
A functional interface type S is more specific than a functional interface type T for an expression e if T is not a subtype of S and one of the following is true (where U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of S, and V1 ... Vk and R2 are the parameter types and return type of the function type of T):
If e is an explicitly typed lambda expression […]
If e is an exact method reference expression (§15.13.1), then i) for all i (1 ≤ i ≤ k), Ui is the same as Vi, and ii) one of the following is true:
R2 is void.
R1 <: R2.
[…]
This does not allow to disambiguate it either. However, changing Functional2.hitIt() to return Number should make Functional1 more specific since Integer <: Number.
This still fails, which seems to confirm that the compiler does not consider it as an exact method reference.
Note that removing the <T> in ImplementingClass.hitIt() allows it to compile, independently of the return type of Functional2.hitIt(). Fun fact: you can leave the <Void> at the call site, the compiler ignores it.
Even stranger: if you leave the <T> and add more type arguments than required at the call site, the compiler still complains about the ambiguous call and not about the number of type arguments (until you remove the ambiguity). Not that this should make the method reference inexact, based on the above definition, but I would think it should be checked first.
Conclusion
Since the Eclipse compiler accepts it, I would tend to consider this as a Javac bug, but note that the Eclipse compiler is sometimes more lenient than Javac with respect to the specs, and some similar bugs have been reported and closed (JDK-8057895, JDK-8170842, …).
This question already has answers here:
Why does type-promotion take precedence over varargs for overloaded methods
(2 answers)
Closed 5 years ago.
While preparing for Java certification exam I was quite surprised to see that Java allows this:
public class Consumer {
public void buy(Object o) {
System.out.println("Buying one object");
}
public void buy(Object... o) {
System.out.println("Buying multiple objects");
}
public static void main(String[] args) {
Consumer consumer = new Consumer();
consumer.buy(new Object());
consumer.buy("a String");
}
}
This class compiles and runs fine. It prints "Buying one object" two times. Actually I thought to see a compiler error because both functions could be used. How does the compiler select the best matching function here? Will it always select the non-varargs function when I pass only one argument?
Method overloading resolution has 3 stages. Only on the 3rd and last stage it considers methods with varags (such as your public void buy(Object... o)), so if a matching method is found in one of the first 2 stages, the varargs methods are ignored, and the non-varag matching method is chosen.
Therefore both calls result in public void buy(Object o) being chosen.
Will it always select the non-varargs function when I pass only one argument?
It will always select the non-varargs method when you pass only one argument, unless the compile time type of that argument is an array:
Object[] arr = new Object[]{"a string"};
consumer.buy(arr);
Passing null will also cause the compiler to select the varargs method:
consumer.buy(null);
Here's the relevant JLS 15.12.2. Compile-Time Step 2: Determine Method Signature quote:
The process of determining applicability begins by determining the potentially applicable methods (§15.12.2.1). Then, to ensure compatibility with the Java programming language prior to Java SE 5.0, the process continues in three phases:
The first phase performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.
This guarantees that any calls that were valid in the Java programming language before Java SE 5.0 are not considered ambiguous as the result of the introduction of variable arity methods, implicit boxing and/or unboxing. However, the declaration of a variable arity method (§8.4.1) can change the method chosen for a given method method invocation expression, because a variable arity method is treated as a fixed arity method in the first phase. For example, declaring m(Object...) in a class which already declares m(Object) causes m(Object) to no longer be chosen for some invocation expressions (such as m(null)), as m(Object[]) is more specific.
The second phase performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.
This ensures that a method is never chosen through variable arity method invocation if it is applicable through fixed arity method invocation.
The third phase allows overloading to be combined with variable arity methods, boxing, and unboxing.
In your particular case the compiler will only select buy(Object... o) if the argument you pass to this function is an array (which also includes the comma-delimited syntax that represents an array). For instance:
Object o1 = new Object();
Object o2 = new Object();
Object[] oArray = new Object[]{o1, o2};
buy((Object[]) null); // will call the varargs function
buy(new Object[]{o1}); // will call the varargs function
buy(oArray); // will call the varargs function
buy(o1, o2); // will call the varargs function
buy((Object) null); // will call the non-varargs function
buy(o1); // will call the non-varargs function
This question already has answers here:
What's the difference between instance method reference types in Java 8?
(3 answers)
Closed 6 years ago.
I found an interesting example of using Stream API:
Stream<String> stream = Stream.of("w", "o", "l", "f");
BiConsumer<StringBuilder, String> append = StringBuilder::append;
StringBuilder collected = stream.collect(StringBuilder::new, append, StringBuilder::append);
System.out.println(collected); //it works correctly
Stream.collect takes three parameters:
Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner
BiConsumer takes two parameters and doesn't return anything.
Why this line compiles and works?
BiConsumer<StringBuilder, String> append = StringBuilder::append;
StringBuilder doesn't have void method append(java.lang.StringBuilder, java.lang.String).
JLS 15.13.3 specifies more or less that the receiver -- the object the method is being called on -- can become the first argument for the functional interface:
If the form is ReferenceType :: [TypeArguments] Identifier, the body of the invocation method similarly has the effect of a method invocation expression for a compile-time declaration which is the compile-time declaration of the method reference expression. Run-time evaluation of the method invocation expression is as specified in §15.12.4.3, §15.12.4.4, and §15.12.4.5, where:
The invocation mode is derived from the compile-time declaration as specified in §15.12.3.
If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference.
If the compile-time declaration is an instance method, then the arguments to the method invocation expression (if any) are the second and subsequent formal parameters of the invocation method. Otherwise, the arguments to the method invocation expression are the formal parameters of the invocation method.
The compile-time declaration is in fact an instance method, so the StringBuilder becomes the first parameter of the invocation method, and the String becomes the second.
In other words, the method reference SomeClass::instanceMethod is equivalent to the lambda (SomeClass receiver, args...) -> receiver.instanceMethod(args...).
The first type argument of the BiConsumer is the type the method is applied on, and the second is the method's single parameter. The classic example would be ArrayList::add.
I am having trouble understanding the syntax for a method reference, where there are two parameters a and b, and the reference is to a method of a on b.
For example I understand how
Arrays.sort(personArray, comparators::compareByName);
is equivalent to
Arrays.sort(personArray, (o1, o2) -> comparators.compareByName(o1, o2));
because in that case the lambda parameters match the method call parameters (o1, o2).
Howevever for this lambda
stream.sorted((o1, o2) -> o1.compareToIgnoreCase(o2));
my IDE tells me that is equivalent to:
stream.sorted(String::compareToIgnoreCase);
and I am not finding a rule for replacing that syntax: a.method(b) with a method reference.
For example, what if there are three or more parameters to the lambda? Is that legal? Does the first parameter become the method target, and the remaining become the parameters?
I think you're looking for JLS section 15.13.3, which includes:
If the form is ReferenceType :: [TypeArguments] Identifier, the body of the invocation method similarly has the effect of a method invocation expression for a compile-time declaration which is the compile-time declaration of the method reference expression. Run-time evaluation of the method invocation expression is as specified in §15.12.4.3, §15.12.4.4, and §15.12.4.5, where:
The invocation mode is derived from the compile-time declaration as specified in §15.12.3.
If the compile-time declaration is an instance method, then the target reference is the first formal parameter of the invocation method. Otherwise, there is no target reference.
If the compile-time declaration is an instance method, then the arguments to the method invocation expression (if any) are the second and subsequent formal parameters of the invocation method. Otherwise, the arguments to the method invocation expression are the formal parameters of the invocation method.
Note the last two bullets, basically.
For example, what if there are three or more parameters to the lambda? Is that legal? Does the first parameter become the method target, and the remaining become the parameters?
Yup :)
I would give a couple of examples here, for those who find Oracle documentation a bit hard to take in.
Imagine you need a reference to a Comparator instance:
.sorted(String::compareTo)
String::compareTo is identical to:
(String a, String b) -> a.compareTo(b);
Because, as Jon explained, a method reference will be transformed to a lambda that will expect 2 parameters. The actual arbitrary object passed in the stream as a first argument, and one more parameter(since Comparator expects int compare(T o1, T o2)).
Another case:
.map(Employee::getSalary)
In this case map expects: Function. Function requires implementation of R apply(T var1) - a method with 1 argument. In this case the only parameter that will be passed to the lambda is the actual arbitrary object - instance on Employee.
To sum up - depending on the compile time context, method reference to arbitrary object will always be "transformed" into a lambda that expects that object as a first parameter + any number of parameters that the target method requires in the same corresponding order.
Below are the two lines of my code snippet:
List<String> listDevs = Arrays.asList("alvin", "Alchemist", "brutus", "larsen", "jason", "Kevin");
listDevs.sort(Comparator.comparing(String::length)); //This works fine
listDevs.sort(String::compareToIgnoreCase); //This works fine
But (out of expermient) when I try to write
listDevs.sort(Comparator.comparing(String::compareToIgnoreCase));
The compiler throws error
Cannot make a static reference to the non-static method
compareToIgnoreCase(String) from the type String
Similar happens to the below code
listDevs.sort(Comparator.comparing(String::compareTo));
I understand the error and that it works fine if I remove the Comparator.comparing (as shown above).
But my point is, how does this line works?
listDevs.sort(Comparator.comparing(String::length));
I believe I am missing something. I have read this thread. Is this the same scenario?
Comparator.comparing expects a Function which describes a comparable property of the elements. So String::length is sufficient as length() is a property of the String evaluating a String to an int (that’s why comparingInt is preferable here).
In contrast, String.compareToIgnoreCase and String.compareTo are comparison methods. They compare two String objects. So references to them are sufficient where a Comparator is expected, but not where a property Function is expected.
It’s like you have a factory saying “Gimme an engine, and we build a car for you” and you are trying to give them a complete car. While that existing car is valid where a car is expected, there is no sense in passing it to the factory to built a car.
Unfortunately, the current compiler implementation is very bad at reporting error with functional signatures. You will almost always see messages like “Cannot make a static reference to the non-static method …” when signatures mismatch.
The sort method expected a Comparator.
When you do this, you are indeed providing one.
listDevs.sort(Comparator.comparing(String::length));
Same happens here(but a bit non-intuitive):
listDevs.sort(String::compareToIgnoreCase)
listDevs.sort((left, right) -> left.compareToIgnoreCase(right)); // same thing as above
That's exactly the definition of a Comparator - take two Strings and return an int.
The line that you say how come this works: listDevs.sort(Comparator.comparing(String::length)); is actually pretty simple.
Comparator.comparing takes a Function that transforms your input type into something that is Comparable. In your case takes a String and returns an Integer; which is Comparable.
JLS says Compile-Time Declaration of a Method Reference of ReferenceType :: [TypeArguments] Identifier can be interpreted in different ways.
Given a targeted function type with n parameters, a set of potentially applicable methods is identified:
ReferenceType :: [TypeArguments] Identifier has two different arities, n and n-1, are considered, to account for the possibility that this form refers to either a static method or an instance method.
A method reference expression of the form ReferenceType :: [TypeArguments] Identifier can be interpreted in different ways. If Identifier refers to an instance method, then the implicit lambda expression has an extra parameter with type of this compared to if Identifier refers to a static method. It is possible for ReferenceType to have both kinds of applicable methods, so the search algorithm described above identifies them separately, since there are different parameter types for each case.
Comparator.comparing method accept a Function<T,R extends Comparable<? super R>>. when you use String::compareToIgnoreCase that will reports error,because it has two parameters one is implicit this another is a comparing string of method parameter, so it is more like a BiFunction<String,String,Integer> not a Function<String,Integer>.
BiFunction<String, String, Integer> comparator = String::compareToIgnoreCase;
// you can't assign a BiFunction to a Function
// because one is incompatiable with another.
Function<String,Integer> function = comparator;
Stream.sort method accept a Comparator, and Comparator is more like a BiFunction<T,T,Integer> so it is compatiable with String::compareToIgnoreCase. on the other hand, they can be interchangeable. for example:
Comparator<String> primary = String::compareToIgnoreCase;
BiFunction<String, String, Integer> comparator1 = primary::compare;
Comparator<String> comparator2 = comparator1::apply;
you can using comparing(String::toLowerCase) instead, it is equalivent to String::compareToIgnoreCase, for example:
// String::compareToIgnoreCase
listDevs.sort(String::compareToIgnoreCase);
// comparing(String::toLowerCase)
listDevs.sort(comparing(String::toLowerCase))