CSc 335 Inheritance Heck
“Inheritance Heck” (previously named slightly differently) refers to seemingly complex and contradictory behavior by the compiler and runtime system when resolving types and methods in an inheritance hierarchy. However, through a proper understanding of how the compiler does type checking and how the runtime system works, it is possible to be able to correctly predict the behavior of even the most complex examples. First, some rulesA) Compile Time Rules:
1) We can assign up the inheritance hierarchy
Foo b = new Bar(); // New hierarchy ->
Bar g = new Goo();
Object o = new String("abc");
Container c = new JPanel(); /
2) We cannot assign down the inheritance hierarchy
Bar g2 = newFoo(); // Type mismatch: cannot convert from Foo to Baz
Goo b2 = newBar(); // Type mismatch: cannot convert from Bar to Goo
String str = newObject(); // Type mismatch: cannot convert from Object to String
JPanel p = newContainer(); //: cannot convert from Container to Jpanel
3) Casting up and casting down are both okay
Object anInt = newInteger(4);
int n = ((Integer) anInt).intValue();
System.out.println(((Object) anInt).getClass()); //class.java.lang.?______?
4) Casting sideways is notokay Object
Double aDouble = 1.2;
Integer int2 = 12;
((Integer) aDouble).toString(); // Cannot cast from Double to Integer
((Double) int2).toString(); // Cannot cast from Integer to Double
B) Runtime Rules
1) Casting below the object's runtime type is not okay.
Foo f = newBar();
// Compiles, but this is a Runtime error: Bar cannot be cast to Goo
((Goo)f).one(); // Assume Bar and Goo both have method one()
2) Casting sideways at runtime is notokay either
Object anInt = new Integer(123);
Object aDouble = new Double(4.56);
Object obj = (Double)anInt; // Compiles, but throws a CastClassException
Recommended Process for Answering Inheritance Heck Questions
Here is a recommended process that should help derive the correct answers.
1) First consider how the compiler will react
- If, based on the compile-time type of the variable, can we cast that type to that type (if there is a cast involved)?
- And, once we do that cast, does that class type have a .method() method, either directly, or inherited from one of its superclasses, found by traversing up the inheritance tree?
If yes to both, the code compiles. If either fails, write CE for CompileTime Error
2) If the message compiles consider the following that could happen at runtime
- Based on the run-time type, which is the type of the actual object rather than the type into which it is stored, can that object be cast to the variable's class type?
If yes, some message will be sent (code will run). Otherwise write RE for Runtime Error. The only time the answer would be "no" at runtime would be if you try to cast too far down the tree.
3) If there is no CE or RE, determine the output (answers can be selected from the table):
The Java runtime starts with the run-time type. It finds which version of the method it should use by traversing back up the inheritance tree. It will always find the method, eventually.
For static methods, the Java runtime starts with the compile-time type. Thus, the specific static method is figured out entirely by the compiler (there is no dynamic binding needed). Examples: Math.sqrt(double) or Integer.parseInt(String)
Here is an example hierarchy with many example problems that follow. Each method simply prints out itsmethod name as shown to the right of each method heading. Some methods concatenate other method calls.Object
+ method1() / "C1 M1"
+ method2() / "C1 M2"
+ staticmethod() / "C1 Static"
+method2() / "C2 M2"
+method3() / super.method2() +
+toString() / "C2 tS"
+staticmethod() / "C2 Static"
+method2() / "C4 M2"
+method3() / method1()+"C4 M3"
Class3+method1() / "C3 M1"
+method3() / "C3 M3"
Assume the four reference variables have been initialized for the questions that follow
Class1 var1 = new Class2();
Class1 var2 = new Class4();
Class2 var3 = new Class3();
Object var4 = new Class2();
Write Compiletime error, Runtime error, or the output if no errors
/*1*/ var1.method1();// C1 M1
Answer /*1*/: First, we determine var1 is of type Class1at compile-time type and Class1 has a method1(), so it compiles. Then it executes at runtime (no casting errors). It looks at the run-time type (Class2) and traverses up the tree looking for the first class with the method and calls the first one it finds. In this case, it looks at Class2 and there is no method1() defined in Class2, so it traverses up the tree (from Class2 to Class1) and looks at Class1 to see if it has a method1() which it does, so it executes that method.
Recall the class hierarchy: Class1
/*2*/ var2.method1(); // C1 M1
/*3*/ var3.method1(); // C3 M1 Note: Class3 has a method1
/*4*/ var4.method1(); // Compiletime Error
Answer /*4*/ First, we determine if var4 is of type Object (the compiler treats it as such), and since that class does not have a method1() it does not compile, and is considered a Compiletime Error.
/*5*/ var1.method2(); // C2 M2 / C1 M1
/*6*/ var2.method2(); // C4 M2
/*7*/ var4.method2(); // Compiletime Error
/*8*/ var1.method3(); // Compiletime Error
/*9*/ var2.method3(); // Compiletime Error
/*10*/ var3.method3(); // C3 M3
/*11*/ var4.method3(); // Compiletime Error
/*12*/ System.out.println(var1); // C2 tS
/*13*/ System.out.println(var2); // Class4@20c10f (or another hex#)
First looks at Class4 to see if there is a toString( ) method. Then, looks at Class1 to see if it has a toString( ). It finds that it does not and looks in the Object class finds that it has one and executes the method.
/*14*/ (Class2)var1.method3();// Compiletime Error
Answer: First, we determine var1 is of type Class1 which does not have a method3() so it does not compile. The cast is not explicit, so the compiler still treats var1 as a Class1 object.
/*15*/ ((Class2)var2).method2(); // Runtime Error
Answer: First, we determine the compile-time type of var2 (Class1) and then determine that this cast to this type to Class2 is legal. Class2 has a method2( ), so it compiles. Then considering the run-time type of var2 (Class4), can we cast to Class2? Since we cannot cast a Class4 to a Class2, this is a Runtime Error and the method is not called on the object.
/*16*/ ((Class4)var2).method2(); // C4 M2
Answer: First, we determine the compile-time type of the expression to the left of the period: Class4. Class4 has a method2( ), so it compiles. Then at runtime, there are no casting errors because a Class4 run-time type can be cast to Class4. It then starts from the run-time type, traversing up the tree looking for the first class with the method and calls the first one it finds. In this case, it looks at Class4 and there is a method2( ) defined there.
/*17*/ ((Class3)var4).method1(); // Runtime Error
Answer: var4 has a run-time type of Class2. So this cast requests a cast downward, which is not allowed.
/*18*/ ((Class3)var3).method2(); // C2 M2 / C3 M1
/*19*/ ((Class2)var1).method3(); // C1 M2 / C2 M3
/*20*/ System.out.println((Class2)var1);// C2 tS
Answer: This one compiles because it looks at var1, determines it is a legal cast and treats the argument to println as a Class2 object. Since Class2 has a toString() method, it compiles. At runtime, the expression has a run-time type of Class2, so it uses its method.
/*21*/ System.out.println((Class4)var1);// Runtime Error
Answer: This one fails at runtime because it looks at var1, determines it is a legal cast (the compiler treats var1 as a Class1 object). The cast treats the argument as a Class4 object. Since Class4 inherits a toString( ) method (all objects do), this line compiles. At runtime, it then determines that var1 has a run-time type of Class2, which cannot be cast to Class4 (this is called a cross cast, and is disallowed in Java).
/*22*/ var1.staticmethod(); // C1 Static
Answer: Static methods (and fields) are treated quite differently in Java. The compiler figures out everything at compile time. Here, var1 has a compile-time type of Class1, so its static method is used. Note that the run-time type of var1 (Class2) is not important, nor is it relevant that Class2 has its own staticmethod().
/*23*/ var2.staticmethod(); // C1 Static
Answer: var3 has a compiler type of Class1, so the samestaticmethod() is used here.
/*24*/ var3.staticmethod(); // C2 Static
Answer: var3 has a compile-time type of Class2, which has its ownstaticmethod().
/*25*/ var4.staticmethod(); // Compiletime Error
Answer: var4 has a compile-time type of Object, which has nostaticmethod(). (The fact that its run-time type is Class2, which does have a staticmethod() is not relevant.)
/*26*/ ((Class2)var1).staticmethod(); // C2 Static
/*27*/ ((Class4)var1).staticmethod(); //Runtime Error
Answer: var1 has a compile-time type of Class1, which can sometimes be cast to Class4, which has astaticmethod(), so everything is fine at compile-time. In fact, the compiler notes that Class4’sstaticmethod() is to be used. At runtime, var1 has a run-time type of Class2, which cannot be cast to Class4 (as it would be a cross-cast). So the problem is with the cast, not the call to the static method.
/*28*/ ((Object)var1).staticmethod(); // Compiletime Error
Answer: You tell me! var1 has a type of Class2, which has a staticmethod(). So why does this generate a compiler error. (Hint: run-time or compile-time type? Does that even matter?)