Method overloading | CodeGuru

Method overloading

Bruce Eckel’s Thinking in Java Contents | Prev | Next One of the important features in any programming language is the use of names. When you create an object, you give a name to a region of storage. A method is a name for an action. By using names to describe your system, you create […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
9 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

One


of the important features in any programming language is the use of names. When


you create an object, you give a name to a region of storage. A method is a


name for an action. By using names to describe your system, you create a


program that is easier for people to understand and change. It’s a lot


like writing prose – the goal is to communicate with your readers.

You


refer to all objects and methods by using names. Well-chosen names make it


easier for you and others to understand your code.

A


problem arises when mapping the concept of nuance in human language onto a


programming language. Often, the same word expresses a number of different


meanings – it’s


overloaded

.


This is useful, especially when it comes to trivial differences. You say


“wash the shirt,” “wash the car,” and “wash the


dog.” It would be silly to be forced to say, “shirtWash the


shirt,” “carWash the car,” and “dogWash the dog”


just so the listener doesn’t need to make any distinction about the


action performed. Most human languages are redundant, so even if you miss a few


words, you can still determine the meaning. We don’t need unique


identifiers – we can deduce meaning from context.

Most


programming languages (C in particular) require you to have a unique identifier


for each function. So you could not have one function called


print( )

for printing integers and another called


print( )

for printing floats – each function requires a unique name.

In


Java, another factor forces the overloading of method names: the constructor

.
Because the constructor’s name is predetermined by the name of the class,
there can be only one constructor name. But what if you want to create an
object in more than one way? For example, suppose you build a class that can
initialize itself in a standard way and by reading information from a file. You
need two constructors, one that takes no arguments (the
default
constructor), and one that takes a
String
as an argument, which is the name of the file from which to initialize the
object. Both are constructors, so they must have the same name – the name
of the class. Thus
method
overloading

is essential to allow the same method name to be used with different argument
types. And although method overloading is a must for constructors, it’s a
general convenience and can be used with any method.

Here’s


an example that shows both overloaded constructors and overloaded ordinary


methods:

//: Overloading.java
// Demonstration of both constructor
// and ordinary method overloading.
import java.util.*;
 
class Tree {
  int height;
  Tree() {
    prt("Planting a seedling");
    height = 0;
  }
  Tree(int i) {
    prt("Creating new Tree that is "
        + i + " feet tall");
    height = i;
  }
  void info() {
    prt("Tree is " + height
        + " feet tall");
  }
  void info(String s) {
    prt(s + ": Tree is "
        + height + " feet tall");
  }
  static void prt(String s) {
    System.out.println(s);
  }
}
 
public class Overloading {
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++) {
      Tree t = new Tree(i);
      t.info();
      t.info("overloaded method");
    }
    // Overloaded constructor:
    new Tree();
  }
} ///:~ 

A


Tree

object can be created either as a seedling, with no argument, or as a plant


grown in a nursery, with an existing height. To support this, there are two


constructors, one that takes no arguments (we call constructors that take no


arguments

default
constructors
[17])
and one that takes the existing height.

You


might also want to call the


info( )

method in more than one way. For example, with a


String

argument if you have an extra message you want printed, and without if you have


nothing more to say. It would seem strange to give two separate names to what


is obviously the same concept. Fortunately, method overloading allows you to


use the same name for both.


Distinguishing
overloaded methods

If


the methods have the same name, how can Java know which method you mean?


There’s a simple rule: Each overloaded method must take a unique list of


argument types.

If


you think about this for a second, it makes sense: how else could a programmer


tell the difference between two methods that have the same name, other than by


the types of their arguments?

Even


differences in the ordering of arguments is sufficient to distinguish two


methods: (Although you don’t normally want to take this approach, as it


produces difficult-to-maintain code.)

//: OverloadingOrder.java
// Overloading based on the order of
// the arguments.
 
public class OverloadingOrder {
  static void print(String s, int i) {
    System.out.println(
      "String: " + s +
      ", int: " + i);
  }
  static void print(int i, String s) {
    System.out.println(
      "int: " + i +
      ", String: " + s);
  }
  public static void main(String[] args) {
    print("String first", 11);
    print(99, "Int first");
  }
} ///:~ 

The


two


print( )

methods have identical arguments, but the order is different, and that’s


what makes them distinct.


Overloading
with primitives

