IMPORTANT NOTE: This lesson as originally published indicates that the programs in Problems 4 and 10 produce compiler errors having to do with ambiguity. That was true for the released version of the Sun compiler when the lesson was published in March of 2002. At some point since then, Sun has improved the compiler to eliminate such ambiguity. The compiler for V1.4.2 no longer produces a compiler error for those two programs. The programs now compile and run successfully.
Revised: August 3, 2004
Published: March 6, 2002
By Richard G. Baldwin
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).
Please see the first lesson in the miniseries entitled Java Advanced Placement Study Guide: Introduction to the Lessons, Primitive Types, for additional background information. The previous lesson was entitled Java Advanced Placement Study Guide: More on Arrays.
Supplementary material
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.
public class Ap079{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ int x = 3; double y = 4.2; System.out.println(square(x) + " " + square(y)); }//end doOverLoad() public int square(int y){ return y*y; }//end square() public double square(double y){ return y*y; }//end square() }// end class |
2. What output is produced by the following program?
public class Ap080{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ int x = 3; double y = 4.2; System.out.print(square(x) + " "); System.out.print(square(y)); System.out.println(); }//end doOverLoad() public float square(float y){ System.out.print("float "); return y*y; }//end square() public double square(double y){ System.out.print("double "); return y*y; }//end square() }// end class |
3. What output is produced by the following program?
public class Ap081{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ double w = 3.2; double x = 4.2; int y = square(w); double z = square(x); System.out.println(y + " " + z); }//end doOverLoad() public int square(double y){ return (int)(y*y); }//end square() public double square(double y){ return y*y; }//end square() }// end class |
4. What output is produced by the following program?
public class Ap082{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ int w = 3; double x = 4.2; System.out.println( new Subclass().square(w) + " " + new Subclass().square(x)); }//end doOverLoad() }// end class class Superclass{ public int square(int y){ return y*y; }//end square() }//end class Superclass class Subclass extends Superclass{ public double square(double y){ return y*y; }//end square() }//end class Subclass |
5. What output is produced by the following program?
public class Ap083{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ int w = 3; double x = 4.2; System.out.println( new Subclass().square(w) + " " + new Subclass().square(x)); }//end doOverLoad() }// end class class Superclass{ public double square(double y){ return y*y; }//end square() }//end class Superclass class Subclass extends Superclass{ public int square(int y){ return y*y; }//end square() }//end class Subclass |
6. What output is produced by the following program?
public class Ap084{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ int x = 2147483647; square(x); long y = 9223372036854775807L; square(y); double z = 4.2; square(z); System.out.println(); }//end doOverLoad() public void square(float y){ System.out.println("float" + " " + y + " "); }//end square() public void square(double y){ System.out.println("double" + " " + y + " "); }//end square() }// end class |
7. What output is produced by the following program?
public class Ap085{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ Test a = new Test(); DumIntfc b = new Test(); overLoadMthd(a); overLoadMthd(b); System.out.println(); }//end doOverLoad() public void overLoadMthd(Test x){ System.out.print("Test "); }//end overLoadMthd public void overLoadMthd(DumIntfc x){ System.out.print("DumIntfc "); }//end overLoadMthd }// end class interface DumIntfc{ }//end DumIntfc class Test implements DumIntfc{ }//end class Test |
8. What output is produced by the following program?
public class Ap086{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ Test a = new Test(); Object b = new Test(); overLoadMthd(a); overLoadMthd(b); System.out.println(); }//end doOverLoad() public void overLoadMthd(Test x){ System.out.print("Test "); }//end overLoadMthd public void overLoadMthd(Object x){ System.out.print("Object "); }//end overLoadMthd }// end class class Test{ }//end class Test |
9. What output is produced by the following program?
public class Ap087{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ SubC a = new SubC(); SuperC b = new SubC(); SubC obj = new SubC(); obj.overLoadMthd(a); obj.overLoadMthd(b); System.out.println(); }//end doOverLoad() }// end class class SuperC{ public void overLoadMthd(SuperC x){ System.out.print("SuperC "); }//end overLoadMthd }//end SuperC class SubC extends SuperC{ public void overLoadMthd(SubC x){ System.out.print("SubC "); }//end overLoadMthd }//end class SubC |
10. What output is produced by the following program?
public class Ap088{ public static void main( String args[]){ new Worker().doOverLoad(); }//end main() }//end class definition class Worker{ public void doOverLoad(){ SubC a = new SubC(); SuperC b = new SubC(); SubC obj = new SubC(); obj.overLoadMthd(a); obj.overLoadMthd(b); System.out.println(); }//end doOverLoad() }// end class class SuperC{ public void overLoadMthd(SubC x){ System.out.print("SubC "); }//end overLoadMthd }//end SuperC class SubC extends SuperC{ public void overLoadMthd(SuperC x){ System.out.print("SuperC "); }//end overLoadMthd }//end class SubC |
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.
According to the subset
document,
"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.)
See the highlighted note at the beginning of this lesson.
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.
Method overriding will be the topic of a future lesson.
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.
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 type
Test, 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.
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.
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.
int x = 2147483647; square((double)x); long y = 9223372036854775807L; square((double)y); |
The above modification would cause the program to produce the following output:
double 2.147483647E9
double 9.223372036854776E18
double 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.)
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.
See the highlighted note at the beginning of this lesson.
The problem that prevented this program from compiling is very subtle.
This program defines a class hierarchy consisting of:
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.
public int square(int y){ return super.square(y); }//end square() |
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.
The JDK 1.3 compiler produced the following error:
Ap081.java:28: square(double) is already
defined in Worker
public double square(double y){
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.
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
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.
-end-