Reference to method is ambiguous when using lambdas and generics - java

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.

Related

Method reference notation vs "standard" Lambda notation

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.

forEach parameter does not match Consumer functional interface parameters but code still compiles, why?

I am studying for the OCP exam and I noticed a following snippet of code, where the parameter to the forEach method invoked on a DoubleStream must match that of the DoubleConsumer functional interface, however the lambda does not match the required types, why does it still compile?
DoubleStream.of(3.14159, 2.71828)
.forEach(c -> service.submit(
() -> System.out.println(10*c)
));
DoubleConsumer (accepts an argument of type Double and has a return type of void), however this forEach has a return type of Future<?> where ? denotes the return type of the Runnable lambda which is void, Future - this is not void. I am saying this because the return type of service.submit(...) is Future<?> it is not void, why does this code compile?
It is not really true that the return type of the lambda expression and the return type of the function type of the target functional interface type has to exactly match. The Java Language Specification specifies that void is a special case.
In §15.27.3, it says:
A lambda 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 the ground target type derived from T.
We are in an invocation context here. T is DoubleConsumer. The ground target type derived from it is also DoubleConsumer, and its function type is a method that takes a double and returns void.
Let's see what does "congruent" mean:
A lambda expression is congruent with a function type if all of the following are true:
[...]
If the lambda parameters are assumed to have the same types as the function type's parameter types, then:
If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
"assumed to have the same types as the function type's parameter types" basically means that you did not explicitly write out the type of c.
A statement expression is just an expression that can be made into a statement by adding a ; at the end. Any method call is a statement expression. This is why the submit call compiles.
5 is not a statement expression (but it is an expression), so c -> 5 does not compile.
If you think about it, by saying that methods that returns something should not be assigned to functional interfaces whose function type has a void return type, you are saying that "functions that take a A and gives out a B" are not a kind of "consumers of A". However, they clearly are "consumers of A"! They take in an A after all. Whether or not something is a consumer of A doesn't depend on what they produce.
Hence, Java is designed to allow this.

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

Why does the Java 8 generic type inference pick this overload?

