Java Advanced Placement Study Guide: Method Overloading

Questions

Welcome

This is one in a miniseries of tutorial lessons designed to help you learn
the essential features of Java object-oriented programming as identified
by The College Board.

Purpose

The purpose of this miniseries is to help you study for the Advanced
Placement Examinations designed by the College Board.

Once you understand everything in this miniseries, plus the material
in the lessons that I published earlier on Java
Data Structures, you should understand the Java programming features
that the College Board considers essential for the first two semesters
of object-oriented programming education at the university level.

Approach

These lessons provide questions, answers, and explanations designed
to help you to understand the subset
of Java features covered by the Java Advanced Placement Examinations (as
of October, 2001).

In addition to the material in these lessons, I recommend that you also
study the other lessons in my extensive collection of online Java tutorials,
which are designed from a more conventional textbook approach. You
will find those lessons published at
Gamelan.com.
However, as of the date of this writing, Gamelan doesn't maintain a consolidated
index of my Java tutorial lessons, and sometimes they are difficult to
locate there. You will find a consolidated index at
Baldwin's
Java Programming Tutorials.

What is Included?

Click here for a preview of the Java
programming features covered by this lesson.

Copyright 2002, Richard G. Baldwin. Reproduction in whole or
in part in any form or medium without express written permission from Richard
Baldwin is prohibited.

About the author

Richard Baldwin
is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java and XML.
In addition to the many platform-independent benefits of Java applications,
he believes that a combination of Java and XML will become the primary
driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects involving
Java, XML, or a combination of the two. He frequently provides onsite
Java and/or XML training at the high-tech companies located in and around
Austin, Texas. He is the author of Baldwin's Java Programming Tutorials,
which has gained a worldwide following among experienced and aspiring Java
programmers. He has also published articles on Java Programming in Java
Pro magazine.

Richard holds an MSEE degree from Southern Methodist University and
has many years of experience in the application of computer technology
to real-world problems.

What is Included?

"Method overloading (e.g. MyClass.foo(String s) and
MyClass.foo(int
n)) is part of the AP CS subset. However, there will be no "trick
questions" that test an understanding of the subtleties of the overloading
resolution mechanism."

(I believe that the comment about trick questions has been removed from
a revised version the subset document.)

Answers and Explanations

Answer 10

Explanation 10

Oops, just when we thought that we were in the
clear, we're back into the subtleties of the overloading resolution
mechanism again.

Swapped the method locations

This program is the same as the program from Question 9, except that
I swapped the positions of the two overloaded versions of the method named
overLoadMthd()
between the subclass and the superclass.

The version of the overloaded method that requires an incoming parameter
of type SubC is now defined in the class named SuperC, and
the version that requires an incoming parameter of type SuperC is
defined in the class named
SubC.

An ambiguous situation

This program produces the following compiler error:

Ap088.java:20: reference to overLoadMthd is ambiguous, both method
overLoadMthd(SubC) in SuperC and method overLoadMthd(SuperC) in SubC match obj.overLoadMthd(a);

While this is a subtle problem, it should not come as a surprise to
you. This is essentially the same problem that was exposed in Question
4. The only difference is that the program in Question 4 dealt with
method parameters of primitive types, and this program deals with method
parameters of class types.

The basic problem

The basic problem is the same. Placing an assignment compatible
version of an overloaded method further down the inheritance hierarchy
than a matching version causes the matching version to be blocked when
looking up from below.

Could be resolved using method overriding

As was the case in Question 4, this problem could be resolved by providing
an overridden version of the method in the lower class and using the super
keyword to invoke the version in the higher class.

Answer 9

Explanation 9

While admittedly a little convoluted, this is another
relatively straightforward application of method overloading using types
from the class hierarchy.

Type SubC, SuperC, or Object

This method defines a class named SuperC,
which extends Object, and a class named SubC, which extends
SuperC.
Therefore, an object instantiated from the class named
SubC can
be treated as any of the following types: SubC,
SuperC,
or Object.

Two overloaded methods in different classes

Two overloaded methods named overLoadMthd()
are defined in two classes in the inheritance hierarchy. The class
named SuperC defines a version that requires an incoming parameter
of type SuperC. The class named SubC defines a version
that requires an incoming parameter of type SubC. When invoked,
each of these overloaded methods prints the type of its formal argument.