Primitives


can be automatically promoted from a smaller type to a larger one and this can


be slightly confusing in combination with overloading. The following example


demonstrates what happens when a primitive is handed to an overloaded method:

//: PrimitiveOverloading.java
// Promotion of primitives and overloading
 
public class PrimitiveOverloading {
  // boolean can't be automatically converted
  static void prt(String s) {
    System.out.println(s);
  }
 
  void f1(char x) { prt("f1(char)"); }
  void f1(byte x) { prt("f1(byte)"); }
  void f1(short x) { prt("f1(short)"); }
  void f1(int x) { prt("f1(int)"); }
  void f1(long x) { prt("f1(long)"); }
  void f1(float x) { prt("f1(float)"); }
  void f1(double x) { prt("f1(double)"); }
 
  void f2(byte x) { prt("f2(byte)"); }
  void f2(short x) { prt("f2(short)"); }
  void f2(int x) { prt("f2(int)"); }
  void f2(long x) { prt("f2(long)"); }
  void f2(float x) { prt("f2(float)"); }
  void f2(double x) { prt("f2(double)"); }
 
  void f3(short x) { prt("f3(short)"); }
  void f3(int x) { prt("f3(int)"); }
  void f3(long x) { prt("f3(long)"); }
  void f3(float x) { prt("f3(float)"); }
  void f3(double x) { prt("f3(double)"); }
 
  void f4(int x) { prt("f4(int)"); }
  void f4(long x) { prt("f4(long)"); }
  void f4(float x) { prt("f4(float)"); }
  void f4(double x) { prt("f4(double)"); }
 
  void f5(long x) { prt("f5(long)"); }
  void f5(float x) { prt("f5(float)"); }
  void f5(double x) { prt("f5(double)"); }
 
  void f6(float x) { prt("f6(float)"); }
  void f6(double x) { prt("f6(double)"); }
 
  void f7(double x) { prt("f7(double)"); }
 
  void testConstVal() {
    prt("Testing with 5");
    f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);
  }
  void testChar() {
    char x = 'x';
    prt("char argument:");
    f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
  }
  void testByte() {
    byte x = 0;
    prt("byte argument:");
    f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
  }
  void testShort() {
    short x = 0;
    prt("short argument:");
    f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
  }
  void testInt() {
    int x = 0;
    prt("int argument:");
    f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
  }
  void testLong() {
    long x = 0;
    prt("long argument:");
    f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
  }
  void testFloat() {
    float x = 0;
    prt("float argument:");
    f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
  }
  void testDouble() {
    double x = 0;
    prt("double argument:");
    f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
  }
  public static void main(String[] args) {
    PrimitiveOverloading p =
      new PrimitiveOverloading();
    p.testConstVal();
    p.testChar();
    p.testByte();
    p.testShort();
    p.testInt();
    p.testLong();
    p.testFloat();
    p.testDouble();
  }
} ///:~ 

If


you view the output of this program, you’ll see that the constant value 5


is treated as an


int

,


so if an overloaded method is available that takes an


int

it is used. In all other cases, if you have a data type that is smaller than


the argument in the method, that data type is promoted.


char

produces a slightly different effect, since if it doesn’t find an exact


char

match, it is promoted to


int

.

What


happens if your argument is


bigger

than the argument expected by the overloaded method? A modification of the


above program gives the answer:

//: Demotion.java
// Demotion of primitives and overloading
 
public class Demotion {
  static void prt(String s) {
    System.out.println(s);
  }
 
  void f1(char x) { prt("f1(char)"); }
  void f1(byte x) { prt("f1(byte)"); }
  void f1(short x) { prt("f1(short)"); }
  void f1(int x) { prt("f1(int)"); }
  void f1(long x) { prt("f1(long)"); }
  void f1(float x) { prt("f1(float)"); }
  void f1(double x) { prt("f1(double)"); }
 
  void f2(char x) { prt("f2(char)"); }
  void f2(byte x) { prt("f2(byte)"); }
  void f2(short x) { prt("f2(short)"); }
  void f2(int x) { prt("f2(int)"); }
  void f2(long x) { prt("f2(long)"); }
  void f2(float x) { prt("f2(float)"); }
 
