6.4 Method Overloading
One of the powerful features Java supports is declaring multiple methods with the same name within a class. As long as the parameters are configured differently, methods with the same name are recognized as separate methods.
1. What Is Overloading?
Normally, an identifier (name) must be unique within a class. However, as long as the parameter types, count, or order are different, multiple methods can be declared with the same name. This is called Method Overloading.
2. Conditions for Overloading
| Condition | Details |
|---|---|
| Method name | Must be the same |
| Parameter types | Different types satisfy overloading |
| Parameter count | Different counts satisfy overloading |
| Parameter order | Different order (with different types) also satisfies overloading |
| Return type | Irrelevant(return type alone cannot distinguish overloads) |
class OverloadingTest {
// 1. Different number of parameters -> overloading applies
int add(int a, int b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }
// 2. Different parameter types -> overloading applies
double add(double a, double b) { return a + b; }
long add(long a, long b) { return a + b; }
// 3. Different parameter order -> overloading applies (when types differ)
void print(String s, int n) { System.out.println(s + ": " + n); }
void print(int n, String s) { System.out.println(n + " - " + s); }
// Non-overloading examples (compile errors):
// int add(int a, int b) { ... } // already exists -> duplicate error
// double add(int a, int b) { ... } // only return type differs -> not overloading
}
A method that differs only in return type is not an overload. The compiler determines which method to call based only on the method name and parameters, so it cannot differentiate by return type alone.
3. System.out.println() Is the Classic Example of Overloading
The reason System.out.println() accepts any type of value is that the println method is overloaded for dozens of types.
// Inside Java standard library PrintStream class (conceptual example)
public class PrintStream {
public void println(boolean x) { ... }
public void println(char x) { ... }
public void println(int x) { ... }
public void println(long x) { ... }
public void println(float x) { ... }
public void println(double x) { ... }
public void println(String x) { ... }
public void println(Object x) { ... }
// ... 10+ overloads
}
// Thanks to this, we don't need to memorize different names for each type
System.out.println(42); // calls the int version
System.out.println(3.14); // calls the double version
System.out.println("Java"); // calls the String version
System.out.println(true); // calls the boolean version
4. Overloading vs Overriding
The two concepts have similar names but are completely different.
| Aspect | Overloading | Overriding |
|---|---|---|
| Where it occurs | Inside the same class | In a child class (inheritance) |
| Method name | Same | Same |
| Parameters | Different | Must be exactly the same |
| Return type | Can differ | Must be the same (covariant return allowed) |
| Determined at | Compile time(static binding) | Runtime(dynamic binding) |
| Keyword | None | @Override annotation recommended |
class Animal {
// Overloading: same name, different parameters within the same class
void sound() { System.out.println("..."); }
void sound(int times) { for(int i=0;i<times;i++) sound(); }
void sound(String prefix) { System.out.println(prefix + "..."); }
}
class Dog extends Animal {
// Overriding: redefines a parent method (same parameters)
@Override
void sound() { System.out.println("Woof!"); }
}
5. Auto Type Promotion and Overloading
When no overloaded method exactly matches the argument type, Java applies automatic type promotion to select the closest matching method.
public class AutoCastOverload {
static void print(int n) { System.out.println("int: " + n); }
static void print(long n) { System.out.println("long: " + n); }
static void print(double n) { System.out.println("double: " + n); }
public static void main(String[] args) {
print(10); // int: 10 (exact match: int -> int)
print(10L); // long: 10 (exact match: long -> long)
print(10.0); // double: 10.0 (exact match: double -> double)
byte b = 5;
print(b); // int: 5 (byte -> int auto promotion; int is the closest)
float f = 3.14f;
print(f); // double: 3.14 (float -> double auto promotion)
}
}
Auto promotion (widening) follows: byte -> short -> int -> long -> float -> double. When overloading, the method with the next wider type is selected.
6. Varargs and Overloading
Introduced in Java 5, variable arguments (varargs) allow you to write a method that accepts a variable number of parameters.
public class VarargsExample {
// int... is internally identical to int[]
static int sum(int... numbers) {
int total = 0;
for (int n : numbers) {
total += n;
}
return total;
}
// Mixing varargs with fixed parameters
static void log(String prefix, Object... messages) {
System.out.print("[" + prefix + "] ");
for (Object msg : messages) {
System.out.print(msg + " ");
}
System.out.println();
}
public static void main(String[] args) {
System.out.println(sum()); // 0 (no arguments)
System.out.println(sum(1, 2, 3)); // 6
System.out.println(sum(1, 2, 3, 4, 5)); // 15
log("INFO", "Server started");
log("ERROR", "Connection failed", "Code:", 404);
log("DEBUG", "a=", 10, "b=", 20, "sum=", 30);
}
}
- Varargs can only be declared once per method and only as the last parameter.
- Overloading a varargs method with an array parameter method can cause conflicts.
7. Advantage of Overloading: API Convenience
Overloading shines especially in library and API design. It allows users to easily use the same functionality with a variety of input forms.
8. Practical Example: Logger Class
Implementing a useful Logger class with overloading.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Logger {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String prefix;
private boolean enabled;
public Logger(String prefix) {
this.prefix = prefix;
this.enabled = true;
}
// Overload 1: message only
public void log(String message) {
print("INFO", message);
}
// Overload 2: level + message
public void log(String level, String message) {
print(level, message);
}
// Overload 3: message + integer data
public void log(String message, int value) {
print("INFO", message + " = " + value);
}
// Overload 4: message + double data
public void log(String message, double value) {
print("INFO", String.format("%s = %.2f", message, value));
}
// Overload 5: exception logging
public void log(String message, Exception e) {
print("ERROR", message + " [" + e.getClass().getSimpleName() + ": " + e.getMessage() + "]");
}
// Overload 6: format string
public void log(String format, Object... args) {
print("INFO", String.format(format, args));
}
private void print(String level, String message) {
if (!enabled) return;
String time = LocalDateTime.now().format(FORMATTER);
System.out.printf("[%s][%s][%s] %s%n", time, level, prefix, message);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public class LoggerTest {
public static void main(String[] args) {
Logger logger = new Logger("MyApp");
// Various overloaded log() calls
logger.log("Server has started");
logger.log("WARN", "Memory usage is high");
logger.log("Current connections", 42);
logger.log("CPU usage", 78.5);
logger.log("Users: %d, Server: %s", 1500, "prod-01");
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.log("Calculation error occurred", e);
}
}
}
Sample Output:
[2025-01-01 12:00:00][INFO][MyApp] Server has started
[2025-01-01 12:00:00][WARN][MyApp] Memory usage is high
[2025-01-01 12:00:00][INFO][MyApp] Current connections = 42
[2025-01-01 12:00:00][INFO][MyApp] CPU usage = 78.50
[2025-01-01 12:00:00][INFO][MyApp] Users: 1500, Server: prod-01
[2025-01-01 12:00:00][ERROR][MyApp] Calculation error occurred [ArithmeticException: / by zero]
9. Constructor Overloading Usage
public class Rectangle {
private double width;
private double height;
// Square: one side length
public Rectangle(double side) {
this(side, side);
}
// Rectangle: width x height
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
// 0x0 default rectangle
public Rectangle() {
this(0, 0);
}
public double area() { return width * height; }
public double perimeter() { return 2 * (width + height); }
@Override
public String toString() {
return String.format("Rectangle(%.1f x %.1f)", width, height);
}
}
public class RectangleTest {
public static void main(String[] args) {
Rectangle square = new Rectangle(5); // square
Rectangle rect = new Rectangle(4, 7); // rectangle
Rectangle empty = new Rectangle(); // default
System.out.println(square + " area: " + square.area()); // 25.0
System.out.println(rect + " area: " + rect.area()); // 28.0
System.out.println(empty + " area: " + empty.area()); // 0.0
}
}
Summary
| Concept | Key Content |
|---|---|
| Overloading | Same name, different parameters (type/count/order) |
| Overloading condition | Parameters must differ (return type is irrelevant) |
| Classic example | System.out.println() |
| Overloading vs Overriding | Compile-time vs runtime decision |
| Auto type promotion | If no exact type match, the next wider type's method is chosen |
| Varargs | type... form, only as the last parameter |
| Advantage | API convenience, intuitive method naming |