Method reference notation vs "standard" Lambda notation - java

I was expecting these to be simple drop-in replacements for each other, but they're not. Clearly I'm not understanding the notation.
Can anyone explain why does that happen?
playButton.setOnAction(e -> track.play());
Here, compiler is happy with play() having a signature of
void play()
but here
playButton.setOnAction(track::play);
it requires
void play(Event e)

Here is a quote from the Java language specification:
A method reference expression (§15.13) is potentially compatible with
a functional interface type T if, where the arity of the function type
of T is n, there exists at least one potentially applicable method
when the method reference expression targets the function type 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 either (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.
...
The definition of potential applicability goes beyond a basic arity
check to also take into account the presence and "shape" of functional
interface target types.
Every method reference should conform to a function interface (which is an interface that declares a single abstract method, non-overriding methods from Object class). The compiler needs to verify whether the provided reference resolves to a single existing method that has required arity (number of parameters) and their types match the types of the method declared by the target functional interface.
Let's have a look at the prior Java 8 code (code-sample from JavaFX tutorial by created Oracle):
button2.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
label.setText("Accepted");
}
});
That is the "shape" that needs to be filled with a code describing an action. Method handle() of the EventHandler interface expects an event as an argument. Whether it would be used or not, that's up to you, the key point is that the abstract method of the target interface expects this argument to be provided.
By using a lambda expression e -> track.play() you're explicitly telling to ignore it.
And when you're passing a method reference track::play, which should be classified as a reference to an instance method of a particular object (see), the compiler will try to resolve it to a method play(Event) and you're getting a compilation error because it fails to find one.
In this case, reference track::play is not an equivalent of lambda e -> track.play(), but () -> track.play(), which doesn't conform to the target functional interface EventHandler.
In case if you wonder, how a method reference which can be applicable to a non-static method of arity n-1 mentioned in the specification (see case ii) can look like, here is an example:
BiPredicate<String, String> startsWith = String::startsWith; // the same as (str1, str2) -> str1.strarsWith(str2);
System.out.println(startsWith.test("abc", "a")); // => true
System.out.println(startsWith.test("fooBar", "a")); // => false
And you can construct a similar reference which conforms to EventHandler interface and applicable an instance method of arity n-1 using one of the parameterless methods of the Event type. It's not likely to be useful in practice, but it would be valid from the compiler perspective of view, so feel free to try it as an exercise.

Related

Why Method reference is compatible to a functional interface with difference number of args? [duplicate]

This question already has an answer here:
Why is BiConsumer allowed to be assigned with a function that only accepts a single parameter?
(1 answer)
Closed 3 years ago.
I understand that method reference can be used to implement a functional interface if the referred method takes the same number of args as the functional interface and return the same type, but why in some cases the referred method has different number of args than the functional interface but are still compatible?
I have a simple BiConsumer that I try to use method reference to implement it. I know I can use lambda expression also as long as the number of args matches. I will show the code to explain it clearly.
I have a BiConsumer<ArrayList<String>, ? super String> that I want to implement.
Lambda expression way to do that is:
BiConsumer<ArrayList<String>, ? super String> b = (firstArg,secondArg) -> firstArg.add(secondArg); since they both takes 2 input args, there is no problem.
But why BiConsumer<ArrayList<String>, ? super String> a = ArrayList::add; is also compatible? The add method on ArrayList only takes 1 input args but the functional interface needs 2.
Any answers would be greatly appreciated. Thanks!
15.12.2.1. Identify Potentially Applicable Methods
A method reference expression (§15.13) is potentially compatible with a functional interface type T if, where the arity of the function type of T is n, there exists at least one potentially applicable method when the method reference expression targets the function type 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 either (i) static and supports arity n, or (ii) not static and supports arity n-1.
The function type you want to use has arity 2
void accept(T t, U u);
and the method ArrayList::add refers to has arity 1, and it's not static. It makes it potentially applicable.
The first argument is the ArrayList to call add on.
In this case,
ArrayList::add
means
(list, obj) -> list.add(obj)
which is a BiConsumer.

