Tuesday, April 19, 2005

Java Surprise 3: The Return of the Class

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:

1 comment:

Rohan said...

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();

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.