Java · lesson 21 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Method Overloading in Java
Learn Java method overloading by running it: how the compiler picks an overload through widening, boxing and varargs, plus the null and remove(int) traps.
By SkillByExample · Updated
Overloading means several methods share a name but differ in their parameter lists. The compiler
picks one at compile time from the argument types, so it is sometimes called compile-time
polymorphism. Every example is a single file run with the JDK 21 source launcher
(java File.java).
Convenience overloads that delegate
The usual reason to overload is a short form for the common case that forwards to the full version.
public class Greeter {
static String greet(String name) {
return greet(name, "Hello", 1);
}
static String greet(String name, String greeting) {
return greet(name, greeting, 1);
}
static String greet(String name, String greeting, int times) {
return (greeting + ", " + name + "! ").repeat(times).trim();
}
public static void main(String[] args) {
System.out.println(greet("Ada"));
System.out.println(greet("Ada", "Hi"));
System.out.println(greet("Ada", "Yo", 3));
}
}
$ java Greeter.java
Hello, Ada!
Hi, Ada!
Yo, Ada! Yo, Ada! Yo, Ada!
Only the three-argument version contains logic; the others fill in defaults. Java has no default parameter values, so this pattern stands in for them.
Return type alone is not enough
The compiler distinguishes overloads by parameter types only.
public class ReturnOnly {
static int parse(String s) { return Integer.parseInt(s); }
static long parse(String s) { return Long.parseLong(s); }
public static void main(String[] args) {
System.out.println(parse("42"));
}
}
$ java ReturnOnly.java
ReturnOnly.java:4: error: method parse(String) is already defined in class ReturnOnly
static long parse(String s) { return Long.parseLong(s); }
^
1 error
error: compilation failed
At the call parse("42") nothing says which return type is wanted, so Java rejects the second
declaration outright rather than the call.
How the compiler picks: widening, boxing, varargs
When no exact match exists, the compiler tries widening first, then boxing, then varargs.
public class Resolution {
static void take(long x) { System.out.println("long (widening)"); }
static void take(Integer x) { System.out.println("Integer (boxing)"); }
static void take(int... xs) { System.out.println("int... (varargs)"); }
static void take(Object x) { System.out.println("Object"); }
public static void main(String[] args) {
int i = 5;
take(i); // int -> long beats int -> Integer
take(Integer.valueOf(5));
take("text");
take(); // only varargs accepts zero arguments
take(1, 2);
}
}
$ java Resolution.java
long (widening)
Integer (boxing)
Object
int... (varargs)
int... (varargs)
Phase one (no boxing, no varargs) finds take(long) for an int, so the boxed version never gets a
chance. Boxing rules were added in Java 5 and placed after widening so that old code kept calling
the same methods.
The remove(int) trap
List has remove(int index) and remove(Object o) — and an int argument always picks the first.
import java.util.ArrayList;
import java.util.List;
public class RemoveTrap {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(10, 20, 1, 30));
nums.remove(1); // index 1, i.e. 20
System.out.println(nums);
nums.remove(Integer.valueOf(1)); // the value 1
System.out.println(nums);
}
}
$ java RemoveTrap.java
[10, 1, 30]
[10, 30]
This is the widening-before-boxing rule again: remove(int) is applicable without boxing, so
remove(Object) is never considered. Box explicitly when you mean the value.
null and ambiguous calls
null fits any reference type. If two overloads are equally specific, the call does not compile.
public class Ambiguous {
static void show(String s) { System.out.println("String"); }
static void show(Integer n) { System.out.println("Integer"); }
static void show(Object o) { System.out.println("Object"); }
public static void main(String[] args) {
show((String) null);
show(null);
}
}
$ java Ambiguous.java
Ambiguous.java:8: error: reference to show is ambiguous
show(null);
^
both method show(String) in Ambiguous and method show(Integer) in Ambiguous match
1 error
error: compilation failed
String and Integer are both more specific than Object, but neither is more specific than the
other, so show(null) is ambiguous. A cast picks one; with only show(String) and show(Object),
the String version would win without one.
Overloads are chosen from the declared type
Because overloading is resolved at compile time, the declared type of the argument decides, not the object’s runtime class.
public class StaticChoice {
static class Animal {}
static class Dog extends Animal {}
static String describe(Animal a) { return "an animal"; }
static String describe(Dog d) { return "a dog"; }
public static void main(String[] args) {
Dog dog = new Dog();
Animal alsoDog = new Dog();
System.out.println(describe(dog));
System.out.println(describe(alsoDog));
System.out.println(alsoDog.getClass().getSimpleName());
}
}
$ java StaticChoice.java
a dog
an animal
Dog
alsoDog really is a Dog, but its declared type is Animal, so the compiler bound the call to
describe(Animal). Overriding is the mechanism that dispatches on the runtime class — see the
polymorphism lesson.
Overloaded constructors with this(…)
Constructors overload the same way and chain to each other with this(...).
public class Rect {
final int width, height;
Rect(int width, int height) {
this.width = width;
this.height = height;
}
Rect(int side) {
this(side, side); // must be the first statement
}
Rect() {
this(1);
}
public static void main(String[] args) {
for (Rect r : new Rect[] {new Rect(3, 4), new Rect(5), new Rect()}) {
System.out.println(r.width + "x" + r.height + " area " + r.width * r.height);
}
}
}
$ java Rect.java
3x4 area 12
5x5 area 25
1x1 area 1
Each shorter constructor delegates, so field assignment happens in exactly one place.
Practice
- Write
maxoverloads for twoints, threeints, and twodoubles, where the three-argument version reuses the two-argument one. Call each.
Solution
public class Max {
static int max(int a, int b) { return a >= b ? a : b; }
static int max(int a, int b, int c) { return max(max(a, b), c); }
static double max(double a, double b) { return a >= b ? a : b; }
public static void main(String[] args) {
System.out.println(max(3, 7));
System.out.println(max(3, 9, 7));
System.out.println(max(2.5, 1.5));
System.out.println(max(2, 1.5)); // int widens to double
}
}
$ java Max.java
7
9
2.5
2.0
max(2, 1.5) has no (int, double) overload, so the int widens and max(double, double) runs.
- Predict, then check: given
f(long)andf(Integer), which runs forf(5)? Addf(int...)and callf()too.
Solution
public class Predict {
static void f(long x) { System.out.println("long"); }
static void f(Integer x) { System.out.println("Integer"); }
static void f(int... x) { System.out.println("varargs " + x.length); }
public static void main(String[] args) {
f(5);
f();
}
}
$ java Predict.java
long
varargs 0
Widening (int to long) is phase one; boxing to Integer is phase two, so it never runs here.
- Fix
Ambiguoussoshow(null)compiles without a cast, by changing only the overloads.
Solution
public class NotAmbiguous {
static void show(String s) { System.out.println("String"); }
static void show(Object o) { System.out.println("Object"); }
public static void main(String[] args) {
show(null);
show(42);
}
}
$ java NotAmbiguous.java
String
Object
With Integer gone, String is strictly more specific than Object, so it wins for null; 42
boxes to Integer and falls through to Object.
- Write
sum(int... nums)plus a dedicatedsum(int a, int b). Show which runs for two arguments and which for three.
Solution
public class Sums {
static int sum(int a, int b) {
System.out.print("fixed: ");
return a + b;
}
static int sum(int... nums) {
System.out.print("varargs: ");
int total = 0;
for (int n : nums) total += n;
return total;
}
public static void main(String[] args) {
System.out.println(sum(1, 2));
System.out.println(sum(1, 2, 3));
}
}
$ java Sums.java
fixed: 3
varargs: 6
Varargs is the last phase, so a fixed-arity overload always wins when it applies — a common way to avoid allocating an array on hot paths.