Java method overloading and varargs

I am trying to understand method overloading, and I have these methods.
public void method(int a){
System.out.println("int a");
}
//implementing interface method
#Override
public void method() {
System.out.println("interface");
}
//varargs
public void method(int ... a){
System.out.println("int ... a");
}
After calling them with these parameters,
int[] a = new int[5];
stack.method();
stack.method(1);
stack.method(5,6);
stack.method(null);
stack.method(a);
I have these results:
interface
int a
int ... a
int ... a
int ... a
As far as I know, the program should not compile, beacuse of ambiguity, but it does anyway. Shouldn't the compiler throw an error?
Eran and Bathsheba have already said why the various ones not using null were chosen.
The rest of the question is: Why does stack.method(null); even compile?
The answer is that it matches the varargs signature, because the varargs method(int...) is effectively the same from the compiler's perspective as method(int[]). Since arrays are referenced by references, null can be used where an int[] is expected.
So:
stack.method();
Exact match for the method() signature in the interface. Not ambiguous with method(int...) because varargs are considered only when others don't match.
stack.method(1);
Matches method(int). Not ambiguous for the same reason as above.
stack.method(5,6);
Matches method(int...) because none of the non-varargs ones matched, but the varargs one did.
stack.method(null);
See earlier explanation.
stack.method(a);
Matches match(int...) for the same reason method(null0 does: Because match(int...) is effectively the same as match(int[]) to the compiler.
Method overloading resolution has three stages. The first and second stages don't consider methods with varargs (also called variable arity methods) as candidates, so only if no matching method without varargs is found, the compiler considers method with varargs as candidates.
Therefore, in the first and second method calls, your void method(int ... a) is ignored, and there is no ambiguity.
15.12.2. Compile-Time Step 2: Determine Method Signature
The second step searches the type determined in the previous step for
member methods. This step uses the name of the method and the argument
expressions to locate methods that are both accessible and applicable,
that is, declarations that can be correctly invoked on the given
arguments.
There may be more than one such method, in which case the most
specific one is chosen. The descriptor (signature plus return type) of
the most specific method is the one used at run time to perform the
method dispatch.
A method is applicable if it is applicable by one of strict invocation
(§15.12.2.2), loose invocation (§15.12.2.3), or variable arity
invocation (§15.12.2.4).
Certain argument expressions that contain implicitly typed lambda
expressions (§15.27.1) or inexact method references (§15.13.1) are
ignored by the applicability tests, because their meaning cannot be
determined until a target type is selected.
Although the method invocation may be a poly expression, only its
argument expressions - not the invocation's target type - influence
the selection of applicable methods.
The process of determining applicability begins by determining the
potentially applicable methods (§15.12.2.1).
The remainder of the process is split into three phases, to ensure
compatibility with versions of the Java programming language prior to
Java SE 5.0. The phases are:
The first phase (§15.12.2.2) 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 (§15.12.2.3) 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 (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.
A method with a variable argument list is only considered by the compiler once all other possibilities have been exhausted.
These "other possibilities" are considered in the normal way.
Hence in your case there is no ambiguity and so the compiler does not emit an error.
No it is fine there is no ambiguity : passing "(5,6)" is fine because the method expxects many integers , passing "(a)" is also fine because a is an integer array passing"(null)" is also fine beacause null can be cast to any reference type like an integer [] so it can be used where you expect int [];
so all these calls call the third method
public void method(int ... a){
System.out.println("int ... a");
}
the first two method calls are self explanatory they call methods
public void method(){
System.out.println("interface");
}
and
public void method(int a){
System.out.println("int a");
}
respectively

Java method overloading - Generic parameter & parameters within same inheritance tree

Let's assume I have following code:
// Method acception generic parameter
public static <T> T foo(T para) {
return para;
}
// Method accepting Integer parameter
public static Integer foo(Integer para) {
return para + 1;
}
// Method accepting Number parameter
public static Number foo(Number para) {
return para.intValue() + 2;
}
public static void main(String[] args) {
Float f = new Float(1.0f);
Integer i = new Integer(1);
Number n = new Integer(1);
String s = "Test";
Number fooedFloat = foo(f); // Uses foo(Number para)
Number fooedInteger = foo(i); // Uses foo(Integer para)
Number fooedNumber = foo(n); // Uses foo(Number para)
String fooedString = foo(s); // Uses foo(T para)
System.out.println("foo(f): " + fooedFloat);
System.out.println("foo(i): " + fooedInteger);
System.out.println("foo(n): " + fooedNumber);
System.out.println("foo(s): " + fooedString);
}
The output looks the following:
foo(f): 3
foo(i): 2
foo(n): 3
foo(s): Test
Now the question(s):
foo(n) calls foo(Number para), most probably because n is defined as Number, even though it has an Integer assigned to it. So am I right in the assumption that the decision, which of the overloaded methods is taken happens at compile-time, without dynamic binding? (Question about static and dynamic binding)
foo(f) uses foo(Number para), while foo(i) uses foo(Integer para). Only foo(s) uses the generic version. So the compiler always looks if there is a non-generic implementation for the given types, and only if not it falls back to the generic version? (Question about generics)
Again, foo(f) uses foo(Number para), while foo(i) uses foo(Integer para). Yet, Integer i would also be a Number. So always the method with the "outer-most" type within the inheritance tree is taken? (Question about inheritance)
I know these are a lot questions, and the example is not taken from productive code, yet I just would like to know what "happens behind" and why things happen.
Also any links to the Java documentation or the Java specification are really appreciated, I could not find them myself.
The rules of determining which method signature to call at compile-time are explained in the language specification. Specifically important is the section on choosing the most specific method. Here are the parts related to your questions:
If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.
...
One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:
m2 is generic, and m1 is inferred to be more specific than m2 for argument expressions e1, ..., ek by §18.5.4.
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).
In this case, Integer is more specific than Number because Integer extends Number, so whenever the compiler detects a call to foo that takes a variable declared of type Integer, it will add an invocation for foo(Integer).
More about the first condition related to the second method being generic is explained in this section. It's a little verbose but I think the important part is:
When testing that one applicable method is more specific than another (§15.12.2.5), where the second method is generic, it is necessary to test whether some instantiation of the second method's type parameters can be inferred to make the first method more specific than the second.
...
Let m1 be the first method and m2 be the second method. Where m2 has type parameters P1, ..., Pp, let α1, ..., αp be inference variables, and let θ be the substitution [P1:=α1, ..., Pp:=αp].
...
The process to determine if m1 is more specific than m2 is as follows:
...
If Ti is a proper type, the result is true if Si is more specific than Ti for ei (§15.12.2.5), and false otherwise. (Note that Si is always a proper type.)
Which basically means that foo(Number) and foo(Integer) are both more specific than foo(T) because the compiler can infer at least one type for the generic method (e.g. Number itself) that makes foo(Number) and foo(Integer) more specific (this is because Integer <: Number and Number <: Number) .
This also means that in your code foo(T) is only applicable (and inherently the most specific method since it's only the one applicable) for the invocation that passes a String.
Am I right in the assumption that the decision, which of the overloaded methods is taken happens at compile-time, without dynamic binding?
Yes, Java chooses among available overloads of a method at compile time, based on the declared types of the arguments, from among the alternatives presented by the declared type of the target object.
Dynamic binding applies to choosing among methods having the same signature based on the runtime type of the invocation target. It has nothing directly to do with the runtime types of the actual arguments.
So the compiler always looks if there is a non-generic implementation for the given types, and only if not it falls back to the generic version?
Because of type erasure, the actual signature of your generic method is
Object foo(Object);
Of the argument types you tested, that is the best match among the overloaded options only for the String.
So always the method with the "outer-most" type within the inheritance tree is taken?
More or less. When selecting among overloads, the compiler chooses the alternative that best matches the declared argument types. For a single argument of reference type, this is the method whose argument type is the argument's declared type, or its nearest supertype.
Things can get dicey if Java has to choose among overloads of multiple-argument methods, and it doesn't have an exact match. When there are primitive arguments, it also has to consider the allowed argument conversions. The full details take up a largish section of the JLS.
So, it is pretty simple:
1) Yes, the decision is made at compile-time. The compiler chooses the method with the most specific matching type. So the compiler will choose the Number version when the variable you pass as an argument is declared as a Number, even if it is an Integer at run-time. (If the compiler finds two "equally matching" methods, an ambiguous method error will make the compilation fail)
2) At run-time, there are no generics, everything is just an Object. Generics are a compile-time and source-code feature only. Therefore the compiler must do the best he can, because the VM surely can not.

