// 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(); 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.g(); // 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. }//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. */