# Monday, May 10, 2004
« Assembly Names | Main | Painful Changes »
Generics Again

I have a hard time truly understanding the Java generics model. I understand the implementation and I think it's a hack and this is probably making it harder for me to see the design objectively.

After looking at the recent JDK 1.5 beta, there was one method that particularly puzzled me:

public T cast(Object obj)

In java.lang.Class. Why would you need a method to dynamically cast? If you understand how generics work under the covers, you know that T is actually java.lang.Object, so calling this cast method doesn't actually buy you anything (the compiler will insert a real cast after your call).

Before diving in, if your knowledge of how Java generics work under the covers is a bit rusty, I recommend reading the paper on GJ: Making the future safe for the past: Adding Genericity to the Java Programming Language. While I was re-reading it, a few things jumped out: "Adding generics to an existing language is almost routine.." In fact, it's so easy that today in 2004 we still don't have generics in Java, even though the design was already done in 1998. Another quote: "GJ comes with a cast-iron guarantee: no cast inserted by the compiler will ever fail. (Caveat: this guarantee is void if the compiler generates an 'unchecked' warning, which may occur if legacy and parametric code is mixed without benefit of retrofitting.)" Please note that the caveat applies when you use one of the principal features of GJ: "GJ contains a novel language feature, raw types, to capture the correspondence between generic and legacy types, and a retrofitting mechanism to allow generic types to be imposed on legacy code."

Another interesting paper (that is referenced in the GJ paper as NextGen), is Compatible Genericity with Run-time Types for the Java TM Programming Language.

Anyway, let's look at some code and try to figure out what the purpose of the new cast method is:

Class<String> c = String.class;;
String s = c.cast("foo");;

This is compiled as:

ldc_w java/lang/String
ldc "foo"
invokevirtual java/lang/Class cast(Ljava/lang/Object;)Ljava/lang/Object;
checkcast java/lang/String

Note in particular the checkcast that I highlighted. This is inserted by the compiler and is key to how Java generics work. Presumably the Class.cast() method also checks that the passed in object is actually castable to the type, so effectively you get two casts for the price of one (or rather, you pay twice for the same cast).

Why would you want (or need) that? The answer became clear (?) to me when I was contrasting the Java generics with the .NET generics model.

Let's look at some more code (C# 2.0 this time):

T LameFactory<T>() where T : new()
  return new T();

This method generically constructs instances. It's not relevant to my point, but it is interesting to note that the C# compiler uses reflection under the covers to implement this. Most C# generics constructs are actually supported by the CLR, but instantation isn't. The compiler generates something like this:

T LameFactory<T>()
  // If T is a Value Type, we don't need
  // to use Activator.CreateInstance.
  if((object)T.default != null)
    return T.default;
  return (T)Activator.CreateInstance(typeof(T));

How would you do something similar in Java? Here's how:

T LameFactory(Class<T> factory)
  return factory.newInstance();

In essence, what you're doing here is the same as what the CLR is doing under the covers (passing an extra parameter with the type information, at least conceptually). Class.newInstance returns an appropriately typed reference, because Class is a generic type. Suppose it hadn't returned the appropriate type, but simply Object like in the good old days. You could have used Class.cast() to the the downcast instead! Admittedly this isn't the greatest example for explaining the existence of Class.cast(), but I do understand now that it provides real functionality that would have been impossible to (safely) get in any order way. Note that the obvious:

T LameFactory(Class factory)
  Object o = factory.newInstance();
  return (T)o;

Isn't the right answer. When compiling this, the compiler rightfully warns: Note: cast.java uses unchecked or unsafe operations. The cast is unsafe, because it dissolves at compile time. Note that this doesn't break type safety (in the VM/security sense), because the caller of LameFactory will have inserted its own cast to T, but it does (allow you to) break compile time type safety. If you value compile time type safety, it's a good idea to stay away from code that generates this warning.

Monday, May 10, 2004 12:07:15 PM (W. Europe Daylight Time, UTC+02:00)  #    Comments [4]
Sunday, May 16, 2004 10:15:45 AM (W. Europe Daylight Time, UTC+02:00)
After so long living without generics in Java I wondered if was such a good idea. JDK1.5 is still in beta and it's already split most of the developer community down the middle.

For me I won't be using it, I've got a telephone book worth of code that casts in the normal way:

for(Iterator i = thing.iterator(); i.hasNext();) {
String mystring = (String)i.next();

Obviously a pain if your collection has mixed objects then you have to check the object type with instanceOf.

Mono or Java, generics will not be bothering me until at least the back end of 2005.

Tuesday, May 25, 2004 6:41:21 PM (W. Europe Daylight Time, UTC+02:00)
FYI, it looks as if wildcard handling in ikvmc may have regressed - a batch file that worked with an older version

CLR version: 1.1.4322.573
mscorlib: 1.0.5000.0
ikvm: 1.0.1418.28869
ik.vm.net: 1.0.1418.28792

doesn't work with the newest version (on a different machine):

CLR version: 1.1.4322.573
mscorlib: 1.0.5000.0
ikvm: 1.0.1586.15362
ik.vm.net: 1.0.1586.15287

It fails with an ArgumentException which I'll post the full stack trace to below.
Tuesday, May 25, 2004 6:43:02 PM (W. Europe Daylight Time, UTC+02:00)
C:\devprojects\nrdo\bin>..\..\ikvm\bin\ikvmc -reference:classpath.dll -out:nrdo.
exe -target:exe -main:net.netreach.nrdo.tools.NRDOTool ..\classes\net\netreach\n
rdo\tools\*.class ..\classes\net\netreach\util\misc\*.class ..\classes\net\netre
ach\smelt\*.class ..\classes\net\netreach\cgl\*.class ..\classes\gnu\text\*.clas

Unhandled Exception: System.ArgumentException: Illegal characters in path.
at System.Security.Permissions.FileIOPermission.HasIllegalCharacters(String[]
at System.Security.Permissions.FileIOPermission.AddPathList(FileIOPermissionA
ccess access, String[] pathListOrig, Boolean checkForDuplicates, Boolean needFul
lPath, Boolean copyPathList)
at System.Security.Permissions.FileIOPermission..ctor(FileIOPermissionAccess
access, String[] pathList, Boolean checkForDuplicates, Boolean needFullPath)
at System.IO.FileInfo..ctor(String fileName)
at Compiler.Main(String[] args)
Wednesday, May 26, 2004 9:14:03 AM (W. Europe Daylight Time, UTC+02:00)
Thanks. I fixed it. It tried to use the file name part of the first non-option argument as the default assembly name, but FileInfo doesn't like wildcards.

You should be able to work around it by putting all your classes in a zip and then process that with ikvmc, or by making sure the first class you specify doesn't have a wildcard (it's legal to specify the same class twice, so you could just add any one of your classes as the first argument).
Home page

I apologize for the lameness of this, but the comment spam was driving me nuts. In order to be able to post a comment, you need to answer a simple question. Hopefully this question is easy enough not to annoy serious commenters, but hard enough to keep the spammers away.

Anti-Spam Question: What method on java.lang.System returns an object's original hashcode (i.e. the one that would be returned by java.lang.Object.hashCode() if it wasn't overridden)? (case is significant)

Comment (HTML not allowed)  

Live Comment Preview