Certification Objective Reference Variable Casting (Objective 5.2)


Certification Objective —Reference Variable Casting (Objective 5.2)

5.2 Given a scenario, develop code that demonstrates the use of polymorphism. Further, determine when casting will be necessary and recognize compiler vs. runtime errors related to object reference casting.

We've seen how it's both possible and common to use generic reference variable types to refer to more specific object types. It's at the heart of polymorphism. For example, this line of code should be second nature by now:

 Animal animal = new Dog(); 

But what happens when you want to use that animal reference variable to invoke a method that only class Dog has? You know it's referring to a Dog, and you want to do a Dog-specific thing? In the following code, we've got an array of Animals, and whenever we find a Dog in the array, we want to do a special Dog thing. Let's agree for now that all of this code is OK, except that we're not sure about the line of code that invokes the playDead method.

 class Animal {   void makeNoise() (System.out.println("generic noise"); } } class Dog extends Animal {   void makeNoise() {System.out.println("bark"); }   void playDead() { System.out.println("     roll over"); } } class CastTest2 {   public static void main(String [] args) {     Animal [] a = {new Animal(), new Dog(), new Animal() };     for(Animal animal : a) {       animal.makeNoise();       if(animal instanceof Dog) {         animal.playDead();       // try to do a Dog behavior ?       }     }   } } 

When we try to compile this code, the compiler says something like this:

 cannot find symbol 

The compiler is saying, "Hey, class Animal doesn't have a playDead() method". Let's modify the if code block:

 if(animal instanceof Dog) {   Dog d = (Dog) animal;      // casting the ref. var.   d.playDead(); } 

The new and improved code block contains a cast, which in this case is sometimes called a downcast, because we're casting down the inheritance tree to a more specific class. Now, the compiler is happy. Before we try to invoke playDead, we cast the animal variable to type Dog. What we're saying to the compiler is, "We know it's really referring to a Dog object, so it's okay to make a new Dog reference variable to refer to that object." In this case we're safe because before we ever try the cast, we do an instanceof test to make sure.

It's important to know that the compiler is forced to trust us when we do a downcast, even when we screw up:

 class Animal { } class Dog extends Animal { } class DogTest {   public static void main(String [] args) {     Animal animal = new Animal();     Dog d = (Dog) animal;          // compiles but fails later   } } 

It can be maddening! This code compiles! When we try to run it, we'll get an exception something like this:

 java.lang.ClassCastException 

Why can't we trust the compiler to help us out here? Can't it see that animal is of type Animal? All the compiler can do is verify that the two types are in the same inheritance tree, so that depending on whatever code might have come before the downcast, it's possible that animal is of type Dog. The compiler must allow things that might possibly work at runtime. However, if the compiler knows with certainty that the cast could not possibly work, compilation will fail. The following replacement code block will NOT compile:

 Animal animal = new Animal(); Dog d = (Dog) animal; String s = (String) animal;  // animal can't EVER be a String 

In this case, you'll get an error something like this:

 inconvertible types 

Unlike downcasting, upcasting (casting up the inheritance tree to a more general type) works implicitly (i.e. you don't have to type in the cast) because when you upcast you're implicitly restricting the number of methods you can invoke, as opposed to downcasting, which implies that later on, you might want to invoke a more specific method. For instance:

 class Animal { } class Dog extends Animal { } class DogTest {   public static void main(String [] args) {     Dog d = new Dog();     Animal a1 = d;           // upcast ok with no explicit cast     Animal a2 = (Animal) d;  // upcast ok with an explicit cast   } } 

Both of the previous upcasts will compile and run without exception, because a Dog IS-A Animal, which means that anything an Animal can do, a Dog can do. A Dog can do more, of course, but the point is—anyone with an Animal reference can safely call Animal methods on a Dog instance. The Animal methods may have been overridden in the Dog class, but all we care about now is that a Dog can always do at least everything an Animal can do. The compiler and JVM know it too, so the implicit upcast is always legal for assigning an object of a subtype to a reference of one of its supertype classes (or interfaces). If Dog implements Pet, and Pet defines beFriendly(), then a Dog can be implicitly cast to a Pet, but the only Dog method you can invoke then is beFriendly(), which Dog was forced to implement because Dog implements the Pet interface.

One more thingif Dog implements Pet, then if Beagle extends Dog, but Beagle does not declare that it implements Pet, Beagle is still a Pet! Beagle is a Pet simply because it extends Dog, and Dog's already taken care of the Pet parts of itself, and all its children. The Beagle class can always override any methods it inherits from Dog, including methods that Dog implemented to fulfill its interface contract.

And just one more thingif Beagle does declare it implements Pet, just so that others looking at the Beagle class API can easily see that Beagle IS-A Pet, without having to look at Beagle's superclasses, Beagle still doesn't need to implement the beFriendly() method if the Dog class (Beagle's superclass) has already taken care of that. In other words, if Beagle IS-A Dog, and Dog IS-A Pet, then Beagle IS-A Pet, and has already met its Pet obligations for implementing the beFriendly() method since it inherits the beFriendly() method. The compiler is smart enough to say, "I know Beagle already IS a Dog, but it's OK to make it more obvious."

So don't be fooled by code that shows a concrete class that declares that it implements an interface, but doesn't implement the methods of the interface. Before you can tell whether the code is legal, you must know what the superclasses of this implementing class have declared. If any class in its inheritance tree has already provided concrete (i.e., non-abstract) method implementations, and has declared that it (the superclass) implements the interface, then the subclass is under no obligation to re-implement (override) those methods.

image from book
Exam Watch

The exam creators will tell you that they're forced to jam tons of code into little spaces "because of the exam engine." While that's partially true, they ALSO like to obfuscate. The following code:

 Animal a = new Dog(); Dog d = (Dog) a; a.doDogStuff(); 

Can be replaced with this easy-to-read bit of fun:

 Animal a = new Dog(); ((Dog)a).doDogStuff (); 

In this case the compiler needs all of those parentheses, otherwise it thinks it's been handed an incomplete statement.

image from book



SCJP Sun Certified Programmer for Java 5 Study Guide Exam 310-055
SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055) (Certification Press)
ISBN: 0072253606
EAN: 2147483647
Year: 2006
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net