Two objects of type SubC

The program instantiates two objects of the SubC
class, storing the reference to one of them in a reference variable of
type SubC, and storing the reference to the other in a reference
variable of type SuperC.

Invoke the overloaded method twice

The next step is to invoke the overloaded method
named overLoadMthd() twice in succession, passing each of the reference
variables of type SubC and SuperC to the method.

Instance methods require an object

Because the two versions of the overloaded method
are instance methods, it is necessary to have an object on which to invoke
the methods. This is accomplished by instantiating a new object of
the SubC class, storing the reference to that object in a reference
variable named obj, and invoking the overloaded method on that reference.

Overloaded methods not in same class

The important point here is that the two versions
of the overloaded method were not defined in the same class. Rather,
they were defined in two different classes in the inheritance hierarchy.
However, they were defined in such a way that both overloaded versions
were contained as instance methods in an object instantiated from the class
named SubC.

No surprises

There were no surprises. When the overloaded
method was invoked twice in succession, passing the two different reference
variables as parameters, the output shows that the version that was invoked
in each case had a formal argument type that matched the type of the parameter
that was passed to the method.

Answer 8

Explanation 8

This is another straightforward application of
method overloading, which produces no surprises.

This program defines a new class named Test,
which extends the Object class. This means that an object
instantiated from the class named Test can be treated either as
type Test, or as type Object.

The program defines two overloaded methods named
overLoadMthd().
One requires an incoming parameter of type
Test. The other
requires an incoming parameter of type Object. When invoked,
each of these methods prints the type of its incoming parameter.

The program instantiates two different objects
of the class Test, storing a reference to one of them in a reference
variable of typeTest, and storing a reference to the other
in a reference variable of type Object.

No surprises here

Then it invokes the overloaded overLoadMthd()
method twice in succession, passing the reference of type Test during
the first invocation, and passing the reference of type Object during
the second invocation.

As mentioned above, the output produces no surprises.
The output indicates that the method selected for execution during each
invocation is the method with the formal argument type that matches the
type of parameter passed to the method.

Answer 7

Explanation 7

Overloaded methods with reference parameters

This is a fairly straightforward application of method overloading.
However, rather than requiring method parameters of primitive types as
in the previous questions in this lesson, the overloaded methods in this
program require incoming parameters of class and interface
types respectively.

Type Test or type DumIntfc

The program defines an interface named DumIntfc and defines a
class named Test that implements that interface. The result
is that an object instantiated from the Test class can be treated
either as type Test or as type DumIntfc(it could also
be treated as type Object as well).

Two overloaded methods

The program defines two overloaded methods named overLoadMthd().
One requires an incoming parameter of type Test, and the other requires
an incoming parameter of type DumIntfc. When invoked, each
of the overloaded methods prints a message indicating the type of its argument.

Two objects of the class Test

The program instantiates two objects of the class Test.
It assigns a reference to one of those objects to a reference variable
named a, which is declared to be of type Test.

The program assigns a reference to the other object of the class Test
to a reference variable named b, which is declared to be of type
DumIntfc.

No surprises here

Then it invokes the overloaded method named overLoadMthd() twice
in succession, passing first the reference variable of type Test
and then the reference variable of type DumIntfc.

The program output doesn't produce any surprises. When the reference
variable of type Test is passed as a parameter, the overloaded method
requiring that type of parameter is selected for execution. When
the reference variable of type DumIntfc is passed as a parameter,
the overloaded method requiring that type of parameter is selected for
execution.

Answer 6

Explanation 6

This program illustrates a subtle issue in the
automatic selection of an overloaded method based on assignment compatibility.

This program defines two overloaded methods named
square().
One requires an incoming parameter of type float, and the other
requires an incoming parameter of type double.

When invoked, each of these methods prints the
type of its formal argument along with the value of the incoming parameter
as represented by its formal argument type. In other words, the value
of the incoming parameter is printed after it has been automatically converted
to the formal argument type.

Printout identifies the selected method

This printout makes it possible to determine which
version is invoked for different types of parameters. It also makes
it possible to determine the effect of the automatic conversion on the
incoming parameter. What we are going to see is that the conversion
process can introduce serious accuracy problems.

Invoke the method three times

