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!