The general reasoning for these casting rules (not only in Java) is that inheritance allows you build trees of classes, not just lists. To put it differently, not every object of type GrandParentClass
is necessarily an object of type ParentClass
and not every object of type ParentClass
is necessarily of type ChildClass
. You could conceive an alternative ParentClass2
inheriting from GrandParentClass
or simply instantiate an object of type GrandParentClass
. Imagine what would happen if you were allowed to access a GrandParentClass
object as if it was a ChildClass
object with all the methods and variables that are defined only in ChildClass
but not above. There is no meaningful resolution.
So, casting "up the chain" always makes sense, since the inheritance relation is known at compile-time. Your dynamic binding example is the same thing; You are binding "up the chain" (not at all "down the chain") since you are assigning a derived type object to a reference of a type it was derived from.