The square() method is invoked three times
in succession, passing values of type int, long, and double
during
successive invocations.

(Note, type long is apparently not
included on the AP CS exam. Suffice it to say that it is a 64-bit
integer type capable of storing integer values that are much larger than
can be stored in type int. The use of this type here is important
for illustration of data corruption that occurs through automatic type
conversion.)

The third invocation of the square() method,
passing a double as a parameter, is not particularly interesting.
There is a version of square() with a matching argument type, and
everything behaves as would be expected for this invocation. The
interesting behavior occurs when the int and long values
are passed as parameters.

Passing an int parameter

The first thing to note is the behavior of the
program produced by the following code fragment.

int x = 2147483647;
square(x);

The above fragment assigns a large integer value
(2147483647) to the int variable x and passes that
variable to the
square() method. This fragment produces the
following output on the screen:

float 2.14748365E9

As you can see, the system selected the overloaded
method that requires an incoming parameter of type float for execution
in this case (rather than the version that requires type double).

Conversion from int to float loses accuracy

Correspondingly, it converted the incoming int
value to type float, losing one decimal digit of accuracy in the
process. (The original int value contained ten digits of accuracy.
This was approximated by a nine-digit float value with an exponent value
of 9.)

This seems like an unfortunate choice of overloaded
method. Selecting the other version that requires a double
parameter as input would not have resulted in any loss of accuracy.

A more dramatic case

Now, consider an even more dramatic case, as illustrated
in the following fragment where a very large long integer value
(9223372036854775807) is passed
to the square() method.

long y = 9223372036854775807L;
square(y);

The above code fragment produced the following
output:

float 9.223372E18

A very serious loss of accuracy

Again, unfortunately, the system selected the
version of the square() method that requires a float parameter
for execution. This caused the long integer to be converted
to a float. As a result, the long value containing
19 digits of accuracy was converted to an estimate consisting of only seven
digits plus an exponent.

(Even if the overloaded square method
requiring a double parameter had been selected, the conversion process
would have lost about three digits of accuracy, but that would have been
much better than losing twelve digits of accuracy.)

The moral to the story is ...

Don't assume that just because the system knows
how to automatically convert your integer data to floating data, it will
protect the integrity of your data. Oftentimes it won't.

To be really save ...

To be really safe, whenever you need to convert
either int or long types to floating format, you should write
your code in such a way as to ensure that it will be converted to type
double
instead of type float.

For example, the following modification would
solve the problem for the int data and would greatly reduce the
magnitude of the problem for the long data. Note the use of
the (double) cast to force that version of the square() method
to be selected for execution.

The above modification would cause the program
to produce the following output:

double 2.147483647E9double 9.223372036854776E18double 4.2

This output shows no loss of accuracy for the int value, and
the loss of three digits of accuracy for the long value.

(Because a long and a double both store their data in 64 bits, it
is not possible to convert a very large long value to a double value without
some loss in accuracy, but even that is much better than converting a 64-bit
long value to a 32-bit floatvalue.)

Answer 5

Explanation 5

Swap method locations

This program is just like the program with the ambiguity problem from
Question 4 with one major difference. I swapped the locations of
the overloaded versions of the method named square() in the inheritance
hierarchy.

In this case, the overloaded version of the method square() that
requires the wider parameter type, double, is defined further
up the inheritance hierarchy than the version that requires the narrower
parameter type, int. As a result, the program compiles and
executes properly.

A subtle difference

When the square() method is invoked on an object of the Subclass
type passing an int as a parameter, there is an exact match to the
required parameter type of the square() method defined in that class.
Thus, there is no ambiguity.

When the square() method is invoked on an object of the Subclass
type passing a double as a parameter, the version of the square()
method defined in the Subclass type is not selected. The double
value is not assignment compatible with the required type of the
parameter (an int is narrower than a double). Thus, there
is no ambiguity.

Having made that determination, the system continues searching for an
overloaded method with a required parameter that is either type double
or assignment compatible with double. It finds the
inherited version that requires a double parameter and invokes it.

Answer 4

Explanation 4

The problem that prevented this program from compiling
is very subtle.

This program defines a class hierarchy consisting
of:

A class named Superclass that extends Object

A class named Subclass that extends Superclass

