Pat said:
The behavior I expected is that
(i) protected methods are not part of the externally visible interface of a
class, but are only visible to "friends" of the class (i.e. classes in the
same package)
and
(ii) protected methods are *internally* visible within the class itself and
its subclasses.
So a subclass can always call an inherited protected method internally - its
calling a method on itself.
I don't usually find it useful to divide a class' interface quite that
way. If I want to make just two categories, then I will invariably lump
in protected members with public ones, because they can _always_ be
_made_ accessible to any class via a trivial subclass. Where the
default-access members should go depends on the purpose of the
categorization.
Anyway, the semantics you describe are good, just, and right as far as
they go. They don't, however, go into the questions of polymorphism and
virtual method dispatch, which complicate the issue.
But at runtime an object can never access the protected methods of another
object which is not
a friend. To take your example, if doIt() is a method on Animal, Bear is a
subclass of Animal in a different package, and walk is a protected method on
Animal :
public void doIt() {
Bear aBear = new Bear();
aBear.walk(); // illegal, walk is a protected method of an object which is
not our friend
}
C++ wont let you do this (call aBear().walk), and neither will java, after
the compiler bug was fixed in 1.4.
But Java will still let you do this:
((Animal) aBear).walk();
which has the same result. (That is, Bear.walk() is selected for
invocation at runtime.) I'm not a C++ expert; what would C++ do with that?
That's not a peculiar case, either; it applies anywhere that the walk()
method is invoked on an expression of _type_ reference to Animal,
regardless of the runtime _class_ of the actual referrent and whether
walk() is overridden in that class. The point is that "access" is a
tricky word in a situation like this, and at least part of its relevance
is indeed restricted to compile time, as Paul Hill wrote. And
furthermore, it applies to every access level except private (private
methods are not subject to virtual dispatch).
That's also why the observed buggy behavior in fact makes a certain
amount of sense -- the java compiler was in effect applying an implicit
upcast of the invocation target expression, resulting in a legal method
invocation. The only problem there is that it is not actually allowed
by the Java language. Oops.
Why is this an issue?
I can think of two reasons
(i) it is counter-intuitive -as I stated originally it is an arbitrary
violation of encapsulation.
I'll agree with that, to some extent. However, any class that can
access some particular method on the superclass can in fact access all
subclasses' overriding versions, if perhaps only indirectly. This is a
feature of polymorphic objects. Is it different in C++? In SmallTalk?
In some other widely-used OO language? (Really, I'm curious.)
(ii) it increases coupling. Now if the protected method of the subclass is
changed, the effects of the change
can ripple up the hierarchy, to superclasses, as well as down, to
subclasses. This is a bad thing.
I think you're missing it here, though. The kind of effect you describe
is an inherent feature of any language that offers polymorphic objects.
Your particular examples does exhibit a coupling problem, in that a
superclass has a dependency on one of its subclasses (allowed in Java),
but I think that's largely unrelated to the method access question we're
discussing.
John Bollinger
(e-mail address removed)