Polymorphism allows a subclass type to appear and behave like its
superclass type. For example, a RewardCard
can be used
exactly like a CreditCard
:
CreditCard cc = new RewardCard(123, "Sally Sixpack"); Date issued = cc.getIssueDate(); Date expired = cc.getExpiryDate(); double limit = cc.getLimit(); // and so on, using any CreditCard method
In Java, you use a cast to convert one type to another type. For example, you can use a cast an integer type to a floating point type when dividing two numbers:
double oneHalf = (double) 1 / 2;
Similarly, it is possible to convert the type of an
object reference to another type. For example, you
can convert a RewardCard
reference to
a CreditCard
reference:
// declared type : RewardCard
// actual type : RewardCard
RewardCard rc = new RewardCard(123, "Sally Sixpack");
// ok, RewardCard can substitute for CreditCard
CreditCard cc = (CreditCard) rc;
RewardCard rc = new RewardCard(123, "Sally Sixpack");
CreditCard cc = (CreditCard) rc;
Such a cast is called an upcast because it casts up the inheritance hierarchy.
Upcasting is legal but unnecessary because of substitutability: You can always substitute a child class instance for a parent class instance.
Sometimes, it is necessary to cast down the inheritance hierarchy using a downcast.
// gc is a GlobalCredit
// someCardNumber is the number of a RewardCard
// declared type : CreditCard
// actual type : RewardCard (we hope!)
CreditCard cc = gc.get(someCardNumber);
// ok, actual type of cc is RewardCard
RewardCard rc = (RewardCard) cc;
Substitutability does not automatically apply when downcasting: a parent class instance CAN NOT substitute for a child class instance.
This means that a downcast can fail:
// declared type : CreditCard // actual type : CreditCard CreditCard cc = new CreditCard(123456, "Sally Sixpack"); // whoops! CreditCard cannot substitute for RewardCard RewardCard rc = (RewardCard) cc;
This results in a run-time error (why not a compile-time error?).
Suppose you have a collection of credit cards that may or may not include reward cards. When you generate the monthly statement for each card, you want to print the reward points balance for the reward cards.
// gc is a GlobalCredit collection
for (CreditCard cc : gc)
{
output.print(cc.getNumber());
// print amount owing
output.printf(" Balance : $%-8.2f", cc.getBalance());
// print reward points
RewardCard rc = (RewardCard) cc;
output.printf("Bonus points : %d%n", rc.getPointBalance());
}
The above code compiles, but fails at runtime with an exception
if one or more of the cards in the collection is not a RewardCard
.
import java.io.PrintStream; import java.util.Random; import type.lib.CreditCard; import type.lib.RewardCard; import type.lib.GlobalCredit; public class PolymorphismFail { public static void main(String[] args) { PrintStream output = System.out; GlobalCredit gc = new GlobalCredit(); // Randomly populate the GlobalCredit colllection // with credit and reward cards. final int NUM_CARDS = 10; Random rng = new Random(); for (int i = 1; i <= NUM_CARDS; i++) { CreditCard cc = null; if (rng.nextBoolean()) { cc = new CreditCard(i, "Some Name"); output.printf("%s CreditCard%n", cc.getNumber()); } else { cc = new RewardCard(i, "Another Name"); output.printf("%s RewardCard%n", cc.getNumber()); } gc.add(cc); } output.println(); for (CreditCard cc : gc) { output.print(cc.getNumber()); // print amount owing output.printf(" Balance : $%-8.2f", cc.getBalance()); // print reward points; the cast will fail if cc is a CreditCard RewardCard rc = (RewardCard) cc; output.printf("\tBonus points : %d%n", rc.getPointBalance()); } } }
instanceof
What we need is a way to check if a CreditCard
reference
refers to an object that
is or is not a RewardCard
(or something substitutable
for a RewardCard
). In Java, you use the instanceof
operator to check if a reference points to an object of a specified type:
x instanceof Y
returns true if the reference variable
x
points to an object that is an instance of class Y
or a subclass of Y
.
instanceof
CreditCard cc1 = new CreditCard(123, "Sally Sixpack"); CreditCard cc2 = new RewardCard(123, "Sally Sixpack"); // does cc1 point to a CreditCard? boolean isCreditCard = cc1 instanceof CreditCard; output.println(isCreditCard); // does cc2 point to a CreditCard? isCreditCard = cc2 instanceof CreditCard; output.println(isCreditCard); // does cc1 point to a RewardCard? boolean isRewardCard = cc1 instanceof RewardCard; output.println(isRewardCard); // does cc2 point to a RewardCard? isRewardCard = cc2 instanceof RewardCard; output.println(isRewardCard);
The above code fragment prints:
true true false true
instanceof
Note that the compiler only allows the client to use
instanceof
where the operation is sensible.
It is a compile-time error if the reference has a type that is not
in the same branch of the inheritance hierarchy as the type name:
// all of these examples are compile-time errors boolean isInstanceOf; Date d = new Date(); isInstanceOf = d instanceof String; Fraction f = new Fraction(); isInstanceOf = f instanceof CreditCard; Scanner s = new Scanner(System.in); isInstanceOf = s instanceof Integer;
instanceof
If you need to downcast, you should use instanceof
to prevent runtime errors.
// assume gc is a GlobalCredit collection
for (CreditCard cc : gc)
{
output.print(cc.getNumber());
// print amount owing
output.printf(" Balance : $%.2f%n", cc.getBalance());
// print reward points if cc is a RewardCard
if (cc instanceof RewardCard)
{
RewardCard rc = (RewardCard) cc;
output.printf("Bonus points : %d%n", rc.getPointBalance());
}
}
Java has two mechanisms for defining a type that has multiple different implementations. The first mechanism is called an abstract class, which is simply a common parent class (the type) shared by one or more child classes (the implementations).
As a client, your concern is not how to implement abstract classes, but how to use recognize and use them.
An abstract class serves as the superclass for the multiple implementations (the subclasses). The subclasses inherit from (extend) the abstract superclass.
You can use inheritance to gather the common parts (attributes and methods) of two or more classes into a single class. Consider Figure 9.7 from the textbook:
In this example, Vehicle
is the superclass of
two classes Car
and Bus
. Vehicle
defines all of the attributes and methods that are common to
Car
s and Bus
es.
You can tell if a class is abstract from its API:
public abstract class Vehicle
An abstract class is usually incomplete in that it can
declare what methods must exist (ie. drive
)
but it cannot implement the methods because the methods depend
on details only the subclasses will know. Because
abstract classes are usually incomplete, Java will not allow
you to create an instance of an abstract class. It is a
compile-time error to try to create a new Vehicle
.
Although you cannot create an instance of an abstract
class, you can create a instance of one of its subclasses
(unless the subclass is also abstract). For example, you
might be able to create a Car
:
Vehicle v = new Car();
Now, you can use v
to call any Vehicle
method. If Vehicle
is well designed for its intended
purpose, you may be able to write your entire program using only
the Vehicle
API!
Another common way to create objects is to use another class that knows how to create subclass objects; such a class is typically called a factory:
Vehicle v = VehicleFactory.createVehicle("car"); Vehicle w = VehicleFactory.createVehicle("bus"); Vehicle x = VehicleFactory.createVehicle("scooter");
The factory class typically has a static method that knows how to create subclass objects. Such methods are called static factory methods.
The textbook uses a different example of
a static factory method; it puts the method
in the Vehicle
class:
Vehicle v = Vehicle.createCar();
In this case, Vehicle
is both
an abstract base class and a factory.
An abstract class can have abstract methods:
public abstract void drive();
As a client, the abstract
modifier has
no effect on your code; as far as you are concerned,
drive
is just an ordinary method:
Vehicle v = new Car(); v.drive();