Published: January 1, 2004
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: The this Keyword, static final Variables, and Initialization of Instance Variables.
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 Ap120{ |
2. What output is produced by the following program?
public class Ap121{ |
3. What output is produced by the following program?
public class Ap122{ |
4. What output is produced by the following program?
public class Ap123{ |
5. What output is produced by the following program?
public class Ap124{ |
6. What output is produced by the following program?
public class Ap125{ |
7. What output is produced by the following program?
public class Ap126{ |
8. What output is produced by the following program?
public class Ap127{ |
9. What output is produced by the following program?
public class Ap128{ |
10. What output is produced by the following program?
public class Ap129{ |
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.
In this program, two classes named A and B extend the class named Base, each overriding the method named test() to produce different behavior. (Typically, overridden methods in different classes will produce different behavior, even though they have the same names.)
Behavior appropriate for object on which method is invoked
In other words, the behavior of the method named test(), when invoked on a reference to an object of type A, is different from the behavior of the method named test() when invoked on a reference to an object of type B.
The method definitions
The definitions of the two classes named A
and B, along with the two versions of the overridden method
named
test()
are shown in the following fragment.
class A extends Base{ |
Store a subclass object's reference as a superclass type
The program declares a reference variable of
type
Base,
instantiates a new object of the class named A, and assigns
that
object's reference to the reference variable of type Base.
Then it invokes the method named test() on that reference as
shown
in the following fragment.
Base myVar = new A(); |
Polymorphic behavior applies
Simple polymorphic behavior causes the overridden version of the method named test(), defined in the class named A, to be executed. This causes the letter A followed by a space character to be displayed on the standard output device.
Store another subclass object's reference as superclass type
Then the program instantiates a new object from the class named B, and assigns that object's reference to the same reference variable, overwriting the reference previously stored there. (This causes the object whose reference was previously stored in the reference variable to become eligible for garbage collection in this case.)
Then the program invokes the method named test()
on the reference as shown in the following fragment.
myVar = new B(); |
Polymorphic behavior applies again
This time, simple polymorphic behavior causes the overridden version of the method named test(), defined in the class named B, to be executed. This causes the letter B followed by a space character to be displayed on the standard output device.
Again, what is runtime polymorphic behavior?
With runtime polymorphic behavior, the method selected for execution is based, not on the type of the reference variable holding the reference to the object, but rather on the actual class from which the object was instantiated.
If the method was properly overridden, the behavior exhibited by the execution of the method is appropriate for an object of the class from which the object was instantiated.
This program compiles and executes
successfully
causing the version of the method named test(), which is
overridden
in the class named A to be executed. That overridden
method
is shown in the following fragment.
class A extends Base{ |
So, what is the issue here?
The purpose of this program is to determine if
you understand polymorphic behavior and the role of downcasting, as
shown
in the following fragment.
Base myVar = new A(); |
This would be a simple case of polymorphic behavior were it not for the downcast shown in the above fragment.
The downcast is redundant
Actually, the downcast was placed there to see
if you could determine that it is redundant. It isn't required,
and
it has no impact on the behavior of this program. This program
would
behave exactly the same if the second statement in the above fragment
were
replaced with the following statement, which does not contain a
downcast.
myVar.test(); |
You need to know when downcasting is required, when it isn't required, and to make use of that knowledge to downcast appropriately.
You can store an object's reference in any reference variable whose declared type is a superclass of the actual class from which the object was instantiated.
May need to downcast later
Later on, when you attempt to make use of that reference, you may need to downcast it. Whether or not you will need to downcast will depend on what you attempt to do.
In order to invoke a method ...
For example, if you attempt to invoke a method on the reference, but that method is not defined in or inherited into the class of the reference variable, then you will need to downcast the reference in order to invoke the method on that reference.
Class Base defines method named test()
This program defines a class named Base that defines a method named test().
Class A extends Base and overrides test()
The program also defines a class named A that extends Base
and overrides the method named test() as shown in the following
fragment.
class Base{ |
A new object of the class Base
The program instantiates a new object of the class Base and
stores
a reference to that object in a reference variable of type Base,
as shown in the following fragment.
Base myVar = new Base(); |
Could invoke test() directly on the reference
Having done this, the program could invoke the method named test()
directly on the reference variable using a statement such as the
following,
which
is not part of this program.
myVar.test(); |
This statement would cause the version of the method named test() defined in the class named Base to be invoked, causing the word Base to appear on the standard output device.
This downcast is not allowed
However, this program attempts to cause the version of the method
named
test()
defined in the class named A to be invoked, by downcasting the
reference
to type A before invoking the method named test().
This is shown in the following fragment.
((A)myVar).test(); |
A runtime error occurs
This program compiles successfully. However, the downcast shown above causes the following runtime error to occur under JDK 1.3:
Exception in thread "main" java.lang.ClassCastException: Base
at
Worker.doIt(Ap126.java:22)
at
Ap126.main(Ap126.java:15)
What you can do
You can store an object's reference in a reference variable whose type is a superclass of the class from which the object was originally instantiated. Later, you can downcast the reference back to the type (class) from which the object was instantiated.
What you cannot do
However, you cannot downcast an object's reference to a subclass of the class from which the object was originally instantiated.
This rather straightforward program instantiates an object of the
class
named Base and assigns that object's reference to a reference
variable
of the type Base as shown in the following fragment. Then
it invokes the method named test() on the reference variable.
Base myVar = new Base(); |
Class Base defines the method named test()
The class named Base contains a concrete definition of the
method
named test() as shown in the following fragment. This is
the
method that is invoked by the code shown in the above fragment.
class Base{ |
Class A is just a smokescreen
The fact that the class named A extends the class named Base,
and overrides the method named test(), as shown in the
following
fragment, is of absolutely no consequence in the behavior of this
program.
Hopefully you understand why this is so. If not, then you still
have
a great deal of studying to do on Java inheritance.
class A extends Base{ |
This program defines an abstract class named Base.
Then it violates one of the rules regarding abstract classes,
by
attempting to instantiate an object of the abstract class as
shown
in the following code fragment.
Base myVar = new Base(); |
The program produces the following compiler error under JDK 1.3:
Ap124.java:19: Base is abstract; cannot be instantiated
Base myVar = new Base();
This program illustrates the use of an abstract class containing an abstract method to achieve polymorphic behavior.
The following code fragment shows an abstract
class named Base that contains an abstract method named test().
abstract class Base{ |
Extending abstract class and overriding abstract method
The class named A, shown in the
following
fragment extends the abstract class named Base and
overrides
the abstract method named test().
class A extends Base{ |
Can store a subclass reference as a superclass type
Because the class named A extends the class named Base, a reference to an object instantiated from the class named A can be stored in a reference variable of the declared type Base. No cast is required in this case.
Polymorphic behavior
Furthermore, because the class named Base contains the method named test(), (as an abstract method), when the method named test() is invoked on a reference to an object of the class named A, stored in a reference variable of type Base, the overridden version of the method as defined in the class named A will actually be invoked. This is polymorphic behavior.
The important code
The following code fragment shows the
instantiation
of an object of the class named A and the assignment of that
object's
reference to a reference variable of type Base. Then the
fragment
invokes the method named test() on the reference variable.
Base myVar = new A(); |
This causes the overridden version of the
method
named test(), shown in the following fragment, to be invoked,
which
causes the letter A to be displayed on the standard output
device.
public void test(){ |
A class in Java may be declared final. A class may also be declared abstract. A class cannot be declared both final and abstract.
Behavior of final and abstract classes
A class that is declared final cannot be extended. A class that is declared abstract cannot be instantiated. Therefore, it must be extended to be useful.
An abstract class is normally intended to be extended.
Methods can be final or abstract, but not both
A method in Java may be declared final. A method may also be declared abstract. However, a method cannot be declared both final and abstract.
Behavior of final and abstract methods
A method that is declared final cannot be overridden. A method that is declared abstract must be overridden to be useful.
An abstract method doesn't have a body.
abstract classes and methods
A class that contains an abstract method must itself be declared abstract. However, an abstract class is not required to contain abstract methods.
Failed to declare the class abstract
In this program, the class named Base
contains
an abstract method named test(), but the class is not declared abstract
as required.
class Base{ |
Therefore, the program produces the following compiler error under JDK 1.3:
Ap122.java:24: Base should be declared
abstract; it does not define test() in Base
class Base{
If you missed this question, you didn't pay attention to the explanation for question 1.
Define a method in a subclass
This program defines a subclass named A, which extends a superclass named Base. A method named test() is defined in the subclass named A but is not defined in any superclass of the class named A.
Store a reference as a superclass type
The program declares a reference variable of the superclass type,
and
stores a reference to an object of the subclass in that reference
variable
as shown in the following code fragment.
Base myVar = new A(); |
Downcast and invoke the method
Then the program invokes the method named test() on the
reference
stored as the superclass type, as shown in the following fragment.
((A)myVar).test(); |
Unlike the program in Question 1, the reference is downcast to the true type of the object before invoking the method named test(). As a result, this program does not produce a compiler error.
Why is the cast required?
As explained in Question 1, it is allowable to store a reference to a subclass object in a variable of a superclass type. Also, as explained in Question 1, it is not allowable to directly invoke, on that superclass reference, a method of the subclass object that is not defined in or inherited into the superclass.
However, such invocation is allowable if the programmer purposely
downcasts
the reference to the true type of the object before invoking the
method.
This program defines a subclass named A, which extends a superclass named Base. A method named test(), is defined in the subclass named A, which is not defined in any superclass of the class named A.
Store a reference as superclass type
The program declares a reference variable of the superclass type,
and
stores a reference to an object of the subclass in that reference
variable
as shown in the following code fragment. Note that no cast is
required
to store a reference to a subclass object in a reference variable of a
superclass type. The required type conversion happens
automatically
in this case.
Base myVar = new A(); |
Invoke a method on the reference
Then the program attempts to invoke the method named test()
on
the reference stored as the superclass type, as shown in the following
fragment. This produces a compiler error.
myVar.test(); |
The reason for the error
It is allowable to store a reference to a subclass object in a variable of a superclass type. However, it is not allowable to directly invoke, (on that superclass reference), a method of the subclass object that is not defined in or inherited into the superclass.
The following error message is produced by JDK 1.3.
Ap120.java:18: cannot resolve symbol
symbol : method test ()
location: class Base
myVar.test();
The solution is ...
Of course, this error can be avoided by casting the reference to
type
A before invoking the method as shown below:
((A)myVar).test(); |
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-