Method Overloading In Java: Essential OCA Java 8 Tips & Tricks

In my last article, Methods in Java, we explored various aspects of methods, including access modifiers and static specifiers. However, we didn’t cover method overloading. This article will focus on method overloading and the nuances you may encounter on exam day.

Method Overloading

Method overloading, also known as compile-time polymorphism, enables us to define multiple methods with the same name but different parameter lists. It is called compile-time polymorphism because the compiler determines the appropriate method to call based on the method signatures at compile time.

Method Lookup

Overloading and Widening Primitive Conversion.

Java always looks for an exact match first. If no exact match is found, Java will look for methods with wider data types in a specific order (ie, the wider data int types of int are long >float > double.)

Consider the following example:

public class MathUtils {

    public static long add(long a, long b) {
        System.out.println("long version being called");
        return a + b;
    }

    public static float add(float a, float b) {
        System.out.println("float version being called");
        return a + b;
    }
    
}

public class Main {
    public static void main(String[] args) {
        System.out.println(MathUtils.add(3, 5));
    }
}

In this example, calling add(3, 5) will invoke the add(long, long) method since there is no exact match for add(int, int), and long is the next wider type.

Overloading & AutoBoxing

Consider the following example:

public class MathUtils {

    public static int add(int a, int b) {
        System.out.println("int version being called");
        return a + b;
    }
    
    public static int add(Integer a, Integer b) {
        System.out.println("Integer version being called");
        return a + b;
    }
}


public class Main {
    public static void main(String[] args) {
        System.out.println(MathUtils.add(3, 5));
    }
}

In this example, calling add(3, 5) will invoke the add(int, int) method. Although Java can autobox primitives into their wrapper classes, it prioritizes an exact match with primitive types. If the add(int, int) method did not exist, Java would then box the int arguments and call add(Integer, Integer)— provided that there are no methods such as add(long, long), add(float, float), or add(double, double), which would take precedence over add(Integer, Integer).

Overloading and Varargs

public class MathUtils {

    public static int add(int... x) {
        System.out.println("varargs version being called");
        int s = 0;
        for (int i : x) {
            s += i;
        }
        return s;
    }
    
}

public class Main {
    public static void main(String[] args) {
        System.out.println(MathUtils.add(3, 5));
    }
}

In this example, calling add(3, 5) will invoke the add(int...) method, since there is no add(int, int), add(long, long), add(float, float), add(double, double), or add(Integer, Integer).

Consider this code:

public class MathUtils {

    public static int add(int... x) {
        System.out.println("varargs version being called");
        int s = 0;
        for (int i : x) {
            s += i;
        }
        return s;
    }

    public static int add(int[] x) {
        System.out.println("array version being called");
        int s = 0;
        for (int i : x) {
            s += i;
        }
        return s;
    }
    
}

This code will not compile because, in Java, varargs are essentially syntactic sugar for arrays. Defining both add(int...) and add(int[]) is considered a duplicate method signature by the compiler.

General Workflow to Resolve Overloaded Method Call

Method Overloading: Workflow to Resolve Overloaded Method

When the compiler resolves overloaded method calls, it follows these steps:

  • Exact Match: The compiler first looks for an exact match of the method signature that matches the provided argument types exactly.
    • Match Found: add(int, int)
  • Widening Primitive Conversion: If no exact match is found, the compiler looks for methods that can be called by widening the primitive types.
    • int to long: Converts the int arguments to long.
    • Match Found: add(long, long)
    • int to float: Converts the int arguments to float.
    • Match Found: add(float, float)
    • int to double: Converts the int arguments to double.
    • Match Found: add(double, double)
  • Boxing Conversion: If no suitable widening conversion is found, the compiler looks for methods where the primitive types can be boxed to their corresponding wrapper classes.
    • int to Integer: Converts the int arguments to Integer.
    • Match Found: add(Integer, Integer)
  • Varargs: If no exact match, widening, or boxing conversion is found, the compiler will consider methods with varargs.
    • int… x: Matches any number of int arguments.
    • Match Found: add(int...)
  • Widening Reference Conversion: If still no match, the compiler looks for methods that can be called by widening the reference types (e.g., from a subclass to a superclass).
    • int to Object: Converts the int arguments to Object. Match Found: add(Object, Object)
  • Ambiguity and Errors: If there are multiple equally specific methods, the compiler will throw an ambiguity error. If no suitable method is found, the compiler will throw a “method not found” error.

Conclusion

Java follows a specific order of precedence, starting with the most specific match and moving to less specific matches if necessary. By understanding this workflow, you can better anticipate which method will be called in your overloaded methods.

Leave a Reply

Your email address will not be published. Required fields are marked *

Join the Tribe