Inheritance and Polymorphism

Ok, so we didn't cover every nitty gritty little detail of this stuff in section.  If you understand the key points we touched on, you'll do fine on whatever test question(s) we ask about polymorphism.  Hopefully this can act as a solid study reference without being too overwhelming.  And if you want to learn all the finer points, they're here too.

See also: Lecture Notes for this topic (week 9).
 
 Constructors & Initializers
Starting at the constructor that is actually invoked with new, things happen in this order: 
  • If there is explicit constructor chaining (this(...)), call it.
  • If there is no explicit chaining:
    • If there is an explicit super call, call it.
    • If there is no explicit super call, call super() automatically.
  • Either way, run data member initializers next.
  • Finally, run the main body of the constructor code.
Remember that an initializer is something like "= 2" at the end of a variable declaration
 
 Access Modifiers
  A child class method or variable can be made more public than the one it is overriding/shadowing, but not less public. 
 
 Binding
  Needed when there is a name conflict between one method/variable and another inherited from a parent class.  The argument types for methods need to be identical as well as the name or there will be no conflict.  Binding decides which of the conflicting items to use.  Both methods of binding in Java start from a particular type, and then look for the version of the item that is most specific to that type.  (So if the given type does not contain a version, it will use the most immediate parent type that does contain one).

Static Binding 
Looks at type of the reference variable; assumes object pointed to is of this type.  This binding is set in stone at compile time, since the type of the variable never changes.

Dynamic Binding 
Looks at type of actual object being pointed to.  Since this binding depends on the value of the variable (what it is pointing to), this binding must happen while the program is running (hence, "dynamic").

Note that some version of the method or variable still has to be accessible via plain static binding.  This guarantees that when dynamic binding happens at runtime, there will always be something to choose.  That is, the variable can never point to an object type that doesn't have any version of the item in question--because, then what the heck would you pick?

Comparison 
  Static binding Dynamic binding
Naming conflict is called Shadowing Overriding
How resolved Looks at type of pointer Looks at type of object being pointed at
Used for - data members
- private methods*
- static methods
- all other methods
* This is true even if using dynamic binding you could find a more specific, but still accessible version (a public version in a subtype)

Binding and this 
Although never declared anywhere, this is just another variable referencing an object.  The reference type of this matches the type of the class the currently-executing code is located inside.  But like any other variable, the actual object pointed to by this may instead be a subtype of that reference type.  So static and dynamic binding based on this CAN give different results. 

For example: 
   class A { f() {...} } 
   class B extends A { ... }  
   B b = new B();  
   b.f();  
While in f(), this is of type A but it points to an object of type B
 

 Interfaces
  Because of dynamic binding, it's ok to have a variable where the reference type is an interface.  Since it is impossible to instantiate an object that is actually of that type, we know the reference will instead always be pointing to an object of a type that implements the interface.  Dynamic binding will be used based on this object's type.  Notice how all items that would use static binding cannot be placed in interfaces--think about why it would be a problem if they could be... 

Interfaces can extend other interfaces.  Classes can only implement interfaces; they cannot extend them. 
 

 Casting
  Casting is the process of changing from one reference type to another.  The type of the object being pointed to is not changed.  In fact, nothing about the object being pointed to changes; only the pointer itself changes.

Upcasting - Casting from a child type to its parent type--more general.
Downcasting - Casting from a parent type to a particular child type--more specific.
One way to remember: think of an aerial view.  Moving up gives you a broader view, moving down gives you more details.

  • Upcasting is implicit--Java will do the cast automatically if you don't write it.
  • Upcasting is guaranteed to work (if you are pointing to a child type, you know the object is also of the parent type).
  • Downcasting is explicit--you need to write a cast expression or else Java will throw a fit when you compile.
  • Downcasting is not guaranteed to work:
    • If your pointer is of the parent type, it may or may not be pointing to an object of the child type you want.  This depends on the value of the reference variable, which of course is not known until run time.
    • At runtime, if the actual type of the pointed-to object matches or is a subclass of the type you are trying to cast to, the cast succeeds.
    • If it is not, you get a ClassCastException.
  • You can write a cast that is guaranteed to fail.  For example, casting from one reference to a type that is neither a parent nor child of that type.  Java considers there casts invalid and won't compile them.
  • Casting can go several "steps" at a time--so you could try to cast from a parent type to one of its children's children, for example.
Syntax 
An explicit cast looks like:
  (type to cast to)reference 
  Example: intPuzzle = (PuzzleAsInt)somePuzzle 

Becuase of Java's "order of operators" (precedence), you will often need to add extra parentheses:
  ( (type to cast to)reference ).member 
 

Peter Flynn