Sunday, May 20, 2007

Watching Josh Bloch's presentation at JavaOne about new topics
in the second edition of Effective Java
makes me want to go out and get my own copy. Unfortunately, he's not
scheduled to have the new edition in print until later this year.

There was a coincidental adjacency between two slides in
Josh's
talk that made me think a bit more about the idea of Super Type Tokens.
The last slide of his discussion of generics gave a complete
implementation of the mind-expanding Typesafe Heterogenous Containers
(THC) pattern using Super Type Tokens:

But on the very next slide, the very first bullet of the
summary of his presentation reminds us

Don't ignore compiler warnings.

This was referring to Josh's advice earlier in the
presentation not
to ignore or suppress unchecked compiler warnings without trying to
understand them. Ideally, you should only suppress these warnings when
you have good reason to believe that the code is type-safe, even though
you might not be able to convince the compiler of that fact.

The method Favorites2.getFavorite,
above, is annotated
to suppress a warning from the compiler. Without that annotation, the
compiler complains about the cast to the type T, a type parameter. Is
this code demonstrably type safe? Is it possible to cause this cast to
fail using code that is otherwise completely type safe? Unfortunately,
the cast is not safe:

This program compiles without warning, but it exposes the
loopole in the type system created by the cast to T in Favorites2.getFavorite.
The compiler's warning does, after all, tell us about a weakness in the
type safety of the program.

The issue is a subtle one: TypeRef
treats two types as the same when the underlying java.lang.reflect.Type
objects are equal. A given java.lang.reflect.Type
object represents a particular static
type appearing in the source, but if it is a type variable it can
represent a different dynamic
type from
one point in the program's execution to another. The program Oops
exploits that mismatch.

The Super Type Token pattern can be redeemed by disallowing
the use of type variables anywhere in the Type object it stores. That
can be enforced at runtime (but not at compile time) in the constructor.

7 comments:

Btw, there is a way to reify generics in Java without adding “List<class T>” syntax that you've mentioned in the original reification post. The first [at the most useful] step is to start recording reified types in all generic class instantiations at JVM level and bytecode level. Whenever constructor is invoked, the actual type parameters passed shall be recorded (I'll omit approaches for implementation at JVM level). If raw type is instantiated, then this fact should be recorded too.Even though it is not 100% satisfactory from type-safety point of view (you still have to allow all the old code to work), it will solve most of practical issues arising from the lack of reification:1) You will be able to retrieve runtime type arguments of a given Object (i.e. runtime Type and not just runtime Class) with something like Object.getType() method.2) You will be allowed to write class literals for generic types (the result will be Type for compatibility as you’ve blogged somewhere before) and use them with Type.isInstance method in a useful way.3) You will be able to instanceof for instantiations of generic types.4) Serialization can be updated to store/load those actual type parameters for non-raw types.However, for compatibility reasons, raw types and unchecked casts shall be still allowed. So, you can have a case where variable "v" has a declared generic type, for example, "List<String>", but at the same time "v instanceof List<String>" is false (and it is indeed heap pollution!!!). It will not cause any compatibility problems, though, since you could not write this instanceof in the old code, but it will remove the necessity of having to deal with those unsafe "type token" patterns. With the above facilities at hand you will be able to write 100% safe implementation of “Favorites” class.

Concerning compiler warnings today: I've _never_ heard of _anyone_ that had common problems with ClassCastExceptions. The gymnastics required to avoid compiler warnings with Java 5 generics are ridiculous. The errors are fine, but the static enforcement at the warning level is clearly incompetent. I find that using generics makes my code easier to read _sometimes_ in that I know what kinds of data I'm working with. But the gymnastics sometimes makes that readability go out the window. I just turn off generics compiler warnings all the way in Eclipse. Makes my life a lot happier. And I still don't get ClassCastExceptions but once in a blue moon.

Really, instead of starting with "closures" (aka function pointer made to java), why not simply start with real-life genericity implementation into Java ?

I mean, genericity at this time (read with erasure) is cool but usefullness limited, because appart from simplifying the code, it does not bring any runtime value.

As an example, on a List of type T as you can not get the exact runtime generic (parameter) class, you can not work using genericity to perform predefine operations such as : add a new element of same "minimum" type, get all the properties of the collection to display it, ...

We don't need "closure" by now, and not in a way that will break the object oriented programming (current proposition).

But we do need right now runtime genericity to bring next generation of framework that will automatically operate with data according to usual ways. Genericity to generify the code, in other words !

The type safety given by compiler checking is fine, but at runtime I feel we have taken a step backwards It is quite possible to have a List<String> hold something other than Strings if there are raw types used anywhere - and as a developer of a single module how am I guaranteed that other modules or third party libraries do the right thing? Certainly not by runtime checking OR compile time checking (unless you recompile all third party libraries from scratch and observe all the compiler warnings).

I have seen it now many times that, especially when forced to deal with non-generified APIs or when having to pull generic types out of a non-typesafe map (e.g. servlet attributes), developers simply use @SuppressWarnings due to frustration in not being able to check whether their collections match the appropriate type which ends up destroying the whole point of generics.

About Me

Neal Gafter works for Microsoft on the evolution of the .NET platform languages.
He also has been known to Kibbitz on the evolution of the Java language.
Neal was granted an OpenJDK Community Innovators' Challenge award for his design and
implementation of lambda expressions for Java.
He was previously a software engineer at Google working on Google Calendar, and a senior staff engineer at Sun Microsystems, where he co-designed and implemented the Java language features in releases 1.4 through 5.0. Neal is coauthor of "Java Puzzlers: Traps, Pitfalls, and Corner Cases" (Addison Wesley, 2005). He was a member of the C++ Standards Committee and led the development of C and C++ compilers at Sun Microsystems, Microtec Research, and Texas Instruments. He holds a Ph.D. in computer science from the University of Rochester.