Wednesday, 19 September 2012

Puzzle 66: A Private Matter


In this program, a subclass field has the same name as a superclass field. What does the program print?
class Base {

    public String className = "Base";

}



class Derived extends Base {

    private String className = "Derived";

}



public class PrivateMatter {

    public static void main(String[] args) {

        System.out.println(new Derived().className);

    }

}


Solution 66: A Private Matter

A superficial analysis of the program might suggest that it should print Derived, because that is what is stored in the className field of each Derived instance. A deeper analysis suggests that class Derived won't compile, because the variable className in Derived has more restrictive access than it does in Base: It is declared private in Derived and public in Base. If you tried compiling the program, you found that neither analysis is correct. The program doesn't compile, but the error is in the class PrivateMatter.
Had className been an instance method instead of an instance field, Derived.className() would have overridden Base.className(), and the program would have been illegal. The access modifier of an overriding method must provide at least as much access as that of the overridden method [JLS 8.4.8.3]. Because className is a field, Derived.className hides Base.className rather than overriding it [JLS 8.3]. It is legal, though inadvisable, for one field to hide another when the hiding field has an access modifier that provides less access than the hidden field. In fact, it is legal for a hiding field to have a type that is completely unrelated to that of the field it hides: The Derived class would be legal even if Derived.className were of type GregorianCalendar.
The compilation error in our program occurs when class PrivateMatter TRies to access Derived.className. Although Base has a public field className, this field is not inherited into Derived because it is hidden by Derived.className. Within the class Derived, the field name className refers to the private field Derived.className. Because this field is declared private, it is not accessible to the class PrivateMatter. Therefore, the compiler generates an error message something like this:
PrivateMatter.java:11: className has private access in Derived

        System.out.println(new Derived().className);

                           ^


Note that it is possible to access the public field Base.className in a Derived instance even though it is hidden, by casting the Derived instance to Base. The following version of PrivateMatter prints Base:
public class PrivateMatter {

    public static void main(String[] args) {

        System.out.println(((Base)new Derived()).className);

    }

}


This demonstrates a big difference between overriding and hiding. Once a method is overridden in a subclass, you can't invoke it on an instance of the subclass (except from within the subclass, by using the super keyword). You can, however, access a hidden field by casting the subclass instance to a superclass in which the field is not hidden.
If you want the program to print Derived—that is, you want it to exhibit overriding behavior—use public methods in place of public fields. This is, in any case, a good idea because it provides better encapsulation [EJ Item 19]. The following version of the program uses this technique and prints Derived as expected:
class Base {

    public String getClassName() {

        return "Base";

    }

}



class Derived extends Base {

    public String getClassName() {

        return "Derived";

    }

}



public class PublicMatter {

    public static void main(String[] args) {

        System.out.println(new Derived().getClassName());

    }

}


Note that we declared the method getClassName to be public in class Derived even though the corresponding field was private in the original program. As mentioned previously, an overriding method must have an access modifier that is no less restrictive than the method it overrides.
The lesson of this puzzle is that hiding is generally a bad idea. The language allows you to hide variables, nested types, and even static methods (as in Puzzle 48), but just because you can doesn't mean that you should. The problem with hiding is that it leads to confusion in the mind of the reader. Are you using the hidden entity or the entity that is doing the hiding? To avoid this confusion, simply avoid hiding.
A class that hides a field with one whose accessibility is more restrictive than that of the hidden field, as in our original program, violates the principle of subsumption, also known as the Liskov Substitution Principle [Liskov87]. This principle says that everything you can do with a base class, you can also do with a derived class. Subsumption is an integral part of the natural mental model of object-oriented programming. Whenever it is violated, a program becomes more difficult to understand. There are other ways that hiding one field with another can violate subsumption: if the two fields are of different types, if one field is static and the other isn't, if one field is final and the other isn't, if one field is constant and the other isn't, or if both are constant and have different values.
For language designers, consider eliminating the possibility of hiding: for example, by making all fields implicitly private. If this seems too draconian, at least consider restricting hiding so that it preserves subsumption.
In summary, hiding occurs when you declare a field, a static method, or a nested type whose name is identical to an accessible field, method, or type, respectively, in a superclass. Hiding is confusing; avoid it. Hiding fields in a manner that violates subsumption is especially harmful. More generally, avoid name reuse other than overriding.

No comments:

Post a Comment

Your comments are welcome!