Two overloaded versions of a method named square()
are defined. One of the overloaded versions, which requires a single
incoming parameter of type int, is defined in the class named Superclass.
The other overloaded version, which requires a single incoming parameter
of type double, is defined in the class named Subclass.

A valid arrangement for overloaded methods

Overloaded methods need not all be defined in
the same class. They can be defined up and down the inheritance hierarchy.
The requirement is simply that two or more versions of the overloaded method
belong to, or be inherited into, an object instantiated from a class at
some level in the inheritance hierarchy.

In this case, an object instantiated from the
class named Subclass will contain two overloaded instance methods
named
square(). One is defined in the Subclass class
from which the object is instantiated. The other is inherited from
the class named Superclass.

Two anonymous objects

The program instantiates two anonymous objects
of the class named Subclass. It invokes the square()
method on one of those objects, passing a double value as a parameter.
There is no problem with this method invocation. The method parameter
is a perfect match for the overloaded method named square() that
is defined in the class named Subclass.

An ambiguous situation

The program also invokes the square() method
on the other object, passing a parameter of type int. This
is where a problem arises. The incoming parameter is directly compatible
with the formal argument of the square() method inherited from Superclass.
It is also assignment compatible with the formal parameter of the
square()
method defined in the class named Subclass. The compiler must
decide which method to invoke. It refuses to make a decision, producing
the following compiler error:

Ap082.java:18: reference to square is ambiguous,
both method square(int) in Superclass and method square(double) in Subclass
match new Subclass().square(w)
+ " "

If the compiler cannot select ...

Apparently this is one of those cases described
by Mark Grand as follows, "If the compiler cannot select
one of the methods as a better match than the others, the method selection
process fails and the compiler issues an error message."

Could be a serious problem

This could be a serious problem in the design
of a program. Basically, the existence of the square() method
requiring a parameter of type double in Subclass blocks access
to the overloaded square() method in Superclass.

Overriding to the rescue

Although method overriding is not the topic
of this lesson, the author of the class named Subclass could solve
this problem by overriding the inherited square() method for a parameter
of type int, using the super keyword to gain access to the
blocked square() method. Such a solution is shown below.
At this point, you can begin to see that overloading and overriding methods
are somewhat related strategies.

Answer 3

Explanation 3

This is not a subtle issue. This program
illustrates the important fact that the return type does not differentiate
between overloaded methods having the same name and formal argument list.

For a method to be overloaded, two or more versions
of the method must have the same name and different formal arguments lists.

The return type can be the same, or it can be
different (it can even be void). It doesn't matter.

These two methods are not a valid overload

This program attempts to define two methods named
square(),
each of which requires a single incoming parameter of type double.
One of the methods casts its return value to type int and returns
type int. The other method returns type double.

Answer 2

Explanation 2

This program is a little more subtle

Once again, the program defines two overloaded methods named square.
However, in this case, one of the methods requires a single incoming parameter
of type
float and the other requires a single incoming parameter
of type double.

(The AP CS exam apparently doesn't cover the float type.
Suffice it to say that the float type is similar to the double type, but
with less precision. It is a floating type, not an integer type.
The double type is a 64-bit floating type and the float type is a 32-bit
floating type.)

Passing a type int as a parameter

This program does not define a method named square() that requires
an incoming parameter of type int. However, the program invokes
the square() method passing a value of type int as a parameter.

What happens to the int parameter?

The first question to ask is, will this cause one of the two overloaded
methods to be invoked, or will it cause a compiler error? The answer
is that it will cause one of the overloaded methods to be invoked because
a value of type int is assignment compatible with type
float
and type double.

Which overloaded method will be invoked?

Since the type int is assignment compatible with type
float
and also with type double, the next question is, which of the two
overloaded methods will be invoked when a value of type int
is passed
as a parameter?

Learn through experimentation

I placed a print statement in each of the overloaded methods to display
the type of that method's argument on the screen when the method is invoked.
By examining the output, we can see that the method with the float
parameter was invoked first (corresponding to the parameter of type
int).
Then the method with the double parameter was invoked
(corresponding
to the parameter of type double).

Converted int to float

Thus, the system selected the overloaded method requiring an incoming
parameter of type float when the method was called passing an int
as a parameter. The value of type int was automatically converted
to type float.

In this case, it wasn't too important which method was invoked to process
the parameter of type int, because the two methods do essentially
the same thing -- compute and return the square of the incoming value.

