Inheritance Notes: Overriding vs. shadowing: Fields (variables) are treated differently than methods. The method called by a.m() depends on the dynamic type of a while the field accessed by a.f depends on the static type (i.e., declared type) of a. Upcasting and downcasting: Casting does not actually change an object; it's necessary in order to communicate with the compiler so that it can do type-checking. Upcasting (moving toward Object in the type hierarchy) is always legal; the necessary type-checking can be done at compile time. When downcasting, some checking is done at compile time (to see if it can possibly make sense), and an additional check is done at runtime. Dynamic method dispatch (4.9 in the text): For a.m(args), the method chosen depends on the *dynamic* type a and the *static* types of the args. In other words, the method signature is determined at compile time, based on the static types of the arguments. The method actually used is not determined until the dynamic (actual) type of a is known at runtime. The file Test.java has some examples. I have discussed dynamic dispatch in lecture, but not the use of static types for the arguments. Compatibility of array types (4.1.10 in text): This is tricky in Java because the designers chose to make B[] a subtype of A[] whenever B is a subtype of A. The following code compiles, but does not run; it throws an ArrayStoreException at runtime. For arrays, a runtime check is necessary to ensure that an assignment is valid. I haven't discussed this in lecture at all. The file ComparableTest.java has some examples. Object[] x = new String[3]; x[0] = new Integer(0);