[3718] in java-interest
Re: Fundamental flaw in Java library distribution scheme
daemon@ATHENA.MIT.EDU (Jim Graham)
Thu Nov 23 01:02:42 1995
Date: Wed, 22 Nov 1995 20:10:55 -0800
From: flar@bendenweyr.Eng.Sun.COM (Jim Graham)
To: java-interest@java.Eng.Sun.COM
Cc: david@longview.com
Dave appears to have two concerns here, and I'll address them
individually. Since I don't know whether this thread started on the
mailing list or the newsgroup, I'll send to the mailing list to
encompass both venues.
The first concern is that we allowed an overriding method to specify a
return type that is more specific (i.e. assignable to) the return type
of the method it is overriding. This feature was added primarily for
cases such as clone() or copy() where the object returned would be of
the specific type of the target object. The subclasses could declare
that they returned "Object" since that is what the version of these
methods in the base class declared, but users would then have to
perform a lot of unwieldy casts at runtime to cast from Object down to
the specific class that should have been known to the Compiler and
runtime. The problem Dave found, which we had already found and
corrected, was that our method lookup included the return type in the
signature that was searched, and so if the subclass tried to declare a
more specific return type, it would essentially be declaring a new
method and failing to override as it had intended.
We have already fixed this problem by removing the feature. The
current compiler (which we haven't released yet) and runtime enforce
that if you declare a method in a subclass with the same name as
another method in a superclass, you must declare the exact same return
type - no subclasses. The language spec will have to be amended to
remove this capability too.
The second concern involves a programmer trying to override a method in
a subclass by declaring it with a more specific argument type than a
method in its superclass. This is not an override, it is an overload -
i.e. a completely new method unrelated to the superclass' method has
been defined and the two methods will be chosen based on type
information available at compile time. This is explicitly spelled out
in section 9.7 of the current language specification which defines how
the runtime dispatches method calls:
At compile time, [a specific definition for the method is found].
If there is a single most specific method definition, it is
called the *compile-time definition* for the method call;
its name and the compile-time types of the parameters in the
definition constitute the *signature* for the method call.
[later]
Any overriding methods are bypassed; the method definition
that was determined to be most specific at compile time is
the definition invoked at run time.
[later]
The lookup process starts from the class that is the run-time
type of the target object and from there works its way up the
chain of superclasses (...). As soon as a class is found with
a method definition that matches the signature for the method
call determined at compile time, that method definition is
invoked.
Note that the definition of which method is invoked is solidified at
compile time. If you want to override a method, you *must* use the
exact same argument types and return type or you will fail to
override. Note that even if you fail to override, you may fool new
classes compiled against your new subclass into calling your method
instead of the superclass' method if they determine your method to be
"more specific" based on your argument types, but this in no way
implies that you have overridden the "similar" method in your
superclass - in fact, you will intercept none of the method calls that
were originally targetted at its more relaxed definition. In
particular, if you want to do the following:
class foo {
void a(foo arg) {
// process a random foo object
}
}
class bar {
void a(bar arg) {
// special processing for bar objects
}
}
and your intention is to override foo.a() with bar.a(), you have failed.
You produced an overloaded (i.e. companion, separate) method, not an
override (i.e. replacement, alternate implementation) method. If you
want to catch method calls to the foo.a() method and perform more
specific processing in the case of arguments of type bar, then you should
write this instead:
class bar {
void a(foo arg) {
if (arg instanceof bar) {
bar myarg = (bar) arg;
// handle specific processing of bar objects
} else {
super.a(arg);
}
}
}
The burden of performing special handling based on dynamic run-time
types is removed from the interpreter/method dispatch and
responsibility is left with the implementor. This allows a much more
efficient method dispatch mechanism.
Dave, I'd like to thank you for pointing out the first problem, even
though we had already discovered this loophole, it is nice to have more
eyes watching out for problems like that. But, the second problem you
point out is more of a problem with understanding the specific way that
the Java language was defined with a goal of implementation speed than
a fundamental flaw in the Java distribution scheme. The definition
works as advertised and implemented, but may unfortunately confuse some
people.
...jim
-
This message was sent to the java-interest mailing list
Info: send 'help' to java-interest-request@java.sun.com