  void f3(char x) { prt("f3(char)"); }
  void f3(byte x) { prt("f3(byte)"); }
  void f3(short x) { prt("f3(short)"); }
  void f3(int x) { prt("f3(int)"); }
  void f3(long x) { prt("f3(long)"); }
 
  void f4(char x) { prt("f4(char)"); }
  void f4(byte x) { prt("f4(byte)"); }
  void f4(short x) { prt("f4(short)"); }
  void f4(int x) { prt("f4(int)"); }
 
  void f5(char x) { prt("f5(char)"); }
  void f5(byte x) { prt("f5(byte)"); }
  void f5(short x) { prt("f5(short)"); }
 
  void f6(char x) { prt("f6(char)"); }
  void f6(byte x) { prt("f6(byte)"); }
 
  void f7(char x) { prt("f7(char)"); }
 
  void testDouble() {
    double x = 0;
    prt("double argument:");
    f1(x);f2((float)x);f3((long)x);f4((int)x);
    f5((short)x);f6((byte)x);f7((char)x);
  }
  public static void main(String[] args) {
    Demotion p = new Demotion();
    p.testDouble();
  }
} ///:~ 

Here,


the methods take narrower primitive values. If your argument is wider then you


must

cast
to the necessary type using the type name in parentheses. If you don’t do
this, the compiler will issue an error message.

You


should be aware that this is a

narrowing
conversion,

which means you might lose information during the cast. This is why the
compiler forces you to do it – to flag the narrowing conversion.

Advertisement

Overloading
on return values

It


is common to wonder “Why only class names and method argument lists? Why


not distinguish between methods based on their return values?” For


example, these two methods, which have the same name and arguments, are easily


distinguished from each other:

void f() {}
int f() {}

This


works fine when the compiler can unequivocally determine the meaning from the


context, as in


int
x = f( )

.


However, you can call a method and ignore the return value; this is often


referred to as


calling
a method for its side
effect

since you don’t care about the return value but instead want the other


effects of the method call. So if you call the method this way:

f();

how


can Java determine which


f( )

should be called? And how could someone reading the code see it? Because of


this sort of problem, you cannot use return value types to distinguish


overloaded methods.


Default
constructors

As


mentioned previously, a default constructor

is
one without arguments, used to create a “vanilla object.” If you
create a class that has no constructors, the compiler will automatically create
a default constructor for you. For example:
//: DefaultConstructor.java
 
class Bird {
  int i;
}
 
public class DefaultConstructor {
  public static void main(String[] args) {
    Bird nc = new Bird(); // default!
  }
} ///:~ 

The


line

new
Bird();

creates


a new object and calls the default constructor, even though one was not


explicitly defined. Without it we would have no method to call to build our


object. However, if you define any constructors (with or without arguments),


the compiler will


not

synthesize one for you:

class Bush {
  Bush(int i) {}
  Bush(double d) {}
}

Now


if you say:

new
Bush();

the


compiler will complain that it cannot find a constructor that matches.


It’s as if when you don’t put in any constructors, the compiler


says “You are bound to need


some

constructor, so let me make one for you.” But if you write a constructor,


the compiler says “You’ve written a constructor so you know what


you’re doing; if you didn’t put in a default it’s because you


meant to leave it out.”


Advertisement

The
this
keyword

If


you have two objects of the same type called


a

and


b

,


you might wonder how it is that you can call a method


f( )

for both those objects:

class Banana { void f(int i) { /* ... */ } }
Banana a = new Banana(), b = new Banana();
a.f(1);
b.f(2);

If


there’s only one method called


f( )

,


how can that method know whether it’s being called for the object


a

or


b

?

To


allow you to write the code in a convenient object-oriented syntax in which you


“send a message to an object,” the compiler does some undercover


work for you. There’s a secret first argument passed to the method


f( )

,


and that argument is the handle to the object that’s being manipulated.


So the two method calls above become something like:

Banana.f(a,1);
Banana.f(b,2);

This


is internal and you can’t write these expressions and get the compiler to


accept them, but it gives you an idea of what’s happening.

Suppose


you’re inside a method and you’d like to get the handle to the


current object. Since that handle is passed


secretly

by the compiler, there’s no identifier for it. However, for this purpose


there’s a keyword:


this

.


The


this

keyword – which can be used only inside a method – produces the


handle to the object the method has been called for. You can treat this handle


just like any other object handle. Keep in mind that if you’re calling a


method of your class from within another method of your class, you don’t


