Java type promotion in parameters

I think this has something to do with JLS's specific rule about 15.12.2.5. Choosing the Most Specific Method. It states that:

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.

How Java chooses the most specific method is further explained by the text:

The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error. In cases such as an explicitly typed lambda expression argument (§15.27.1) or a variable arity invocation (§15.12.2.4), some flexibility is allowed to adapt one signature to the other.

In the case of your example, all methods are accessible and applicable to method invocation, therefore, Java needs to determine which of them is most specific.

For these methods, none can be determined to be more specific:

public static void printSum(int a, double b) {
    System.out.println("In intDBL " + (a + b));
} // int, double cannot be passed to long, long or double, long without error

public static void printSum(long a, long b) {
    System.out.println("In long " + (a + b));
} // long , long cannot be passed to int, double or double, long without error

public static void printSum(double a, long b) {
    System.out.println("In doubleLONG " + (a + b));
} // double, long cannot be passed to int, double or long, long without error

The fourth method clears ambiguity precisely because it fulfills the necessary condition to be most specific.

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

That is, (int, long) can be passed to (int, double), (long, long), or (double, long) without compilation errors.


It is indeed a very interesting question. Let's go through the Java Language Specification step by step.

  1. When the compiler is trying to identify potentially applicable methods, the first thing it does is serching for methods applicable by Strict Invocation.

  2. In your case there is no such methods, so the next step is to find methods applicable by Loose Invocation

  3. At this point all of the methods match, so the most specific method (§15.12.2.5) is chosen among the methods that are applicable by loose invocation.

This is a key moment, so let's look at this closely.

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:

(We are interested in the following case only):

  • 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).

Simply put, a method is more specific if all of its parameters types are more specific. And

A type S is more specific than a type T for any expression if S <: T (§4.10).

Expression S <: T means that S is a subtype of T. For primitives we have the following relationship:

double > float > long > int

So let's look at your methods and see which one is more specific than others.

public static void printSum(int a, double b) {  // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(double a, long b) { // method 2
    System.out.println("In doubleLONG " + (a + b));
}

In this example the first parameter of the method 1 is obviously more specific than the first parameter of the method 2 (if you call them with integer values: printSum(1, 2)). But the second parameter is more specific for the method 2, because long < double. So none of these methods is more specific than another. That's why you have an ambiguity here.

In the following example:

public static void printSum(int a, double b) { // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(long a, double b) { // method 2
    System.out.println("In longDBL " + (a + b));
}

the first parameter type of the method 1 is more specific than the one in the method 2, because int < long and the second parameter type is the same for both of them, that's why the method 1 is chosen.