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.
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
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.
This question already has answers here:
Instance Method Reference and Lambda Parameters
(2 answers)
Closed 7 years ago.
The oracle Java 8 documentation defines 4 types of method references you can use instead of Lambda Expressions. What I am trying to understand is the kind of method reference described as: "Reference to an instance method of an arbitrary object of a particular type " which is written as ContainingType::methodName.
I am not sure if I am missing something, but to me it seems more like:
"Reference to the first parameter of the abstract method of the Functional Interface, assuming it is of type ContainingType". I tried to come up with examples where this 'arbitrary object' is the second parameter, but of course it does not compile.
Is there an official reference how this object is resolved by the compiler? Am I correct in my understanding that:
The arbitrary object has to be the 1st parameter of the abstract method of the functional interface.
The signature of the method reference must be the same as that of the abstract method of the functional interface, without the first parameter.
So a functional interface with abstract method A method(B b, C c, D d) can only be passed instance method references x::methodImpl or B::methodImpl. There is no way I can pass C::methodImpl for example, where it would be an instance of class C with its signature A methodImpl(B b, D d).
Are there any other cases I am missing, which might be the reason why Oracle wrote this in such an ambiguous way?
No, your understanding is correct. The documentation you linked implies (but does not adequately emphasize) that given a functional interface that expects args a1, a2, a3, ..., a method reference of this type is equivalent to a lambda that calls a1.namedMethod(a2, a3, ...).
Note that a concrete definition like this is required for consistency's sake - given the example on the linked documentation of a functional interface with two String arguments (String s1, String s2), how would you determine whether the behavior would be s1.doThing(s2) or s2.doThing(s1) otherwise?
You can find this specified precisely in the JLS:
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.
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.