Reference to method is ambiguous when using lambdas and generics

I am getting an error on the following code, which I believe should not be there... Using JDK 8u40 to compile this code.
public class Ambiguous {
public static void main(String[] args) {
consumerIntFunctionTest(data -> {
Arrays.sort(data);
}, int[]::new);
consumerIntFunctionTest(Arrays::sort, int[]::new);
}
private static <T> void consumerIntFunctionTest(final Consumer<T> consumer, final IntFunction<T> generator) {
}
private static <T> void consumerIntFunctionTest(final Function<T, ?> consumer, final IntFunction<T> generator) {
}
}
The error is the following:
Error:(17, 9) java: reference to consumerIntFunctionTest is ambiguous
both method consumerIntFunctionTest(java.util.function.Consumer,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous and method consumerIntFunctionTest(java.util.function.Function,java.util.function.IntFunction) in net.tuis.ubench.Ambiguous match
The error occurs on the following line:
consumerIntFunctionTest(Arrays::sort, int[]::new);
I believe there should be no error, as all Arrays::sort references are of type void, and none of them return a value. As you can observe, it does work when I explicitly expand the Consumer<T> lambda.
Is this really a bug in javac, or does the JLS state that the lambda cannot automatically be expanded in this case? If it is the latter, I would still think it is weird, as consumerIntFunctionTest with as first argument Function<T, ?> should not match.
In your first example
consumerIntFunctionTest(data -> {
Arrays.sort(data);
}, int[]::new);
the lambda expression has a void-compatible block which can be identified by the structure of the expression without the need to resolve the actual types.
In contrast, in the example
consumerIntFunctionTest(Arrays::sort, int[]::new);
the method reference has to be resolved to find out, whether it conforms to either, a void function (Consumer) or a value returning function (Function). The same applies to the simplified lambda expression
consumerIntFunctionTest(data -> Arrays.sort(data), int[]::new);
which could be both, void- compatible or value- compatible, depending on the resolved target method.
The problem is that resolving the method requires knowledge about the required signature, which ought to be determined via target typing, but the target type isn’t known until the type parameters of the generic method are known. While in theory both could be determined at once, the (still being awfully complex) process has been simplified in the specification in that method overload resolution is performed first and type inference is applied last (see JLS §15.12.2). Hence, the information that type inference could provide cannot be used for solving overload resolution.
But note that the first step described in 15.12.2.1. Identify Potentially Applicable Methods contains:
An expression is potentially compatible with a target type according to the following rules:
A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:
The arity of the target type's function type is the same as the arity of the lambda expression.
If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).
If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).
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.
…
The definition of potential applicability goes beyond a basic arity check to also take into account the presence and "shape" of functional interface target types. In some cases involving type argument inference, a lambda expression appearing as a method invocation argument cannot be properly typed until after overload resolution.
So your in first example one of the methods is sorted out by the lambda’s shape while in case of a method reference or a lambda expression consisting of a sole invocation expression, both potentially applicable methods endure this first selection process and yield an “ambiguous” error before type inference can kick in to aid finding a target method to determine if it’s a void or value returning method.
Note that like using x->{ foo(); } to make a lambda expression explicitly void-compatible, you can use x->( foo() ) to make a lambda expression explicitly value-compatible.
You may further read this answer explaining that this limitation of combined type inference and method overload resolution was a deliberate (but not easy) decision.
With method references, you could have entirely different parameter types, let alone return types, and still get this if you have another method where the arity (number of arguments) matches.
For example:
static class Foo {
Foo(Consumer<Runnable> runnableConsumer) {}
Foo(BiFunction<Long, Long, Long> longAndLongToLong) {}
}
static class Bar {
static void someMethod(Runnable runnable) {}
static void someMethod(Integer num, String str) {}
}
There's no way Bar.someMethod() could ever satisfy longAndLongToLong, and yet the code below emits the same compile error regarding ambiguity:
new Foo(Bar::someMethod);
Holger's answer explains the logic and pertinent clause in the JLS behind this rather well.
What about binary compatibility?
Consider if the longAndLongToLong version of Foo constructor didn't exist but was added later in a library update, or if the two parameter version of Bar.someMethod() didn't exist but added later: Suddenly previously compiling code can break due to this.
This is an unfortunate side-effect of method overloading and similar problems have affected plain method calls even before lambdas or method references came along.
Fortunately, binary compatibility is preserved. The relevant clause is in 13.4.23. Method and Constructor Overloading:
Adding new methods or constructors that overload existing methods or constructors does not break compatibility with pre-existing binaries. The signature to be used for each invocation was determined when these existing binaries were compiled; ....
While adding a new overloaded method or constructor may cause a compile-time error the next time a class or interface is compiled because there is no method or constructor that is most specific (§15.12.2.5), no such error occurs when a program is executed, because no overload resolution is done at execution time.

