Java 11 Developer Certification - Method Overloading

December 15, 2020

Overloaded Method

An overloaded method is a method with the same name as another method, but with a different signature (parameter set). There is no limit in the number of overloaded methods we can create in any class. Lets look at the java code to get a proper understanding of overloaded methods We have primitive datatypes as parameters and one Object type parameter, which gets executed at the last method call of Byte type. Note that Byte method call does not execute the method accepting byte parameter.

package maxCode.online.Methods;

public class OverloadPrimitive {
	public static void main(String[] args) {
		OverloadPrimitive o = new OverloadPrimitive();
		o.methodOverloadingExample((byte) 1);
		o.methodOverloadingExample('a');
		o.methodOverloadingExample((short) 1);
		o.methodOverloadingExample(1);
		o.methodOverloadingExample(1L);
		o.methodOverloadingExample(1f);
		o.methodOverloadingExample(1D);
		o.methodOverloadingExample(true);
		o.methodOverloadingExample(Byte.valueOf((byte) 1));
	}

	private void methodOverloadingExample(byte b) {
		System.out.println("methodOverloadingExample for byte");
	}

	private void methodOverloadingExample(char c) {
		System.out.println("methodOverloadingExample for char");
	}

	private void methodOverloadingExample(short s) {
		System.out.println("methodOverloadingExample for short");
	}

	private void methodOverloadingExample(int i) {
		System.out.println("methodOverloadingExample for int");
	}

	private void methodOverloadingExample(long l) {
		System.out.println("methodOverloadingExample for long");
	}

	private void methodOverloadingExample(float l) {
		System.out.println("methodOverloadingExample for float");
	}

	private void methodOverloadingExample(double d) {
		System.out.println("methodOverloadingExample for double");
	}

	private void methodOverloadingExample(boolean b) {
		System.out.println("methodOverloadingExample for boolean");
	}

	private void methodOverloadingExample(Object o) {
		System.out.println("methodOverloadingExample for Object");
	}
}

Output

methodOverloadingExample for byte
methodOverloadingExample for char
methodOverloadingExample for short
methodOverloadingExample for int
methodOverloadingExample for long
methodOverloadingExample for float
methodOverloadingExample for double
methodOverloadingExample for boolean
methodOverloadingExample for Object

Now if we comment out the overloaded method accepting Object as parameter, the last method call will execute the method accepting byte parameter, and hence will print methodOverloadingExample for byte.

To understand overloaded methods properly, it is very important to understand what is NOT an overloaded method. Lets review that in the section below.

Understanding Method Overloading

So below is a simple code which shows what is NOT overloading. The method signature needs to be different for overloaded methods. If the method signatures are same, Java will not be able to resolve which method to execute during which method call.

In the below code, we have 2 methods named overloadedMethod and both have the same signature. So these methods will return a compile time error - Duplicate method overloadedMethod(int) in type NoLoad

package maxCode.online.Methods;

class NoLoad {
	public void overloadedMethod(int i) {
		System.out.println("overloadedMethod for int");
	}
	
	public void overloadedMethod(int i) {
		System.out.println("Not Overloaded");
	}
}

public class NotOverloaded {
	public static void main(String[] args) {
		NoLoad n = new NoLoad();
		n.overloadedMethod(1);
	}
}

In case we have varargs in multiple overloaded method, we can work it out by passing an array of that particular datatype, otherwise Java will throw compiler error.

Phases of method signature

The Java specification for determining the method signature is present here.

The section describes 3 phases of determining method signature

Phase 1: Perform an overloaded resolution without permitting boxing or unboxing conversion or the use of variable arity’s, or var arguments, method invocation.

Within Phase 1, if multiple methods meet this criteria, the best method is then selected by,

  • choose method with exact match or matches
  • choose method with more specific match or matches

For primitives, subtypes are considered to climb up either of these branches

  • char is a subtype of int
  • byte is NOT a subtype of char
  • int can be considered a subtype of double (only for method selection)

    • byte, short, int
    • char, int, long, float, double
  • If a primitive is passed and a method exists within a parameter that uses widening, this will be selected and narrowing conversion, on the other hand, won’t be selected.

Phase 2: allows boxing and unboxing, but ignores var args method invocation.

Phase 3: allows overloading to be combined with var args methods, boxing and unboxing.

In short, var arg methods will match dead last if better options exist.

Below is the code in which one class has 5 overloaded methods, each accepting a parameter of type Integer, long, short, Character and char varargs. We call these overloaded methods passing a char parameter which doesnt exactly match any of the overloaded methods.

The output is an interesting one as it prints long matched, and none of short or Character or varargs. So as per the above rules, a char will never be widened to a short, and it will match long on widening conversion before checking for varargs parameter.

It also didnt match method with Character because again as per the rules, the parameters wont be evaluated for wrappers with boxing, unboxing until the second phase, only IF the first phase completes with no matches.

