Let’s return to the Item and SaleItem classes, which we defined starting in Listing A.2.12. Try the following program and see what happens:
The first assignment statement for item1 is no surprise; it assigns an Item object to an Item variable. But that second assignment statement—assigning a SaleItem object to an Item variable? How can that possibly work? What mad sorcery is this?
Remember, we’re talking about an is-a relationship. A SaleItemis anItem. That’s why it’s legal to make the assignment. That is called polymorphism—the ability to assign a child class object to a parent class variable.
Note that you can’t assign a parent object to a child variable. Every SaleItem is an Item, but not every Item is a SaleItem. If you try this:
The compiler will (correctly) complain:
Polymorphism.java:7: error: incompatible types:
Item cannot be converted to SaleItem
SaleItem badNews = new Item("Oops", "X-000", 6.66);
If you compile and run this program (without the bad line in it), you get this output:
Take a look at the System.out.println statements that produced that output. The first one uses Item’s toString method to print item1 with its name, SKU, and price. The second one uses SaleItem’s toString method to print item2’s name, SKU, price and discount rate.
But both item1 and item2 are Item objects, so how did the second println know to call the toString method from SaleItem?
The answer is dynamic binding. At compile time, both item1 and item2 have the Item data type. But at run time, the JVM looks at the actual object that item2 refers to and finds that it has the SaleItem data type, and it uses SaleItem’s toString method.
This difference between what the compiler sees and what the runtime looks at has very important consequences.
Consider Listing A.3.3, which sets up an array of Item. Polymorphism allows some of them to be Item instances and others to be SaleItem instances:
We want the program to go through the array and print each item’s name and price, and, if it’s a sale item, say how much the customer saves:
Rye Bread: $3.95
Tomato Soup: $1.23 - you save $0.06
Canned Lima Beans: $2.52 - you save $0.46
Frozen Pizza: $5.19 - you save $0.71
Organic Salsa: $3.79
Here’s the pseudo-code for what we want to do:
Note that the for loop variable is an Item, because we have told the compiler that foods is an array of Item. Here’s the big question: how do we determine—at run time—if an array element is a SaleItem or an Item? We use the instanceof operator, which takes the form:
instanceof returns true if the given variable is an instance of Class; false otherwise. A first try at the code looks like this:
But when we compile, the compiler tells us:
PolyArray.java:17: error: cannot find symbol
food.getDiscount();
^
symbol: method getDiscount()
location: variable food of type Item
Why can’t the compiler find getDiscount? The answer is in variable food of type Item. As far as the compiler is concerned, food is an Item, and that class does not have a getDiscount method in it.
What we have to do is use a cast to tell the compiler, “yes, we declared it as an Item, but please treat it as a SaleItem”:
Now the compiler is happy—saleFood is a SaleItem, and that class has a getDiscount method.
Here’s a quick summary of inheritance, polymorphism, and dynamic binding.
A subclass (the child class) extends a superclass (the parent class).
A subclass constructor can call the superclass constructor by invoking the super method.
If you use a super constructor, it must be the first non-comment line.
Subclass methods can invoke the superclass methods by using super.method anywhere in the subclass method body.
Polymorphism allows you to assign a subclass object to a superclass variable. For example,
At compile time, the compiler sees the superclass variable as having the superclass data type. In the preceding code, the compiler says that myItem has data type Item.
At run time, the compiler uses the actual data type of the object. When you say:
the JVM will see that myItem contains a reference to a SaleItem object and will invoke SaleItem’s toString method.
The compiler won’t let you call a method that exists only in the subclass on a superclass variable. This won’t work:
getPrice is fine; that method belongs to Item, but getDiscount belongs only to the subclass.
You can determine if a variable belongs to a class at run time by using the instanceof operator:
Once you have established that you have a variable of the subclass data type, you can convince the compiler to treat it as a subclass by using a cast:
The extra parentheses around the cast are required to get everything evaluated in the correct order.
ExercisesExercises
1.
This exercise will let you practice polymorphism and dynamic binding. Implement the Bicycle, ElectricBicycle, and CargoBicycle classes. (These are not defined in the same way as in the preceding text.) The parent Bicycle class has these attributes and methods:
frameSize, (in centimeters) a double, with a getter (but not a setter).
nGears, an integer, with a getter (but not a setter).
currentGear, an integer, with both a getter and setter.
A constructor with two parameters for the frame size and number of gears.
A toString method that includes frame size, number of gears, and current gear, properly labeled.
The ElectricBicycle class is a child of Bicycle and adds these attributes and methods:
batteryCapacity (in watt-hours), an integer, with a getter (but not a setter).
currentCharge (in watt-hours), a double, with a getter and setter.
A constructor with three parameters for frame size, number of gears, and battery capacity.
chargePercent, a method that returns the percentage charge in the battery as a decimal, by dividing current charge by battery capacity.
A toString method that includes frame size, number of gears, current gear, battery capacity, and current charge, properly labeled.
The CargoBicycle class is also a child of Bicycle and adds these attributes and methods:
maxLoad (in kilograms), a double, with a getter (but not a setter). This is the maximum load that the bicycle can carry.
currentLoad (in kilograms), a double, with a getter and setter.
A constructor with three parameters for frame size, number of gears, and maximum cargo load.
loadFactor, a method that returns the percentage of load on the bicycle as a decimal, by dividing the current load by the maximum load.
A toString method that includes frame size, number of gears, current gear, maximum load, and current load, properly labeled.
Figure A.3.13 is the UML diagram for the three classes.
Once you have implemented these classes, write a class named BicycleTest with a main method that does the following:
Create an array of Bicycle with these bicycles:
Set the electric bicycle’s current charge to 312.5 wH.
Set the cargo bicycle’s current load to 27.5 kg. Note: you may need to use a cast to do this step and the preceding step!
Iterate through the array. For each bicycle, call the toString method and print the information it returns.
2.
This exercise will let you practice polymorphism and dynamic binding. It uses the Account, SavingsAccount, and CreditCardAccount classes you developed in Exercise A.2.2.3. Instead of creating a customer with multiple accounts, create an array of these accounts:
An Account number 1066 with a balance of $7,500.
A SavingsAccount number 30507 with a balance of $4,500 and an APR of 1.5\%
A CreditCardAccount number 51782737 with a balance of $7,000.00, APR of 8\%, and credit limit of $1000.00
A CreditCardAccount number 629553328 with a balance of $1,500.00, an APR of 7.5\%, and a credit limit of $5,000
A CreditCardAccount number 4977201043L with a balance of -$5,000.00, an APR of 7\%, and a credit limit of $10,000 (The L after the account number lets the compiler know that the account number is a long integer.)
Your program will use a loop to do the following for each account: