Why does the compiler prefer an int overload to a varargs char overload for a char?
Asked Answered
H

4

63

Code

public class TestOverload {

    public TestOverload(int i){System.out.println("Int");}
    public TestOverload(char... c){System.out.println("char");}

    public static void main(String[] args) {
        new TestOverload('a');
        new TestOverload(65);
    }
}

Output

Int
Int

Is it expected behaviour? If so, then why? I am expecting: char, Int

Note: I am using Java 8

Histogenesis answered 9/9, 2015 at 5:25 Comment(8)
solution - add a (char) methodTrimester
The First Rule of Programming: It's Always Your FaultRandy
Interesting design choice they made there. In C# I believe the equivilent to '...' is params. C# would chose the char, behaviour I consider more sensible, but it is of course a different language.Spoliate
@NathanCooper: varargs were added rather late, so their extremely low priority is necessary for backwards-compatibility, or rather library evolvability. You don't want existing clients to suddenly choose a different overload, just because you added a varargs overload to your existing public API.Burchett
What sort of method could take either an int or a char and do something distinctive based on the argument type. Not theoretically, but actually. If you can answer that, can you tell me what you'd name such a function and why overloading it would be better than using a different name? A problem that only happens for TestOverload() isn't an actual problem.Seepage
In my case, I wanted to overloaded constructor of a CustomList. User can pass char..., or int initialSize.Histogenesis
@AmitGupta just add an explicit cast if you want to force a specific overloaded method to be used. In this case, a single char is an int, not a char array (which is what you supplied as a recognised argument to your overloaded constructor).Sileas
@black, new edited title seems having a complete question in itself. So it is confusing me.Histogenesis
R
80

Methods with varargs (...) have the lowest priority when the compiler determines which overloaded method to choose. Therefore TestOverload(int i) is chosen over TestOverload(char... c) when you call TestOverload with a single char parameter 'a', since a char can be automatically promoted to an int.

JLS 15.12.2 :

  1. 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.

  2. 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.

  3. The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.

EDIT:

It you wish to force the compiler to call the TestOverload(char... c) constructor, you can pass to the constructor call a char[] :

new TestOverload (new char[] {'a'});
Rosabella answered 9/9, 2015 at 5:27 Comment(8)
Oh I though either it'll call correct method or will give an ambiguity error. Is there any way to call correct method without changing the method signature?Histogenesis
@AmitGupta You can pass a char array having a single char : new TestOverload (new char[] {'a'});Rosabella
new TestOverload(new char[] {'a'})Yellowbird
@Rosabella For completeness, you should probably add the explanation of how to explicitly call the varargs method to your answer. :-)Cari
If you add public TestOverload(char c){ this(new char[]{ c }); }, calling the constructor becomes nicer: new TestOverload('a'), just as the OP wanted.Absolution
@Absolution I assumed the OP's request to call correct method without changing the method signature also meant not adding more constructors.Rosabella
new TestOverload (Character.toChars ('a')) would be confusing :) Too bad there is no char [] Arrays.toArray (char...)Derma
The reason this is the case is that varargs were added later and existing code which called a non var arg method should continue to do so.Duodenal
C
35

Yes, it is expected behaviour. The precedence for method calling goes like this :

  1. Widending
  2. Boxing
  3. Varargs

Below is excerpt from Java docs related to same :-

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.

Cisneros answered 9/9, 2015 at 5:37 Comment(1)
Sorry, I started to edit that question but then realized it behind my capability :-). So downvoted and saved what ever I have fixed. Initially i though only question body needs fix. But i cannot format any question.Cisneros
M
12

Solid advice from Joshua Bloch (Effective Java, 2nd Ed):

"only choose as arguments for an overloaded method those that have -radically- different types."

An object with a radically different type is one that can not reasonably be cast into another of the argument types. Following this rule can potentially save you hours of debugging a mysterious error that can happen when the compiler chooses at compile time the method overloading that you did not expect.

Your lines of code violate this rule and open the door for bugs:

public TestOverload(int i){System.out.println("Int");}
public TestOverload(char... c){System.out.println("char");}

A char is interconvertible with an int and so the only way you can predict what will happen with the invocations is to go to the Java Language Specification and read the somewhat arcane rules about how overloadings are resolved.

Luckily, this situation shouldn't need JLS research. If you have arguments that are not radically different from each other, probably the best option is to not overload. Give the methods different names so that there is no possibility for error or confusion on the part of anyone who may need to maintain the code.

Time is money.

Mascara answered 9/9, 2015 at 8:38 Comment(4)
I think overloading with similar types is acceptable if the behaviour of the overloads is very similar. e.g. int max(int x, int y) and long max(long x, long y) are perfectly fine.Triphibious
Only the problem is when you have overloaded constructors not methods. You can't choose different name. As it was in my case. However I modified my code so that it'll use this runtime polymorphism to determine which method should be called. :)Histogenesis
To amplify a key point, what kind of method could usefully take a char or an int and do something distinctive in each case? I really can't think of one. I also agree that "the best option is not to overload". Nice answer to a language misfeature.Seepage
I created my own primitive list where user can pass size of list or just char... to create it's object.Histogenesis
M
0

I took the code from this link and modified some parts of it:

    public static void main(String[] args) {
    Byte i = 5;
    byte k = 5;
    aMethod(i, k);
}

//method 1
static void aMethod(byte i, Byte k) {
    System.out.println("Inside 1");
}

//method 2
static void aMethod(byte i, int k) {
    System.out.println("Inside 2");
}

//method 3
static void aMethod(Byte i, Byte k) {
    System.out.println("Inside 3 ");
}

//method 4
static void aMethod(Byte  i, Byte ... k) {
    System.out.println("Inside 4 ");
}

The compiler gives error (The method is ambiguous for the type Overloading) for methods 1, 2 and 3 but not 4 (why?)

The answer lies in the mechanism which java uses to match method calls to method signatures. The mechanism is done in three phases, in each phase if it finds matching method it stops:

+phase one: use widening to find matching method (no matching methods found)

+phase two: (also) use boxing/unboxing to find matching method (method 1,2 and 3 match)

+phase three: (also) use var args (method 4 matches!)

Materially answered 16/1, 2017 at 20:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.