So even though Character seem to be a better match here, Java Virtual Machine will not use it due to backward compatibility of older code. We can force it by passing the exact parameter as a wrapper Character.

package maxCode.online.Methods;

class WhichOne {
	public String thisOne(Integer i) {
		return "Integer matched";
	}

	public String thisOne(long i) {
		return "long matched";
	}

	public String thisOne(short s) {
		return "short matched";
	}

	public String thisOne(char... c) {
		return "char matched";
	}

	// Adding a method with matching wrapper
	public String thisOne(Character c) {
		return "Character matched";
	}
}

public class OverloadMatches {
	public static void main(String[] args) {
		WhichOne whichOne = new WhichOne();
		char c = 'a';
		System.out.println("Method (" + whichOne.thisOne(c) + ") was executed for " + c);
	}
}
Method (long matched) was executed for a

Constructor Overloading

Since a constructor is just a method with a few special restrictions, constructors can be overloaded too with the same set of rules applied. There are two ways to overload a constructor

  • using new to instantiate a new object and passing appropriate parameters
  • calling a different overloaded constructor from another constructor, also called constructor chaining.

If we use varargs as a paramter in constructor, it can be done only as the first statement and hence we would be able to create only a single chain.

Below is a simple example demonstrating constructor overloading

package maxCode.online.Methods;

//The MixAndMatch class has 3 constructors to demonstrate
//constructor overloading and constructor chaining
class MixAndMatch {
	String mix;
	String match;
	String mixAndMatch;
	static int counter = 0;

	// MixAndMatch constructor, no parameters
	MixAndMatch() {
		counter++;
	}

	// MixAndMatch constructor, one parameter
	MixAndMatch(String mixAndMatch) {
		// constructor chaining - call the no args constructor
		this();
		this.mixAndMatch = mixAndMatch;
		System.out.println("mixAndMatch = " + this.mixAndMatch + " for instance # " + counter);
	}

	// MixAndMatch constructor, two parameters
	MixAndMatch(String mix, String match) {
		// constructor chaining - call the single parameter constructor
		this(mix + " and " + match);
		this.mix = mix;
		this.match = match;
	}
}

public class ConstructorOverload {
	public static void main(String[] args) {
		// Test a variety of constructors
		new MixAndMatch("Mix", "Match");
		new MixAndMatch("Mix or Match");
		new MixAndMatch();
		new MixAndMatch("Not this", "Not that");
	}
}

Output

mixAndMatch = Mix and Match for instance # 1
mixAndMatch = Mix or Match for instance # 2
mixAndMatch = Not this and Not that for instance # 4

We are actually creating a constructor chain by using this(). So eventually the two String constructor will make a call to the single arg constructor, and the single arg constructor will in turn call the no-arg constructor.

Nothing is printed for the call to no-args constructor, since it just increments the counter and prints nothing.

Next we will look at some of the special examples of overloaded methods.

package maxCode.online.Methods;

class Calculator {
    public static long add(int a, Double f) {
        System.out.println("int + Double");
        long result = (long) (a + f);
        return result;
    }

    public static long add(int a, long... b) {
        System.out.println("int a, Var args long b");
        int total = a;
        for (long val : b) {
            total += val;
        }
        return total;
    }

    public static long add(long... a) {
        System.out.println("Var args long a");

        int total = 0;
        for (long val : a) {
            total += val;
        }
        return total;
    }

    public static long add(int a, Long b) {
        System.out.println("int + Long");
        return a + b;
    }
}

public class OverloadTests {
    public static void main(String[] args) {

        // Testing Calculator with multiple primitive data types
        var result = Calculator.add(1, 2);
        System.out.println("result = " + result);

        result = Calculator.add(1, 2, 3);
        System.out.println("result = " + result);

        result = Calculator.add(1, 10l);
        System.out.println("result = " + result);

        result = Calculator.add(1, 12.3);
        System.out.println("result = " + result);
    }
}
int a, Var args long b
result = 3
int a, Var args long b
result = 6
int + Long
result = 11
int + Double
result = 13

The above class has got multiple static overloaded methods for add. And in the main class, we are calling the add method passing appropriate parameters.

If we change the last method parameter and pass it as a float (12.3f), it will throw a compile time error since we do not have any method accepting float parameter, and the float parameter will not become a double wrapper.

Important things to remember here

  • a primitive data-type int will get widened to a long as it looks for a matching method parameter but it won’t ever become a long wrapper, which is why a method with a long wrapper parameter won’t match an int primitive parameter.
  • the method with the primitive long takes precedence over a method with the wrapper class long if we have both in the same class.
  • a float literal will never become a double wrapper.
  • Anytime if we have such overloaded methods where the JVM cannot decide which method to execute, we will get a compiler error.

Method overloading has a lot of variations and its best to practice multiple types of code to be absolutely clear with the concepts.

Thats all for overloading, we will look at static methods in the next section.