However, if the behavior of the two methods were different from one
another, it could make a lot of difference, which one gets invoked on an
assignment
compatible basis.

(Even in this case, it makes some difference. As we
will see later, when a very large int value is converted to a float, there
is some loss in accuracy. However, when the same very large int value
is converted to a double, there is no loss in accuracy.)

Avoiding the problem

One way to avoid this kind of subtle issue is to avoid passing assignment-compatible
values to overloaded methods.

Passing assignment-compatible values to overloaded methods allows
the system to resolve the issue through automatic type conversion.
Automatic type conversion doesn't always provide the best choice.

Using a cast to force your choice of method

Usually, you can cast the parameter values to a specific type before
invoking the method and force the system to select your overloaded method
of choice.

For example, in this problem, you could force the method with the double
parameter to handle the parameter of type int by using the following
cast when the method named square() is invoked:

square((double)x)

However, as we will see later, casting may not be the solution in every
case.

Answer 1

Explanation 1

What is method overloading?

A rigorous definition of method overloading is very involved and won't
be presented here. However, from a practical viewpoint, a method
is overloaded when two or more methods having the same name and different
formal argument lists are defined in the class from which an object is
instantiated, or are inherited into an object by way of superclasses of
that class.

How does the compiler select among overloaded
methods?

The exact manner in which the system determines which method to invoke
in each particular case is also very involved. Basically, the system
determines which of the overloaded methods to execute by matching the types
of parameters passed to the method to the types of arguments defined
in the formal argument list.

Assignment compatible matching

However, there are a number of subtle issues that arise, particularly
when there isn't an exact match. In selecting the version of the
method to invoke, Java supports the concept of an "assignment compatible"
match (or possibly more than one assignment compatible match).

Briefly, assignment compatibility means that it would be allowable to
assign a value of the type that is passed as a parameter to a variable
whose type matches the specified argument in the formal argument list.

Selecting the best match

According to Java Language Reference by Mark Grand, "If more
than one method is compatible with the given arguments, the method that
most closely matches the given parameters is selected. If the compiler
cannot select one of the methods as a better match than the others, the
method selection process fails and the compiler issues an error message."

No trick questions

Fortunately, the AP CS subset
document states, "there will be no trick questions that test an
understanding of the subtleties of the overloading resolution mechanism."

Understanding subtleties

On the other hand, if you plan to be a Java programmer, you must have
some understanding of the subtle issues involving overloaded methods, and
the relationship between overloaded methods and overridden methods.
Therefore, the programs in this lesson will provide some of that information
and discuss some of the subtle issues that arise.

Even if you don't care about the subtle issues regarding method overloading,
many of those issues really involve subtle issues regarding automatic type
conversion. You should study these questions to learn about the problems
associated with automatic type conversion.

This program is straightforward

However, there isn't anything subtle about the program for Question
1. This program defines two overloaded methods named square().
One requires a single incoming parameter of type int. The
other requires a single incoming parameter of type double.
Each method calculates and returns the square of the incoming parameter.

The program invokes a method named square twice in succession,
and displays the values returned by those two invocations. In the
first case, an int value is passed as a parameter. This causes
the method with the formal argument list of type int to be invoked.

In the second case, a double value is passed as a parameter.
This causes the method with the formal argument list of type double
to be invoked.

Overloaded methods may have different return
types

Note in particular that the overloaded methods have different return
types. One method returns its value as type int and the other
returns its value as type double. This is reflected in the
output format for the two return vales as shown below:

9 17.64

Copyright 2002, Richard G. Baldwin. Reproduction in whole or
in part in any form or medium without express written permission from Richard
Baldwin is prohibited.

About the author

Richard Baldwin
is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java and XML.
In addition to the many platform-independent benefits of Java applications,
he believes that a combination of Java and XML will become the primary
driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects involving
Java, XML, or a combination of the two. He frequently provides onsite
Java and/or XML training at the high-tech companies located in and around
Austin, Texas. He is the author of Baldwin's Java Programming Tutorials,
which has gained a worldwide following among experienced and aspiring Java
programmers. He has also published articles on Java Programming in Java
Pro magazine.

Richard holds an MSEE degree from Southern Methodist University and
has many years of experience in the application of computer technology
to real-world problems.