SwadhyAI Notes

All Java notes

Java Method Overloading

›Short versionOpen if you only have a minute.
  • Overloading is one name with different parameter types, counts, or order. The compiler chooses.
  • Return type is not part of the signature. Two methods that differ only by return type do not compile.
  • Resolution tries a strict fixed-arity match first, then boxing, then varargs.
  • Among matches in one phase, the most specific method wins. A tie is a compile error.
  • The choice is compile-time. Overriding is the run-time choice among instance methods with the same signature.

A calculator that offers addTwoInts, addTwoDoubles, and addThreeInts makes every call site repeat a distinction the arguments already show. The operation is addition. The inputs are what change.

keeps the name add and lets the carry the difference. The compiler selects a body before the program runs. That selection has a precise order. Skipping the order is how a call that looks like it should box into Integer widens to long instead.

What counts as a different method

add(int, int), add(double, double), and add(int, int, int) are three methods. A call with two int arguments uses the first. A call with two doubles uses the second. A call with three ints uses the third. Parameter names are irrelevant. add(int a, int b) and add(int left, int right) would be the same method written twice, and the class would not compile.

Calculator.java
public class Calculator {
    static int add(int a, int b) {
        return a + b;
    }

    static double add(double a, double b) {
        return a + b;
    }

    static int add(int a, int b, int c) {
        return a + b + c;
    }

    public static void main(String[] args) {
        System.out.println(add(5, 3));
        System.out.println(add(5.5, 3.2));
        System.out.println(add(1, 2, 3));
    }
}
Terminal
$ javac Calculator.java
$ java Calculator
8
8.7
6

Order is part of the signature when the types differ. createUser(String, int) and createUser(int, String) are both callable. The first argument’s type decides which body runs. Two strings in a row would not give you that protection.

Changing only the return type does not overload. int calculate(int, int) and double calculate(int, int) are a duplicate signature. The call calculate(5, 3) would not have a type-based way to choose, so the language rejects the declarations rather than guessing from how the caller uses the result.

The three phases

The compiler gathers methods with the right name, then tests them in phases. The first phase wants a fixed number of parameters and a strict match: identity or . An int argument matches an int parameter, and it also matches a long parameter by widening. It does not, in this phase, match an Integer parameter.

is the second phase. It runs when the first phase found no applicable method. Unboxing is allowed there too. Variable arity is the third phase. It runs when the first two phases found nothing. A varargs method does not steal a call that a fixed-arity method already accepts.

java
static void report(int value) {
    System.out.println("int");
}

static void report(Integer value) {
    System.out.println("Integer");
}

report(1);                    // int
report(Integer.valueOf(1));  // Integer

report(1) finds report(int) in the first phase, so the Integer overload is never consulted. report(Integer.valueOf(1)) matches Integer by identity in that same first phase. Unboxing to int would be a second-phase conversion, and the search does not get there. null matches Integer and does not match int, so report(null) prints Integer when both methods exist.

Remove report(int) and leave show(long) beside show(Integer). A bare 10 still does not box. Widening from int to long succeeds in the first phase, that phase already has a method, and show(Integer) is never asked. The call prints long.

java
static void show(long value) {
    System.out.println("long");
}

static void show(Integer value) {
    System.out.println("Integer");
}

show(10); // long

Most specific, and a tie

Several methods can survive one phase. label("Ada") is applicable to both label(String) and label(Object), by identity and by widening. The method is label(String). A string is an Object, and an arbitrary Object is not a String, so the string parameter accepts a narrower set of calls.

java
static void label(Object value) {
    System.out.println("object");
}

static void label(String value) {
    System.out.println("string");
}

label("Ada");          // string
label(Integer.valueOf(1)); // object

A tie is a compile error, not a run-time guess. mix(1, 2) with mix(int, double) and mix(double, int) makes both methods applicable in the first phase: each int can widen to double. Neither signature is more specific. The call needs a cast, such as mix(1, (double) 2), so only one method applies. The same thing happens with null when two reference overloads, Integer and Long, both accept it and neither is more specific.

is a different mechanism, and the names get swapped in reviews. Overloading selects a signature from the compile-time types of the arguments. Overriding selects an instance method body from the run-time class of the receiver, and the signature already matches. A subclass that declares draw(int) when the superclass has draw() has overloaded draw. Calls written as draw() still run the superclass method, unless the subclass also declares draw().

Generics do not create a second method the JVM can keep. After erasure, print(List<String>) and print(List<Integer>) are both the descriptor print(List), so the class is rejected with a name clash. The compiler can see List<String> at a call in source, and it still cannot emit two methods with one descriptor. Overload on a difference that survives erasure, such as List versus Map, or give the methods different names.

A class has report(int) and report(Integer). The call is report(1), with a bare int literal. Which body runs, and why is the Integer method left out?

report(int) runs. The literal is an int, so report(int) is applicable in the strict phase, without boxing. Resolution stops in that phase. report(Integer) would require boxing, which is only considered when no strict method applies. Passing Integer.valueOf(1) or null selects the Integer method, because those arguments do not match the int parameter strictly.