Consider the following program:
public class GenericTypeInference {
public static void main(String[] args) {
print(new SillyGenericWrapper().get());
}
private static void print(Object object) {
System.out.println("Object");
}
private static void print(String string) {
System.out.println("String");
}
public static class SillyGenericWrapper {
public <T> T get() {
return null;
}
}
}
It prints "String" under Java 8 and "Object" under Java 7.
I would have expected this to be an ambiguity in Java 8, because both overloaded methods match. Why does the compiler pick print(String) after JEP 101?
Justified or not, this breaks backward compatibility and the change cannot be detected at compile time. The code just sneakily behaves differently after upgrading to Java 8.
NOTE: The SillyGenericWrapper is named "silly" for a reason. I'm trying to understand why the compiler behaves the way it does, don't tell me that the silly wrapper is a bad design in the first place.
UPDATE: I've also tried to compile and run the example under Java 8 but using a Java 7 language level. The behavior was consistent with Java 7. That was expected, but I still felt the need to verify.
The rules of type inference have received a significant overhaul in Java 8; most notably target type inference has been much improved. So, whereas before Java 8 the method argument site did not receive any inference, defaulting to Object, in Java 8 the most specific applicable type is inferred, in this case String. The JLS for Java 8 introduced a new chapter Chapter 18. Type Inference that's missing in JLS for Java 7.
Earlier versions of JDK 1.8 (up until 1.8.0_25) had a bug related to overloaded methods resolution when the compiler successfully compiled code which according to JLS should have produced ambiguity error Why is this method overloading ambiguous? As Marco13 points out in the comments
This part of the JLS is probably the most complicated one
which explains the bugs in earlier versions of JDK 1.8 and also the compatibility issue that you see.
As shown in the example from the Java Tutoral (Type Inference)
Consider the following method:
void processStringList(List<String> stringList) {
// process stringList
}
Suppose you want to invoke the method processStringList with an empty list. In Java SE 7, the following statement does not compile:
processStringList(Collections.emptyList());
The Java SE 7 compiler generates an error message similar to the following:
List<Object> cannot be converted to List<String>
The compiler requires a value for the type argument T so it starts with the value Object. Consequently, the invocation of Collections.emptyList returns a value of type List, which is incompatible with the method processStringList. Thus, in Java SE 7, you must specify the value of the value of the type argument as follows:
processStringList(Collections.<String>emptyList());
This is no longer necessary in Java SE 8. The notion of what is a target type has been expanded to include method arguments, such as the argument to the method processStringList. In this case, processStringList requires an argument of type List
Collections.emptyList() is a generic method similar to the get() method from the question. In Java 7 the print(String string) method is not even applicable to the method invocation thus it doesn't take part in the overload resolution process. Whereas in Java 8 both methods are applicable.
This incompatibility is worth mentioning in the Compatibility Guide for JDK 8.
You can check out my answer for a similar question related to overloaded methods resolution Method overload ambiguity with Java 8 ternary conditional and unboxed primitives
According to JLS 15.12.2.5 Choosing the Most Specific Method:
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.
Then:
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).
m2 is not generic, and m1 and m2 are applicable by variable arity
invocation, and where the first k variable arity parameter types of m1
are S1, ..., Sk and the first k variable arity parameter types of m2
are T1, ..., Tk, the type Si is more specific than Ti for argument ei
for all i (1 ≤ i ≤ k). Additionally, if m2 has k+1 parameters, then
the k+1'th variable arity parameter type of m1 is a subtype of the
k+1'th variable arity parameter type of m2.
The above conditions are the only circumstances under which one method may be more specific than another.
A type S is more specific than a type T for any expression if S <: T (§4.10).
The second of three options matches our case. Since String is a subtype of Object (String <: Object) it is more specific. Thus the method itself is more specific. Following the JLS this method is also strictly more specific and most specific and is chosen by the compiler.
In java7, expressions are interpreted from bottom up (with very few exceptions); the meaning of a sub-expression is kind of "context free". For a method invocation, the types of the arguments are resolved fist; the compiler then uses that information to resolve the meaning of the invocation, for example, to pick a winner among applicable overloaded methods.
In java8, that philosophy does not work anymore, because we expect to use implicit lambda (like x->foo(x)) everywhere; the lambda parameter types are not specified and must be inferred from context. That means, for method invocations, sometimes the method parameter types decide the argument types.
Obviously there's a dilemma if the method is overloaded. Therefore in some cases, it's necessary to resolve method overloading first to pick one winner, before compiling the arguments.
That is a major shift; and some old code like yours will fall victim to incompatibility.
A workaround is to provide a "target typing" to the argument with "casting context"
print( (Object)new SillyGenericWrapper().get() );
or like #Holger's suggestion, provide type parameter <Object>get() to avoid inference all together.
Java method overloading is extremely complicated; the benefit of the complexity is dubious. Remember, overloading is never a necessity - if they are different methods, you can give them different names.
First of all it has nothing to do with overriding , but it has to deal with overloading.
Jls,. Section 15 provides lot of information on how exactly compiler selects the overloaded method
The most specific method is chosen at compile time; its descriptor
determines what method is actually executed at run time.
So when invoking
print(new SillyGenericWrapper().get());
The compiler choose String version over Object because print method that takes String is more specific then the one that takes Object. If there was Integer instead of String then it will get selected.
Moreover if you want to invoke method that takes Object as a parameter then you can assign the return value to the parameter of type object E.g.
public class GenericTypeInference {
public static void main(String[] args) {
final SillyGenericWrapper sillyGenericWrapper = new SillyGenericWrapper();
final Object o = sillyGenericWrapper.get();
print(o);
print(sillyGenericWrapper.get());
}
private static void print(Object object) {
System.out.println("Object");
}
private static void print(Integer integer) {
System.out.println("Integer");
}
public static class SillyGenericWrapper {
public <T> T get() {
return null;
}
}
}
It outputs
Object
Integer
The situation starts to become interesting when let say you have 2 valid method definations that are eligible for overloading. E.g.
private static void print(Integer integer) {
System.out.println("Integer");
}
private static void print(String integer) {
System.out.println("String");
}
and now if you invoke
print(sillyGenericWrapper.get());
The compiler will have 2 valid method definition to choose from , Hence you will get compilation error because it cannot give preference to one method over the other.
I ran it using Java 1.8.0_40 and got "Object".
If you'll run the following code:
public class GenericTypeInference {
private static final String fmt = "%24s: %s%n";
public static void main(String[] args) {
print(new SillyGenericWrapper().get());
Method[] allMethods = SillyGenericWrapper.class.getDeclaredMethods();
for (Method m : allMethods) {
System.out.format("%s%n", m.toGenericString());
System.out.format(fmt, "ReturnType", m.getReturnType());
System.out.format(fmt, "GenericReturnType", m.getGenericReturnType());
}
private static void print(Object object) {
System.out.println("Object");
}
private static void print(String string) {
System.out.println("String");
}
public static class SillyGenericWrapper {
public <T> T get() {
return null;
}
}
}
You will see that you get:
Object public T
com.xxx.GenericTypeInference$SillyGenericWrapper.get()
ReturnType: class java.lang.Object
GenericReturnType: T
Which explains why the method overloaded with Object is used and not the String one.

Overloading var-args

Who can explain why first method preferable than second?
I know this rules for overloading (except first of all compiler find appropriate args)
widening
autoboxing
var-args
Code:
public class Proba{
public static void show(Object ... args){
System.out.println("Object ...");
}
public static void show(Integer[] ... args){
System.out.println("Integer ...");
}
public static void main(String[] args) {
Integer[] array = {3,2,5,1};
show(array);
}
}
Console : Object ...
The rules of method resolution in Java require that a match be attempted without auto-(un)boxing and variable arity before attempting a match with those features. This ensures source compatibility with language versions that predate those features.
The rules for overload resolution are described in the JLS (§15.12.2):
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.
Deciding whether a method is applicable will, in the case of generic methods
(§8.4.4), require that type arguments be determined. Type arguments may be
passed explicitly or implicitly. If they are passed implicitly, they must be inferred
(§15.12.2.7) from the types of the argument expressions.
If several applicable methods have been identified during one of the three phases
of applicability testing, then the most specific one is chosen, as specified in section
§15.12.2.5.
In your example, there are two candidates during Step 1: the method with an Object[] parameter, and the method with an Integer[][] parameter. The argument type at your call site is Integer[]. Since Object[] is assignable from Integer[], but Integer[][] is not, a single applicable method has been found, and overload resolution halts there. Steps 2 and 3 are never reached in this case.
Mike is correct; there are 3 phases,
15.12.2.2. Phase 1: Identify Matching Arity Methods Applicable by Subtyping
15.12.2.3. Phase 2: Identify Matching Arity Methods Applicable by Method Invocation Conversion
15.12.2.4. Phase 3: Identify Applicable Variable Arity Methods
show(Object[]) is chosen in the first phase, but show(Integer[]...) can only be chosen in the 3rd phase.
If the first method signature is changed to show(Object[] ... args), you'll see the expected result.
If the second method signature is changed to show(Integer ... args), you'll also see the expected result. The method also fits in phase 1, and it is more specific than show(Object...)
If we have
public static void show(Object ... args){
System.out.println("Object ...");
}
static class IntArray{}
public static void show(IntArray ... args){
System.out.println("IntArray ...");
}
show(new IntArray());
it prints the expected IntArray .... Here IntArray is not a subtype of Object[].
This is all too confusing. Programmers usually don't know about these phases; they think about all applicable methods and the most specific one among them. It might have been better if the spec does that too.

Categories

Resources