need to use


this;

you simply call the method. The current


this

handle is automatically used for the other method. Thus you can say:

class Apricot {
  void pick() { /* ... */ }
  void pit() { pick(); /* ... */ }
}

Inside


pit( )

,


you


could

say


this.pick( )

but there’s no need to. The compiler does it for you automatically. The


this

keyword is used only for those special cases in which you need to explicitly


use the handle to the current object. For example, it’s often used in


return

statements when you want to return the handle to the current object:

//: Leaf.java
// Simple use of the "this" keyword
 
public class Leaf {
  private int i = 0;
  Leaf increment() {
    i++;
    return this;
  }
  void print() {
    System.out.println("i = " + i);
  }
  public static void main(String[] args) {
    Leaf x = new Leaf();
    x.increment().increment().increment().print();
  }
} ///:~ 

Because


increment( )

returns the handle to the current object via the


this

keyword, multiple operations can easily be performed on the same object.


Calling
constructors from constructors

When


you write several constructors for a class, there are times when you’d


like to call one constructor from another to avoid duplicating code. You can do


this using the


this

keyword.

Normally,


when you say


this

,


it is in the sense of “this object” or “the current


object,” and by itself it produces the handle to the current object. In a


constructor, the


this

keyword takes on a different meaning when you give it an argument list: it


makes an explicit call to the constructor that matches that argument list. Thus


you have a straightforward way to call other constructors:

//: Flower.java
// Calling constructors with "this"
 
public class Flower {
  private int petalCount = 0;
  private String s = new String("null");
  Flower(int petals) {
    petalCount = petals;
    System.out.println(
      "Constructor w/ int arg only, petalCount= "
      + petalCount);
  }
  Flower(String ss) {
    System.out.println(
      "Constructor w/ String arg only, s=" + ss);
    s = ss;
  }
  Flower(String s, int petals) {
    this(petals);
//!    this(s); // Can't call two!
    this.s = s; // Another use of "this"
    System.out.println("String & int args");
  }
  Flower() {
    this("hi", 47);
    System.out.println(
      "default constructor (no args)");
  }
  void print() {
//!    this(11); // Not inside non-constructor!
    System.out.println(
      "petalCount = " + petalCount + " s = "+ s);
  }
  public static void main(String[] args) {
    Flower x = new Flower();
    x.print();
  }
} ///:~ 

The


constructor


Flower(String
s, int petals)

shows that, while you can call one constructor using


this

,


you cannot call two. In addition, the constructor call must be the first thing


you do or you’ll get a compiler error message.

This


example also shows another way you’ll see


this

used. Since the name of the argument


s

and


the name of the member data


s

are the same, there’s an ambiguity. You can resolve it by saying


this.s

to refer to the member data. You’ll often see this form used in Java


code, and it’s used in numerous places in this book.

In


print( )

you can see that the compiler won’t let you call a constructor from


inside any method other than a constructor.


The
meaning of static

With


the


this

keyword


in mind, you can more fully understand what it means to make a

method
static.
It means that there is no
this
for that particular method. You cannot call non-
static
methods from inside
static
methods
[18]
(although the reverse is possible), and you can call a
static
method for the class itself, without any object. In fact, that’s
primarily what a
static
method is for. It’s as if you’re creating the equivalent of a
global function (from C). Except global functions are not permitted in Java,
and putting the
static
method inside a class allows it access to other
static
methods
and to
static
fields.

Some


people argue that


static

methods are not object-oriented since they do have the semantics of a global


function; with a


static

method you don’t send a message to an object, since there’s no


this

.


This is probably a fair argument, and if you find yourself using a


lot

of static methods you should probably rethink your strategy. However,


static

s


are pragmatic and there are times when you genuinely need them, so whether or


not they are “proper OOP” should be left to the theoreticians.


Indeed, even

Smalltalk
has the equivalent in its “class methods.”

[17]

In some of the Java literature from Sun they instead refer to these with the


clumsy but descriptive name “no-arg constructors.” The term


“default constructor” has been in use for many years and so I will


use that.

[18]

The one case in which this is possible occurs if you pass a handle to an object


into the


static

method. Then, via the handle (which is now effectively


this

),


you can call non-


static

methods and access non-


static

fields. But typically if you want to do something like this you’ll just


make an ordinary, non-


static

method.

Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.