// Rules for inheritance and type casting in Java. Here, AA is a superclass. // BB and CC both extends AA. The same type-casting rules apply if AA is // an interface and BB and CC both implements AA. The only difference would // be that nothing will be inherited from AA. // This program illustrate a subtlety that must be understood when using // dynamic dispatch. class AA // super class { void f() { System.out.println("AA.f being called"); } void g() { System.out.println("which f will be called?"); f(); } }//AA class BB extends AA { void f() { System.out.println("BB.f being called"); } // g() inherited. } class CC extends AA { void h() { System.out.println("CC.h being called"); } // f, g inherited } public class casting { static void p(String x) { System.out.println("String version"); } static void p(Object x) { System.out.println("Object version"); } public static void main(String[] ags) { AA a1 = new AA(); // here, the static and dynamic types are the same BB b1 = new BB(); CC c1 = new CC(); a1.g(); b1.g(); c1.h(); ////////////////////////////// nothing interesting so far. AA n1 = new BB(); // n1 has static type AA, points to dynamic type BB AA n2 = new CC(); // n2 has static type AA, dynamic type CC AA n3; if (Math.random()<0.5) n3 = new AA(); else n3 = new BB(); // n3 has static type AA, but what's the dynamic type? // The compiler only uses the static type when checking code. n1.f(); // which version of f will be called? // DYNAMIC DISPATCH means it depends on the dynamic type of n1. /////////// When and how to use TYPE CASTING: /////////// // n2.h(); // compiler error because n2 has static type AA (no h) ((CC)n2).h(); // now OK // ((CC)n1).h(); // runtime error, because n1 has dynamic type BB // ((CC)b1).h(); // compiler error: can't type cast across, only up/down a1 = b1; // ok: no type casting needed is BB extends/implements AA //b1 = (BB)a1; // compiles but produces runtime error, since a1 is an // AA object, not a BB object. Object[] M = new Object[10]; // Object is the superclass of all classes M[0] = "abc"; // a String is an Object M[1] = 3.14; // automatically converts to Double, which is an Object // System.out.println(M[0].length()); // compiler error, why? System.out.println(((String)M[0]).length()); // ok now //System.out.println(((String)M[1]).length()); // runtime error, why? // now for a semi-tricky question: ((AA)b1).g(); // which f will g call? (run to find out, then explain) // When calling something n.p(x), dynamic dispatch only occurs on n, // not on x. if there are two versions of p in object n then only // the static type of x is used to disambiguate them: Object x = "abc"; p(x); // prints "Object version" because static type of x is Object. switching.test(); // see below }//main } /* In summary, type casting in the sense of casting between classes only informs the compiler that a certain object should be treated as of a certain type. However, whether that object is indeed of that type depends on runtime type information. Furthermore, type casting only makes sense from a superclass object to a subclass object. That is if B is a subclass of A and x is declared to be of type A: (A x;), which means that x has static type A, then B y = (B)x; is required to type cast x from an A object to a B object. Type casting in the reverse direction, from subclass to superclass, is implicit: x = y; // assuming y has static type B. this is because a B object is ALWAYS an A object. Why is ( B y = (B)x; ) allowed? because an A object COULD BE a B object. Furthermore, type casting is only allowed between superclass and subclass. If C also extends/implements A, then casting from B to C or C to B will both generate compiler errors. B and C will in general each contain information and procedures not found in the other, so one cannot simply "become" the other. Modernized Alternative To Type Casting In recent versions of Java, the switch statement (or switch expression) has been extended to allow matching on types. This is arguably better than type casting as it forces one to deal with the possibility that the casting may fail: */ class switching { static void test() { AA n3; double r = Math.random(); if (r<0.3) n3 = new AA(); else if (r<0.6) n3 = new BB(); else n3 = new CC(); switch(n3) { case BB x: x.f(); break; case CC x: x.h(); break; case AA x: x.g(); break; } }//test } /* The switch cases must be ordered from specific to general. That is, the case for AA cannot go above the cases for BB or CC. */