Overloading
Table of Contents
Introduction
Oftentimes when writing classes in Java, we run into situations where a method provides useful functionality, but its implementation should change depending on the arguments sent to it. Java allows us to redefine a method with the same name but a different signature through overloading.
How To Overload a Method
Overloading in Java is simple. For a method to be overloaded, the name of the method must be reused and a different set of parameters must be defined. The new set can be a different number of parameters, different types of parameters, or a mixture of both. Take the Log class below as an example:
1: public class Logger { 2: 3: static void log(String message){ 4: System.out.printf("[LOG] %s%n", message); 5: } 6: 7: static void log(String message, String level){ 8: System.out.printf("[%s] %s%n", level, message); 9: } 10: 11: static void log(String message, String level, int lineNumber){ 12: System.out.printf("[%s] (line %d) %s%n", level, lineNumber, message); 13: } 14: 15: static void log(Exception e){ 16: System.out.printf("[ERROR] %s: %s%n", e.getClass().getSimpleName(), e.getMessage()); 17: } 18: 19: public static void main(String[] args){ 20: log("Application started"); 21: log("Disk usage above 90%", "WARNING"); 22: log("Variable x is null", "ERROR", 42); 23: log(new IllegalArgumentException("Invalid input supplied")); 24: } 25: }
Lines 3, 7, 11, and 15 all declare a method named log. What varies between them are the parameters provided in the method declarations. The first declaration takes one String; the second takes two Strings; the third takes two Strings and an int; the fourth and final takes an Exception. The compiler "knows" which log to call based on the arguments provided in the method call. Line 20's call corresponds with Line 3's declaration, etc.
This is called ad hoc polymorphism.
When Overloading is Useful
Overloading occurs frequently within the Java standard library. This is often because Java programmers favour streamlined, aesthetically cohesive API's over distinctly named methods. For example, some believe it is easier to think about a single log method that changes based on argument context than a logSingleMessage, logMessageWithLevel, logMessageWithLineNumber, and logWithException.
Overloading Constructors
Overloading often pops up in constructor definitions. For example, the String class has 16 unique constructors! We can see some of them utilized below:
1: import java.nio.charset.StandardCharsets; 2: 3: public class AllStringConstructors { 4: public static void main(String[] args) throws Exception { 5: 6: // 1. String() — empty string 7: String s1 = new String(); 8: System.out.println("1: '" + s1 + "'"); 9: 10: // 2. String(String original) — copy of another string 11: String s2 = new String("Hello"); 12: System.out.println("2: " + s2); 13: 14: // 3. String(char[]) — from char array 15: char[] chars = {'J', 'a', 'v', 'a'}; 16: String s3 = new String(chars); 17: System.out.println("3: " + s3); 18: 19: // 4. String(char[], int offset, int count) — subarray of chars 20: String s4 = new String(chars, 1, 3); // "ava" 21: System.out.println("4: " + s4); 22: 23: // 5. String(int[] codePoints, int offset, int count) — from Unicode code points 24: int[] codePoints = {72, 101, 108, 108, 111}; // "Hello" 25: String s5 = new String(codePoints, 0, codePoints.length); 26: System.out.println("5: " + s5); 27: 28: // 6. String(byte[]) — from bytes using default charset 29: byte[] bytes = "Java".getBytes(); 30: String s6 = new String(bytes); 31: System.out.println("6: " + s6); 32: 33: // 7. String(byte[], int offset, int length) — subarray of bytes, default charset 34: String s7 = new String(bytes, 1, 3); // "ava" 35: System.out.println("7: " + s7); 36: 37: // 8. String(byte[], Charset) — from bytes using specified Charset object 38: String s8 = new String(bytes, StandardCharsets.UTF_8); 39: System.out.println("8: " + s8); 40: 41: // 9. String(byte[], String charsetName) — from bytes using charset name 42: String s9 = new String(bytes, "UTF-8"); 43: System.out.println("9: " + s9); 44: 45: // 10. String(byte[], int offset, int length, Charset) — subarray with Charset object 46: String s10 = new String(bytes, 0, 4, StandardCharsets.UTF_8); 47: System.out.println("10: " + s10); 48: 49: // 11. String(byte[], int offset, int length, String charsetName) — subarray with charset name 50: String s11 = new String(bytes, 0, 4, "UTF-8"); 51: System.out.println("11: " + s11); 52: 53: // 12. String(StringBuffer) — from StringBuffer 54: StringBuffer sb = new StringBuffer("Hello from StringBuffer"); 55: String s12 = new String(sb); 56: System.out.println("12: " + s12); 57: 58: // 13. String(StringBuilder) — from StringBuilder 59: StringBuilder sbl = new StringBuilder("Hello from StringBuilder"); 60: String s13 = new String(sbl); 61: System.out.println("13: " + s13); 62: } 63: }
Compiling and running we get:
josephraskind@stargazer:/tmp/overloading$ javac AllStringConstructors.java; java AllStringConstructors 1: '' 2: Hello 3: Java 4: ava 5: Hello 6: Java 7: ava 8: Java 9: Java 10: Java 11: Java 12: Hello from StringBuffer 13: Hello from StringBuilder
Exercises
- The
BankAccountclass from earlier chapters currently requires both an owner name and an initial balance to be provided at construction time. Add a second constructor toBankAccountthat takes only an owner name and defaults the balance to0.0. Write a program that creates one account using each constructor and callsprintBalanceon both. What does this tell you about when overloading constructors is useful? The chapter states that return type alone cannot distinguish two overloads. Attempt to compile the following class and record the error:
public class InvalidOverload { static int compute(int x){ return x * 2; } static double compute(int x){ return x * 2.0; } }
In your own words, explain why Java cannot use the return type to resolve which method to call. Hint: think about what happens when the return value is not assigned to a variable, as in
System.out.println(compute(5)).When the compiler must choose between two overloaded methods and an exact match is not available, it will use widening to find the closest compatible type. Write the following class, predict which overload will be called for each invocation, then compile and verify:
public class Widening { static void print(int x){ System.out.println("int: " + x); } static void print(double x){ System.out.println("double: " + x); } public static void main(String[] args){ print(42); // which overload? print(3.14); // which overload? print(42L); // which overload? (long) print((byte) 5); // which overload? (byte) } }
What general rule can you infer about how Java resolves overloads when no exact match exists?
- Add a
depositoverload toBankAccountthat accepts anintin addition to the existingdoubleversion. Write a program that calls both overloads and verify the results are identical. Then consider: is this a good use of overloading, or would a singledoublemethod suffice? What are the tradeoffs? - The chapter mentions that the
Stringclass has 13 constructors. Compile and run theAllStringConstructorsprogram from the chapter. Then identify which constructors produce identical output despite having different signatures (e.g. constructors 8, 9, 10, and 11 all printJava). Explain why different constructors can produce the same result, and why Java still provides all of them as separate overloads rather than collapsing them into one.