|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 5
Here's an example showing how downcasting and instanceof can be used to the detriment of flexibility:
// In file rtci/ex5/Animal.java
class Animal {
//...
}
// In file rtci/ex5/Dog.java
class Dog extends Animal {
public void woof() {
System.out.println("Woof!");
}
//...
}
// In file rtci/ex5/Cat.java
class Cat extends Animal {
public void meow() {
System.out.println("Meow!");
}
//...
}
// In file rtci/ex5/Hippopotamus.java
class Hippopotamus extends Animal {
public void roar() {
System.out.println("Roar!");
}
//...
}
// In file rtci/ex5/Example5.java
class Example5 {
public static void main(String[] args) {
makeItTalk(new Cat());
makeItTalk(new Dog());
makeItTalk(new Hippopotamus());
}
public static void makeItTalk(Animal animal) {
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.meow();
}
else if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.woof();
}
else if (animal instanceof Hippopotamus) {
Hippopotamus hippopotamus = (Hippopotamus) animal;
hippopotamus.roar();
}
}
}
Although functionally the previous example is correct, I am hoping its design will trigger alarms in the object-oriented brains of most readers.
The makeItTalk() method's use of instanceof and downcasting represent one of the fundamental ways runtime class information can be abused in Java programs.
The trouble with this approach is that if you add a new Animal subtype, say Orangutan, you would also have to add a new else-if clause to the makeItTalk() method. Polymorphism and dynamic binding will take care of this for you automatically. (Polymorphism means you can use a variable of a superclass type to hold a reference to an object whose class is the superclass or any of
its subclasses. Dynamic binding means the JVM will decide at runtime which method implementation to invoke based on the class of the object. For more on
these concepts, see Resources.)
Your object-oriented brain will hopefully be more comfortable with the next version of the program, in which downcasting is cast aside in favor of polymorphism and dynamic binding.
// In file rtci/ex6/Animal.java
abstract class Animal {
public abstract void talk();
//...
}
// In file rtci/ex6/Dog.java
class Dog extends Animal {
public void talk() {
System.out.println("Woof!");
}
//...
}
// In file rtci/ex6/Cat.java
class Cat extends Animal {
public void talk() {
System.out.println("Meow!");
}
//...
}
// In file rtci/ex6/Hippopotamus.java
class Hippopotamus extends Animal {
public void talk() {
System.out.println("Roar!");
}
//...
}
// In file rtci/ex6/Example6.java
class Example6 {
public static void main(String[] args) {
makeItTalk(new Cat());
makeItTalk(new Dog());
makeItTalk(new Hippopotamus());
}
public static void makeItTalk(Animal animal) {
animal.talk();
}
}
Because class Animal declares a talk() method that Dog, Cat, and Hippopotamus override, the makeItTalk() method only needs to invoke talk() on the Animal reference. Dynamic binding ensures that the correct version of talk() will be invoked. If the Animal passed to makeItTalk() is a Dog, makeItTalk() will invoke Dog's implementation of talk(), which says, "Woof!".
Polymorphism and dynamic binding enable you to write code that doesn't need to know about subtypes. You don't have to use
runtime class information via the instanceof operator precisely because when you invoke an instance method, the JVM uses runtime class information to figure out which
implementation of the method to execute.
Although downcasting and instanceof can be abused as described above, they also have legitimate uses. One common use of a downcast is to cast an Object reference extracted from a Collection to a more specific subtype. Here's an example:
class Hippopotamus {
private String yawnAdjective;
Hippopotamus(String yawnAdjective) {
this.yawnAdjective = yawnAdjective;
}
void yawn() {
System.out.println(yawnAdjective + " yawn!");
}
}
// In file rtci/ex1/Example1.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
class Example1 {
public static void main(String[] args) {
ArrayList hippos = new ArrayList();
hippos.add(new Hippopotamus("Big"));
hippos.add(new Hippopotamus("Little"));
hippos.add(new Hippopotamus("Technicolor"));
makeHipposYawn(hippos);
}
// Client must pass a collection of Hippopotami
static void makeHipposYawn(Collection hippos) {
Iterator it = hippos.iterator();
while (it.hasNext()) {
Hippopotamus hippo = (Hippopotamus) it.next();
hippo.yawn();
}
}
}
In this example, Iterator's next() method returns a reference to a Hippopotamus object, but the type of the reference is Object. The reference is immediately downcast to Hippopotamus, so that the yawn() method can be invoked on the object.
Note that instanceof was not needed here, because a precondition of the makeHipposYawn() method states that the Collection, passed in as parameter hippos, be full of Hippopotamus objects. Nevertheless, this code makes use of runtime class information because the JVM checks to see whether the cast is
valid. In other words, the JVM makes sure the object referenced by the return value of next() really is a Hippopotamus. If not, the JVM will throw a ClassCastException and the makeHipposYawn() method will complete abruptly.
The primary use of instanceof is to find out whether you can perform some kind of operation on an object. In the next example, class DancingHippopotamus (a subclass of Hippopotamus) declares a dance() method. The makeHipposDance() method of class Example2 assumes it is receiving a collection of hippopotami, but not necessarily DancingHippopotamus objects. It uses instanceof to find out whether each object in the collection is an instance of DancingHippopotamus objects. For each instance of DancingHippopotamus it finds, it downcasts the reference to DancingHippopotamus and invokes dance().
// In file rtci/ex2/Hippopotamus.java
class Hippopotamus {
private String yawnAdjective;
Hippopotamus(String yawnAdjective) {
this.yawnAdjective = yawnAdjective;
}
void yawn() {
System.out.println(yawnAdjective + " yawn!");
}
}
// In file rtci/ex2/DancingHippopotamus.java
class DancingHippopotamus extends Hippopotamus {
DancingHippopotamus(String yawnAdjective) {
super(yawnAdjective);
}
void dance() {
System.out.println("Dance!");
}
}
// In file rtci/ex2/Example2.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
class Example2 {
public static void main(String[] args) {
ArrayList hippos = new ArrayList();
hippos.add(new Hippopotamus("Big"));
hippos.add(new DancingHippopotamus("Little"));
hippos.add(new Hippopotamus("Technicolor"));
makeHipposYawn(hippos);
makeHipposDance(hippos);
}
// Client must pass a collection of Hippopotami
static void makeHipposYawn(Collection hippos) {
Iterator it = hippos.iterator();
while (it.hasNext()) {
Hippopotamus hippo = (Hippopotamus) it.next();
hippo.yawn();
}
}
// Client must pass a collection of Hippopotami
static void makeHipposDance(Collection hippos) {
Iterator it = hippos.iterator();
while (it.hasNext()) {
Object hippo = it.next();
if (hippo instanceof DancingHippopotamus) {
DancingHippopotamus dh = (DancingHippopotamus) hippo;
dh.dance();
}
}
}
}
The previous example highlights the primary legitimate use of instanceof: to find out whether an object can do something for you (in this case, dance). When instanceof returns true, the reference is downcast to a more specific type so methods can be invoked.