Is there any statement in Java which "void" can participate in?

Is there any statement in Java which "void" (with small v, returned from a method) can participate in?
For example it seems it not even possible to do voidMe() instanceof Void where:
void voidMe(){...}.
(This is a theoretical question out of curiosity.)
Yes - a simple method call:
voidMe();
That's a statement.
If you mean an expression, then
voidMe()
is still an expression too - just one where the result is nothing. From section 15.1 of the JLS:
When an expression in a program is evaluated (executed), the result denotes one of three things:
A variable (§4.12) (in C, this would be called an lvalue)
A value (§4.2, §4.3)
Nothing (the expression is said to be void)
...
An expression denotes nothing if and only if it is a method invocation (§15.12) that invokes a method that does not return a value, that is, a method declared void (§8.4). Such an expression can be used only as an expression statement (§14.8), because every other context in which an expression can appear requires the expression to denote something.
The highlighted part basically states that the only use of an expression such as voidMe() is a method invocation. There's no "bigger" expression that can have voidMe() as a part of it.
I copied from Java Specification §8.4.2
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:
If R1 is void then R2 is void.
It can be used by Class literals Java Specification §15.8.2
The type of void.class (§8.4.5) is Class<Void>.
...
A class literal evaluates to the Class object for the named type (or for void)
as defined by the defining class loader of the class of the current instance.
In Java, void is not a type or an object. Thus it cannot be part of an "expression".
Any operator or function in an expression would use its arguments as input. However, since void is not a valid type, and there is no object of type void, it would not be possible to evaluate the expression.
Thus, any void function must be a statement by itself.
You also cannot create an instance of Void, because "The Void class is an uninstantiable placeholder class".
Void is uninstantiable and thus you cannot use its value anywhere. (Seriously, what would you want to do with nothing? How can you even have an instance of nothing?)
You can however declare Void as a type parameter. This is necessary if you want to use some classes from the java.concurrent package. For example, you need to declare a Future<Void> in order to submit a void method onto an executor service. In one place you can then ask for result, which will be of type Void. This result is always null.

Categories

Resources