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: Extending Classes, Overriding Methods, and Polymorphic Behavior.
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 Ap131{ |
2. What output is produced by the following program?
public class Ap132{ |
3. What output is produced by the following program?
public class Ap133{ |
4. What output is produced by the following program?
public class Ap134{ |
5. What output is produced by the following program?
public class Ap135{ |
6. What output is produced by the following program?
public class Ap136{ |
7. What output is produced by the following program?
public class Ap137{ |
8. What output is produced by the following program?
public class Ap138{ |
9. What output is produced by the following program?
public class Ap139{ |
10. What output is produced by the following program?
public class Ap140{ |
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.
The class and interface definitions for the classes and interfaces named Base, A, B, X, and Y are the same as in Question 9.
Invoke the test method differently
However, the invocation of the method named test()
in the object instantiated from the class named
B is somewhat different.
The difference is identified by the boldface code in the following
fragment.
void doIt(){ |
Invoking test method on Base-type reference
In Question 9, and in the above code fragment as well (shown in Italics) the method named test() was invoked on each of the objects using a reference stored in a reference variable of type Base.
Invoking the overridden version of test method
This might be thought of as invoking the overridden version of the method, through polymorphism, without regard for anything having to do with the interfaces.
Invoking test method on interface-type reference
The boldface code in the next fragment invokes
the same method named test() on one of the same objects using a
reference variable of the interface type X.
X myVar2 = (X)myVar1; |
Only one test method in each object
Keep in mind that each object defines only one method named test(). This single method serves the dual purpose of overriding the method having the same signature from the superclass, and implementing a method with the same signature declared in each of the interfaces.
Implementing the interface method
Perhaps when the same method is invoked using a reference variable of the interface type, it might be thought of as implementing the interface method rather than overriding the method defined in the superclass.
The same method is invoked regardless of reference type
In any event, in this program, the same method is invoked whether it is invoked using a reference variable of the superclass type, or using a reference variable of the interface type.
Illustrates the behavior of signature collisions
The purpose of this and the previous question is not necessarily to illustrate a useful inheritance and implementation construct. Rather, these two questions are intended to illustrate the behavior of Java for the case of duplicated superclass and interface method signatures.
The question often arises in my classroom as to what will happen if a class inherits a method with a given signature and also implements one or more interfaces that declare a method with an identical signature.
The answer
The answer is that nothing bad happens, as long as the class provides a concrete definition for a method having that signature.
Only one method definition is allowed
Of course, only one definition can be provided for any given method signature, so that definition must satisfy the needs of overriding the inherited method as well as the needs of implementing the interfaces.
An example of signature collisions
The following fragment defines a class named Base
that defines a method named test(). The code also defines
two interfaces named X and Y, each of which declares a
method
named test() with an identical signature.
class Base{ |
Classes A and B extend Base and implement X and Y
The code in the following fragment defines two
classes, named A and B, each of which extends Base,
and implements both interfaces X and Y. Each
class
provides a concrete definition for the method named test(),
with
each class providing a different definition.
class A extends Base implements X,Y{ |
Override inherited method and define interface method
Each of the methods named test() in the above fragment serves not only to override the method inherited from the class named Base, but also to satisfy the requirement to define the methods declared in the implemented interfaces named X and Y.
In other words, a single definition of the method in each of the classes serves to satisfy three purposes, one of which is (normally) optional, and the other two of which are required.
Store object's references as type Base and invoke test method
Finally, the code in the following fragment
declares
a reference variable of the type Base. Objects
respectively
of the classes Base, A, and B are instantiated
and
stored in the reference variable. Then the method named test()
is invoked on each of the references in turn.
void doIt(){ |
As you probably expected, this causes the following text to appear on the screen:
Base A B
This program is very similar to the programs in Question 6 and Question 7. The program is Question 6 exposed a specific type mismatch problem. The program in Question 7 provided one solution to the problem.
A different solution
The boldface code in the following fragment
illustrates
a different solution to the problem.
void doIt(){ |
An array object of the interface type
In this case, rather than to declare the array object to be of type Object, the array is declared to be of the interface type X.
This is a less generic container than the one declared to be of type Object. Only references to objects instantiated from classes that implement the X interface, or objects instantiated from subclasses of those classes can be stored in the container. However, this is often adequate.
What methods can be invoked?
Since the references are stored as the interface type, any method declared in the interface can be invoked on the references stored in the container.
(Although it isn't implicitly obvious, it is also possible to invoke any of the eleven methods defined in the Object class on an object's reference being stored as an interface type.)Not the standard approach
If you are defining your own container, this is a satisfactory approach to implementation of the observer design pattern. However, you cannot use this approach when using containers from the standard collections framework, because those containers are designed to always store references as the generic type Object. In those cases, the casting solution of Question 7 is the required.
This program illustrates the correct use of an interface. It
uses
a cast of the interface type in the boldface statement in the following
fragment to resolve the problem that was discussed at length in
Question
6 earlier.
void doIt(){ |
The word container is often used in Java, with at least two different meaning. One meaning is to refer to the type of an object that is instantiated from a subclass of the class named Container. In that case, the object can be considered to be of type Container, and typically appears in a graphical user interface (GUI). That is not the usage of the word in the explanation of this program.
A more generic meaning
In this explanation, the word container has a more generic meaning. It is common to store a collection of object references in some sort of Java container, such as an array object or a Vector object. In fact, there is a complete collections framework provided to facilitate that sort of thing (Vector is one of the concrete classes in the Java Collections Framework).
Storing references at type Object
It is also common to declare the type of references stored in the container to be of the class Object. Because Object is a completely generic type, this means that a reference to any object instantiated from any class can be stored in the container. The standard containers such as Vector and Hashtable take this approach.
A class named Base and an interface named X
In a manner similar to several previous
programs,
this program defines a class named Base and an interface named X
as shown in the following fragment.
class Base{ |
Classes A and B extend Base and implement X
Also similar to previous programs, this
program
defines two classes named A and B. Each of these
classes
extends the class named Base and implement the interface named X,
as shown in the next fragment.
class A extends Base implements X{ |
Concrete definitions of the interface method
As before, these methods provide concrete definitions of the method named intfcMethodX(), which is declared in the interface named X.
An array of references of type Object
The interesting portion of this program begins
in the following fragment, which instantiates and populates a
two-element
array object (container) of type Object. (In
the
sense of this discussion, an array object is a container, albeit a very
simple one.)
void doIt(){ |
Store object references of type A and B as type Object
Because the container is declared to be of type Object, references to objects instantiated from any class can be stored in the container. The code in the above fragment instantiates two objects, (one of class A and the other of class B), and stores the two object's references in the container.
Cannot invoke interface method as type Object
The boldface statement in the next fragment produces the following compiler error under JDK 1.3:
Ap136.java:24: cannot resolve symbol
symbol : method intfcMethodX
()
location: class java.lang.Object
myArray[i].intfcMethodX();
for(int i=0;i<myArray.length;i++){ |
What methods can you invoke as type Object?
It is allowable to store the reference to an object instantiated from any class in a container of the type Object. However, the only methods that can be directly invoked (without a cast) on that reference are the following eleven methods. These methods are defined in the class named Object:
Some, (but not all), of the methods in the above list are defined with default behavior in the Object class, and are meant to be overridden in new classes that you define. This includes the methods named equals and toString.
Some of the methods in the above list, such as getClass, are simply utility methods, which are not meant to be overridden.
Polymorphic behavior applies
If you invoke one of these methods on an object's reference (being stored as type Object), polymorphic behavior will apply. The version of the method overridden in, or inherited into, the class from which the object was instantiated will be identified and executed.
Otherwise, a cast is required
In order to invoke any method other than one of the eleven methods in the above list, (on an object's reference being stored as type Object), you must cast the reference to some other type.
Casting to an interface type
The exact manner in which you write the cast
will
differ from one situation to the next. In this case, the problem
can be resolved by rewriting the program using the interface cast shown
in boldface in the following fragment.
void doIt(){ |
The observer design pattern
By implementing an interface, and using a cast such as this, you can store references to many different objects, of many different actual types, each of which implements the same interface, but which have no required superclass-subclass relationship, in the same container. Then, when needed, you can invoke the interface methods on any of the objects whose references are stored in the container.
This is a commonly used design pattern in Java, often referred to as the observer design pattern.
Registration of observers
With this design pattern, none, one, or more observer objects, (which implement a common observer interface) are registered on an observable object. This means references to the observer objects are stored in a container by the observable object.
Making a callback
When the observable object determines that some interesting event has occurred, the observable object invokes a specific interface method on each of the observer objects whose references are stored in the container.
The observer objects execute whatever behavior they were designed to execute as a result of having been notified of the event.
The model-view-control (MVC) paradigm
In fact, there is a class named Observable and an interface named Observer in the standard Java library. The purpose of these class and interface definitions is to make it easy to implement the observer design pattern.
(The Observer interface and the Observable class are often used to implement a programming style commonly referred to as the MVC paradigm.)Delegation event model, bound properties of Beans, etc.
Java also provides other tools for implementing the observer design pattern under more specific circumstances, such as the Delegation Event Model, and in conjunction with bound and constrained properties in JavaBeans Components.
This program illustrates a more substantive use of the interface than was the case in the previous programs.
The class named Base
The program defines a class named Base as shown in the
following
fragment.
class Base{ |
The interface named X
The program also defines an interface named X as shown in
the
next fragment. Note that this interface declares a method named
intfcMethodX().
interface X{ |
Class A extends Base and implements X
The next fragment shows the definition of a class named A,
which
extends Base and implements X.
class A extends Base implements X{ |
Defining interface method
Because the class named A implements the interface named X, it must provide a concrete definition of all the methods declared in X.
In this case, there is only one such method. That method is named intfcMethodX(). A concrete definition for the method is provided in the class named A.
Class B also extends Base and implements X
The next fragment shows the definition of another class (named B),
which also extends Base and implements X.
class B extends Base implements X{ |
Defining the interface method
Because this class also implements X, it must also provide a concrete definition of the method named intfcMethodX().
Different behavior for interface method
However (and this is extremely important), there is no requirement for this definition of the method to match the definition in the class named A, or to match the definition in any other class that implements X.
Only the method signature is necessarily common among all the classes that implement the interface.
In fact, the definition of the method named intfcMethodX() in the class named A is different from the definition of the method having the same name in the class named B.
The interesting behavior
The interesting behavior of this program is illustrated by the
boldface
code in the following fragment.
void doIt(){ |
Store object's references as interface type X
The code in the above fragment causes one object to be instantiated from the class named A, and another object to be instantiated from the class named B.
The references to each of these objects are stored in two different reference variables, each declared to be of the type of the interface X.
Invoke the interface method on each reference
A method named intfcMethodX() is invoked on each of the reference variables. Despite the fact that both object's references are stored as type X, the system selects and invokes the appropriate method, (as defined by the class from which each object was instantiated), on each of the objects. This causes the following text to appear on the screen:
A-intfcMethodX B-intfcMethodX
No subclass-superclass relationship exists
Thus, the use of an interface makes it possible to invoke methods having the same signatures on objects instantiated from different classes, without any requirement for a subclass-superclass relationship to exist among the classes involved.
In this case, the only subclass-superclass relationship between the classes named A and B was that they were both subclasses of the same superclass. Even that relationship was established for convenience, and was not a requirement.
Different behavior of interface methods
The methods having the same signature, (declared in the common interface, and defined in the classes), need not have any similarity in terms of behavior.
A new interface relationship
However, the fact that both classes implemented the interface named X created a new relationship among the classes. That relationship is not based on class inheritance.
The program defines a class named Base,
and a class named A, which extends Base, and implements
an
interface named X, as shown below.
class Base{ |
Implementing interfaces
A class may implement none, one, or more interfaces.
The cardinal rule on interfaces
If a class implements one or more interfaces, that class must either be declared abstract, or it must provide concrete definitions of all methods declared in and inherited into all of the interfaces that it implements. If the class is declared abstract, its subclasses must provide concrete definitions of the interface methods.
A concrete definition of an interface method
The interface named X in this program declares a method named intfcMethod(). The class named A provides a concrete definition of that method.
(The minimum requirement for a concrete definition is a method that matches the method signature and has an empty body.)Storing object's reference as an interface type
The interesting part of the program is shown
in
boldface in the following code fragment.
void doIt(){ |
The boldface code in the above fragment instantiates a new object of the class named A, and saves a reference to that object in a reference variable of the declared type X.
How many ways can you save an object's reference?
Recall that a reference to an object can be held by a reference variable whose type matches any of the following:
In this program, the type of the reference variable matches the interface named X, which is implemented by the class named A.
What does this allow you to do?
When a reference to an object is held by a reference variable whose type matches an interface implemented by the class from which the object was instantiated, that reference can be used to invoke any method declared in or inherited into that interface.
(That reference cannot be used to invoke methods not declared in or not inherited into that interface.)In this simple case ...
The method named intfcMethod() is declared in the interface named X and implemented in the class named A.
Therefore, the method named intfcMethod() can be invoked on an object instantiated from the class named A when the reference to the object is held in a reference variable of the interface type.
(The method could also be invoked if the reference is being held in a reference variable of declared type A.)Invocation of the method causes the text A-intfcMethod to appear on the screen.
One way to describe runtime polymorphic behavior is:
The runtime system selects among two or methods having the same signature, not on the basis of the type of the reference variable in which an object's reference is stored, but rather on the basis of the class from which the object was originally instantiated.Illustrates simple class and interface inheritance
The program defines a class named Base,
and a class named A, which extends Base, and implements
the
interface named X, as shown in the following fragment.
class Base{ |
Define an interface method
The interface named X declares a method named intfcMethod(). A concrete definition of that method is defined in the class named A.
A new object of type Base
The code in the following fragment
instantiates
a new object of the class Base and invokes its inherMethod().
This causes the word Base to appear on the output. There
is
nothing special about this. This is a simple example of the use
of
an object's reference to invoke one of its instance methods.
void doIt(){ |
A new object of type A
The boldface code in the following fragment
instantiates
a new object of the class A and invokes its intfcMethod().
This causes the test A-intfcMethod to appear on the output
screen.
There is also nothing special about this. This is also a simple
example
of the use of an object's reference to invoke one of its instance
methods.
A myVar2 = new A(); |
Not polymorphic behavior
The fact that the class named A implements the interface named X does not indicate polymorphic behavior in this case. Rather, this program is an example of simple class and interface inheritance.
Interface type is not used
The program makes no use of the interface as a type, and exhibits no polymorphic behavior (no decision among methods having the same signature is required).
The class named A inherits an abstract method named intfcMethod from the interface and must define it. (Otherwise, it would be necessary to declare the class named A abstract.)
The interface is not a particularly important player in this program.
This program is designed to test your knowledge of simple hierarchical polymorphic behavior.
Implement the interface named X
This program defines a class named A that extends a class
named
Base,
and implements an interface named X, as shown in the following
code
fragment.
class A extends Base implements X{ |
Override and define some methods
The class named A overrides the method named inherMethod(), which it inherits from the class named Base. It also provides a concrete definition of the method named intfcMethod(), which is declared in the interface named X.
Store object's reference as superclass type
The program instantiates an object of the class named A and
assigns
that object's reference to a reference variable of type Base,
as
shown in the following code fragment.
Base myVar2 = new A(); |
Oops! Cannot invoke this method
So far, so good. However, the next fragment shows where the
program
turns sour. It attempts to invoke the method named intfcMethod()
on the object's reference, which was stored as type Base.
myVar2.intfcMethod(); |
Polymorphic behavior doesn't apply here
Because the class named Base does not define the method named intfcMethod(), hierarchical polymorphic behavior does not apply. Therefore a reference to the object being stored as type Base cannot be used to directly invoke the method named intfcMethod(), and the program produces a compiler error.
What is the solution?
Hierarchical polymorphic behavior is possible only when the class defining the type of the reference (or some superclass of that class) contains a definition for the method that is invoked on the reference.
There are a couple of ways that downcasting could be used to solve the problem in this case.
Can store reference as interface type
A reference to an object instantiated from a class can be assigned to any reference variable whose declared type is the name of an interface implemented by the class from which the object was instantiated, or implemented by any superclass of that class.
Define two classes and an interface
This program defines a class named A that extends a class named Base. The class named Base extends Object by default.
The program also defines an interface named X.
Instantiate an object
The following statement instantiates an object of the class named A,
and attempts to assign that object's reference to a reference variable
whose type is the interface type named X.
X myVar2 = new A(); |
Interface X is defined but not implemented
Neither the class named A, the class named Base, nor the class named Object implements the interface named X. Therefore, it is not allowable to assign a reference to an object of the class named A to a reference variable whose declared type is X. Thus, the program produces the following compiler error under JDK 1.3:
Ap131.java:20: incompatible types
found : A
required: X
X myVar2 = new A();
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-