Inheritance is one of the primary composition mechanisms available in object-oriented programming. Some people criticize Java for supporting only single class inheritance as opposed to the multiple class inheritance that's possible under C++. However, even the less-complicated single inheritance leaves plenty of room for developer error. Below is a look at a few of the inheritance-related gotchas that are easy to commit and often difficult to debug.
When both a parent class and its subclass have a field with the same name, this technique is called variable shadowing. If the field in the parent class has private access or is in another package and has default access, there is no room for confusion. The child class cannot access the field in question of the parent class. So it's clear with which variable any reads and writes in the child class will take place.
However, if the like-named variable in the parent class is accessible to instances of the child class, some rather nonintuitive rules determine which field is accessed by different code invocations. The general rule is that the variable accessed depends on the class to which the variable has been cast.
For example, Listing A includes two classes, Base and Sub, which represent a parent class/subclass relationship. Both of these classes have an integer field named field. Listing B shows the instantiation of class Sub followed by many legal ways to refer to one or another of the field variables.
The first of these techniques, marked access one, directly accesses the field variable in the instance of Sub named s. This access, as one would expect, yields a value of 1. Access two, however, shows the logical disconnect involved in variable shadowing. Even though the expression b==s is true, the second access, b.field, evaluates to 0.
This difference between b.field and s.field, despite the referential equivalency of b and s, clearly displays the minefield inherent in variable shadowing. The only difference between b and s in this example is the type of the variable in which they’re stored. Variable shadowing isn't the only case wherein the value of something is determined by the type you call it, but developers nonetheless trip over these issues with surprising regularity.
The expressions marked access three and access four follow the rule of type dictating value. Specifically, access three resolves to a value of 1 because object b is cast to type Sub before its field variable is checked. Similarly, access four yields a value of 0 due to object s being cast to type Base before the field is accessed.
While like-named fields in subclasses overshadow their namesakes in parent classes, different terminology is used to discuss similar problems with methods. When a parent class and a child class each have a method with the same signature, the method of the child class overrides the method of the parent class. In Listing A, one can see that the getField method in class Sub has the same name and parameters, which is to say none, as the getField method in class Base.
Listing C shows the same object instantiation as found in Listing B and many possible invocations of the two getField methods. However, unlike Listing B, the results in Listing C are more to the liking of an object-oriented programmer. The line marked access one in Listing C invokes the getField method on a reference to the single object in the example while it is stored in a variable of type Sub. As was the case in the similar looking access one in Listing B, this invocation yields a value of 1.
The difference between method overriding and variable overshadowing is visible when comparing access two in Listing C to its like-named mate in Listing B. Whereas in Listing B the parent class’s field was accessed, in Listing C the field variable in the subclass is invoked, despite the type of the variable in which the reference is stored, for a resulting value of 1.
This strict adherence to reference/instance identity vs. variable type is the heart of polymorphism. Polymorphism is the feature that delegates behavior to the actual class of the referenced instance—not to the type in which the reference is stored. It is a fundamental concept to object-oriented programming, but it is also entirely as applies to fields. Polymorphism is a method-only affair.
Continuing with the examples, one comes upon access three and access four in Listing C. As do access one and access two, both of these produce a value of 1. Again this is due to the polymorphic nature of method access. To access the shadowed variable in the Base class via method invocation, one must use the syntax super.field within the Sub class.
In all honesty, the rules for neither variable shadowing nor method overloading are particularly complex; they’re just directly opposed. While method overriding relies on the actual object being accessed, variable shadowing depends on the type of the variable in which the object’s reference is stored. Both constructs are legal Java, but the power of method polymorphism vs. the contrary nature of variable shadowing finds expert programmers using method level polymorphism as a lynchpin in their designs. Variable shadowing, on the other hand, offers no benefits and plenty of pitfalls.