Skip to content

Unit 13: Overloading

Learning Objectives

After reading this unit, students should be able to:

  • explain what method overloading is and how it differs from method overriding
  • identify when two methods are considered overloaded in Java
  • correctly define overloaded methods and constructors by varying parameter types, order, or arity
  • explain why changing only parameter names or return types does not result in overloading
  • reason about which overloaded method is selected at compile time based on argument types

Overview

In the previous unit, we studied method overriding, which allows subclasses to replace the behavior of inherited methods at runtime. Overriding supports polymorphism, where the same method call can behave differently depending on the object’s runtime type.

In this unit, we shift focus to method overloading, which addresses a different concern: convenience and expressiveness. Overloading allows a class to provide multiple methods with the same name that operate on different kinds of inputs, while still performing conceptually similar tasks.

Unlike overriding, overloading is resolved entirely at compile time. Understanding this distinction is essential, as it explains both what Java allows you to overload, and why some seemingly reasonable overloads are rejected by the compiler.

Method overloading

In the previous unit, we introduced method overriding — when a subclass defines an instance method with the same method descriptor as an instance method in the parent class.

In contrast, method overloading occurs when a class has access to (either defined or inherited) two or more methods with the same name but a different method signatures. In other words, we create an overloaded method by changing the type, order, or number of parameters of the method while keeping the method name identical.

Let's consider an add method which allows us to add two numbers, and returns the result. What if we would like to create an add method to sum up three numbers?

1
2
3
4
5
6
7
public int add(int x, int y) {
  return x + y;
}

public int add(int x, int y, int z) {
  return x + y + z;
}

In the example above, the methods add(int, int) and add(int, int, int) are overloaded. They have the same name but a different number of parameters. We can see that this allows us to write methods to handle differing inputs.

Now let's consider our Circle class again. Our Circle::contains(Point) method allows us to check if a Point is within the radius of the current instance of the Circle. We would like to create a new method Circle::contains(double, double) which will allow us to check if an (x, y) coordinate (another valid representation of a point) is within our circle.

Circle v0.6a with Overloaded `contains` Method
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Circle {
  private Point c;
  private double r;

  public Circle(Point c, double r) {
    this.c = c;
    this.r = r;
  }

  public double getArea() {
    return Math.PI * this.r * this.r;
  }

  public boolean contains(Point p) {
    return false;
    // TODO: Left as an exercise
  }

  public boolean contains(double x, double y) {
    return false;
    // TODO: Left as an exercise
  }

  @Override
  public String toString() {
    return "{ center: " + this.c + ", radius: " + this.r + " }";
  }
}

In the above example, Circle::contains(Point) and Circle::contains(double, double) are overloaded methods.

Recall that overloading requires changing the order, number, and/or type of parameters and says nothing about the names of the parameters. Consider the example below, where we have two contains methods in which we swap parameter names.

1
2
3
4
5
6
7
8
9
  public boolean contains(double x, double y) {
    return false;
    // TODO: Left as an exercise
  }

  public boolean contains(double y, double x) {
    return false;
    // TODO: Left as an exercise
  }

Because parameter names are not part of the method signature, swapping parameter names does not produce a new method. These two methods have the same method signature, and therefore contains(double, double) and contains(double, double) are not distinct methods. The Java compiler will reject this code with an error indicating that the method is already defined.

Note that the return type is part of the method descriptor, but not part of the method signature, so we cannot have two methods with the signature but different return types. For example, the following code will also be rejected by the Java compiler.

1
2
3
4
5
6
7
  public int add(int x, int y) {
    return x + y;
  }

  public double add(int x, int y) {
    return (double)(x + y);
  }

On the other hand, we can have two overloaded methods with different return types:

1
2
3
4
5
6
7
  public int add(int x, int y) {
    return x + y;
  }

  public double add(double x, double y) {
    return x + y;
  }

Overloading Constructor

As a constructor is also a method, it is possible to overload the class constructor as well. As in the example below, we can see an overloaded constructor which gives us a handy way to instantiate a Circle object that is the unit circle.

Circle v0.6b with Overloading Constructor
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Circle {
  private Point c;
  private double r;

  public Circle(Point c, double r) {
    this.c = c;
    this.r = r;
  }

  // Overloaded constructor
  public Circle() {
    this.c = new Point(0, 0);
    this.r = 1;
  }

  // Other methods omitted 
}
1
2
3
4
// c1 points to a new Circle object with a center (1, 1) and a radius of 2
Circle c1 = new Circle(new Point(1, 1), 2);
// c2 points to a new Circle object with a center (0, 0) and a radius of 1
Circle c2 = new Circle();

It is also possible to overload static class methods in the same way as instance methods. In the next unit, we will see how Java chooses which method implementation to execute when a method is invoked.

Similar to super, the this keyword can be used to invoke another constructor. While super is used to invoke the constructor in the superclass, this invokes an overloaded constructor in the same class. This is particularly useful as it allows us to avoid duplicating code. For example, we can modify our overloaded constructor in the Circle class to invoke the primary constructor instead of directly initializing the instance variables.

Circle v0.6c with Overloaded Constructor using this()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Circle {
  private Point c;
  private double r;

  public Circle(Point c, double r) {
    this.c = c;
    this.r = r;
  }

  // Overloaded constructor
  public Circle() {
    this(new Point(0, 0), 1);  // call the primary constructor
  }

  // Other methods omitted 
}