First of all: good news! The final version of The Java Language Specification, Third Edition is now available online! The specification has been improved considerably since the latest draft. Gilad Bracha seems to be responsible for the bulk of the work, which is a tough job. I think that the result is pretty good, although I'm afraid that I will keep bothering him with comments and requests for clarification ;).
Now back to the issue of this post. First of all: I have no idea how well-known the issue in this post is. I didn't know it, but it might actually be quite well-known. I have some references to previous discussions on this issue at the end of the post.
First, I want to say something about what influences the return type of a method in Java. Before Java 1.5, the return type of a method was just the plain return type specified in the method declaration. In other words, the return type did not depend on anything.
Java 1.5 introduces parameterized types and generic methods. The return type of a method can now also include type variables. This makes the return type dependent on the values of the type variables that occur in it. The type variables can have two different scopes: the class of the method or just the method itself, which makes it a generic method. So, the actual return type of a method now also depends on the value of these type variables.
However, there is one method in the Java library that does not return what it declares to return and needs another dependency. Indeed, there is an additional factor that influences the return type of this method.
The method I'm talking about is Object.getClass()
,
which returns the class of an object. In Java 1.5,
Class
itself is parameterized with the type that it
represents. For example, the Class
for
String
is Class<String>
. The question
is: what should the type parameter of the Class
returned by Object.getClass()
be? Well, at the
declaration of the method we basically know nothing, and that is
indeed the declared return type: a wildcard (unknown type) with a
very general bounds: the type must extend Object
.
public final Class<? extends Object> getClass()
However, let's take a look at a piece of code where
getClass
is invoked. Assuming that
getClass
returns what it claims to return, we cannot
declare c
to be of a more specific type, for example
Class<List>
. We must declare it with a very general
value for the type parameter: a wildcard.
List<String> list = ...; Class<?> c = list.getClass();
This is unfortunate, since we actually know more about the type
parameter of Class
. We know at the invocation
site that it is a List
, but of course we cannot
declare that in the return type of getClass
in this
way! So, we would like to let the return type of
getClass
dependent on the static type of the
Object
on which the method is invoked. In this way, the
variable c could be declared to be of type
Class<List>
.
The developers of the Java specification decided to make the return
type of this method a special case. That is, the Java Language
Specification defines that an invocation of the
getClass
method must be treated in special way. In
other words, the return type of the method is different from the one
declared on the source code. The bounds of the Class
returned by Object.getClass()
is changed by the
specification to the static type of the expression on which the
method getClass
is invoked. This is a useful feature,
but it is a pity that this return type cannot be declared!
This post is getting way too long, but I would like to
relate this to the implicit this
argument of methods in
object-oriented languages. For ordinary method arguments, you can
declare types, which might include type variables. These type
variables can influence the return type of the method. This is more
or less what we want, but now we need this for our implicit
this
argument. I'm not sure if a solution in this
direction is more attractive, but there is some link ... Are there
more methods whose return type we would like to dependent on the
static type of the object at the invocation site? If so, then this
should not be supported by the language itself. Unfortunately, I
cannot think of an example at the moment ;) .
There is even more to tell about this getClass
method,
since the type parameter of the Class
is not the static
type of the subject expression, but the erased variant of it. Maybe
I'll make that a future post ...
Some references to related discussions:
- Bug report in the Sun bug database about using the erased type as the parameter of the resulting Class: Object.getClass() should return erased class type
- Discussion in the Java Generics forum on the same issue: Are there bugs in the generics tutorial?
- Bug report for the Eclipse JDT subproject: Object.getClass() need to be treated special ?
- Another Generics FAQ: Is the capture of a bounded wildcard compatible to the bound?
1 comment:
It is possible to correctly type getClass() in an inheritance+generics type system like Nice (which also happens to integrate nicely with Java code).
For example:
void main(String[] args) {
List<int> l = new ArrayList();
Class<List<int>> c = l.getClass();
println(c);
}
prints "class java.util.ArrayList" (yes, that's java.util.ArrayList)
So what's the type of getClass?
<T> Class<!T> getClass(!T)
which, if it were allowed, would look in Java something like:
Class<? extends Object> ?.getClass()
ie. while getClass() is "physically" defined on Object the type system recognises that logically it is defined on the class that it is being called on.
clone is another method where the return type depends on the implicit this argument type.
Post a Comment