July 2008


The biggest difference between the GNU Classpath and Sun OpenJDK VM interfaces is the point at which control shifts from the library to the virtual machine. Both solutions do provide separation between the library and the VM. Contrary to what may be initially assumed, this is true of OpenJDK even though both HotSpot and the JDK are maintained in the same location. This is what allows different versions of HotSpot to be swapped in, as mentioned in the OpenJDK trademark license. That said, there are likely to be closer ties between the JDK within OpenJDK and HotSpot than there are between GNU Classpath and any of its VMs, simply because of the number of and variance between the latter.

OpenJDK’s VM interface is entirely C-based. The class library calls into the VM using a number of functions with the prefix ‘JVM_’ that are listed in src/share/javavm/export/jvm.h. Implementations of these functions can be found both in HotSpot’s src/share/vm/prims/jvm.cpp and CACAO’s src/native/vm/openjdk/jvm.c. These are dynamically linked in at runtime for a variety of dynamic libraries held in jre/lib/${arch} where ${arch} is the architecture in use such as amd64. The output below shows their use in the recent b31 drop:

Checking build/linux-amd64/j2sdk-image/jre/lib/amd64/libjava.so...
                 U JVM_ActiveProcessorCount@@SUNWprivate_1.1
                 U JVM_ArrayCopy@@SUNWprivate_1.1
                 U JVM_AssertionStatusDirectives@@SUNWprivate_1.1
                 U JVM_Available@@SUNWprivate_1.1
                 U JVM_ClassDepth@@SUNWprivate_1.1
                 U JVM_ClassLoaderDepth@@SUNWprivate_1.1
                 U JVM_Clone@@SUNWprivate_1.1
                 U JVM_Close@@SUNWprivate_1.1
                 U JVM_CompileClass@@SUNWprivate_1.1
                 U JVM_CompileClasses@@SUNWprivate_1.1
                 U JVM_CompilerCommand@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetClassAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetClassAtIfLoaded@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetDoubleAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetFieldAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetFieldAtIfLoaded@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetFloatAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetIntAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetLongAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetMemberRefInfoAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetMethodAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetMethodAtIfLoaded@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetSize@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetStringAt@@SUNWprivate_1.1
                 U JVM_ConstantPoolGetUTF8At@@SUNWprivate_1.1
                 U JVM_CountStackFrames@@SUNWprivate_1.1
                 U JVM_CurrentClassLoader@@SUNWprivate_1.1
                 U JVM_CurrentLoadedClass@@SUNWprivate_1.1
                 U JVM_CurrentThread@@SUNWprivate_1.1
                 U JVM_CurrentTimeMillis@@SUNWprivate_1.1
                 U JVM_DefineClassWithSource@@SUNWprivate_1.1
                 U JVM_DesiredAssertionStatus@@SUNWprivate_1.1
                 U JVM_DisableCompiler@@SUNWprivate_1.1
                 U JVM_DoPrivileged@@SUNWprivate_1.1
                 U JVM_DumpThreads@@SUNWprivate_1.1
                 U JVM_EnableCompiler@@SUNWprivate_1.1
                 U JVM_FillInStackTrace@@SUNWprivate_1.1
                 U JVM_FindClassFromClassLoader@@SUNWprivate_1.1
                 U JVM_FindLibraryEntry@@SUNWprivate_1.1
                 U JVM_FindLoadedClass@@SUNWprivate_1.1
                 U JVM_FindPrimitiveClass@@SUNWprivate_1.1
                 U JVM_FindSignal@@SUNWprivate_1.1
                 U JVM_FreeMemory@@SUNWprivate_1.1
                 U JVM_GC@@SUNWprivate_1.1
                 U JVM_GetAllThreads@@SUNWprivate_1.1
                 U JVM_GetArrayElement@@SUNWprivate_1.1
                 U JVM_GetArrayLength@@SUNWprivate_1.1
                 U JVM_GetCallerClass@@SUNWprivate_1.1
                 U JVM_GetClassAccessFlags@@SUNWprivate_1.1
                 U JVM_GetClassAnnotations@@SUNWprivate_1.1
                 U JVM_GetClassConstantPool@@SUNWprivate_1.1
                 U JVM_GetClassContext@@SUNWprivate_1.1
                 U JVM_GetClassDeclaredConstructors@@SUNWprivate_1.1
                 U JVM_GetClassDeclaredFields@@SUNWprivate_1.1
                 U JVM_GetClassDeclaredMethods@@SUNWprivate_1.1
                 U JVM_GetClassInterfaces@@SUNWprivate_1.1
                 U JVM_GetClassLoader@@SUNWprivate_1.1
                 U JVM_GetClassModifiers@@SUNWprivate_1.1
                 U JVM_GetClassName@@SUNWprivate_1.1
                 U JVM_GetClassSignature@@SUNWprivate_1.1
                 U JVM_GetClassSigners@@SUNWprivate_1.1
                 U JVM_GetComponentType@@SUNWprivate_1.1
                 U JVM_GetDeclaredClasses@@SUNWprivate_1.1
                 U JVM_GetDeclaringClass@@SUNWprivate_1.1
                 U JVM_GetEnclosingMethodInfo@@SUNWprivate_1.1
                 U JVM_GetInheritedAccessControlContext@@SUNWprivate_1.1
                 U JVM_GetInterfaceVersion@@SUNWprivate_1.1
                 U JVM_GetLastErrorString@@SUNWprivate_1.1
                 U JVM_GetPrimitiveArrayElement@@SUNWprivate_1.1
                 U JVM_GetProtectionDomain@@SUNWprivate_1.1
                 U JVM_GetStackAccessControlContext@@SUNWprivate_1.1
                 U JVM_GetStackTraceDepth@@SUNWprivate_1.1
                 U JVM_GetStackTraceElement@@SUNWprivate_1.1
                 U JVM_GetSystemPackage@@SUNWprivate_1.1
                 U JVM_GetSystemPackages@@SUNWprivate_1.1
                 U JVM_Halt@@SUNWprivate_1.1
                 U JVM_HoldsLock@@SUNWprivate_1.1
                 U JVM_IHashCode@@SUNWprivate_1.1
                 U JVM_InitProperties@@SUNWprivate_1.1
                 U JVM_InternString@@SUNWprivate_1.1
                 U JVM_Interrupt@@SUNWprivate_1.1
                 U JVM_InvokeMethod@@SUNWprivate_1.1
                 U JVM_IsArrayClass@@SUNWprivate_1.1
                 U JVM_IsInterface@@SUNWprivate_1.1
                 U JVM_IsInterrupted@@SUNWprivate_1.1
                 U JVM_IsNaN@@SUNWprivate_1.1
                 U JVM_IsPrimitiveClass@@SUNWprivate_1.1
                 U JVM_IsSupportedJNIVersion@@SUNWprivate_1.1
                 U JVM_IsThreadAlive@@SUNWprivate_1.1
                 U JVM_LatestUserDefinedLoader@@SUNWprivate_1.1
                 U JVM_LoadLibrary@@SUNWprivate_1.1
                 U JVM_Lseek@@SUNWprivate_1.1
                 U JVM_MaxMemory@@SUNWprivate_1.1
                 U JVM_MaxObjectInspectionAge@@SUNWprivate_1.1
                 U JVM_MonitorNotify@@SUNWprivate_1.1
                 U JVM_MonitorNotifyAll@@SUNWprivate_1.1
                 U JVM_MonitorWait@@SUNWprivate_1.1
                 U JVM_NanoTime@@SUNWprivate_1.1
                 U JVM_NativePath@@SUNWprivate_1.1
                 U JVM_NewArray@@SUNWprivate_1.1
                 U JVM_NewInstanceFromConstructor@@SUNWprivate_1.1
                 U JVM_NewMultiArray@@SUNWprivate_1.1
                 U JVM_Open@@SUNWprivate_1.1
                 U JVM_RaiseSignal@@SUNWprivate_1.1
                 U JVM_Read@@SUNWprivate_1.1
                 U JVM_RegisterSignal@@SUNWprivate_1.1
                 U JVM_ResolveClass@@SUNWprivate_1.1
                 U JVM_ResumeThread@@SUNWprivate_1.1
                 U JVM_SetArrayElement@@SUNWprivate_1.1
                 U JVM_SetClassSigners@@SUNWprivate_1.1
                 U JVM_SetLength@@SUNWprivate_1.1
                 U JVM_SetPrimitiveArrayElement@@SUNWprivate_1.1
                 U JVM_SetProtectionDomain@@SUNWprivate_1.1
                 U JVM_SetThreadPriority@@SUNWprivate_1.1
                 U JVM_Sleep@@SUNWprivate_1.1
                 U JVM_StartThread@@SUNWprivate_1.1
                 U JVM_StopThread@@SUNWprivate_1.1
                 U JVM_SupportsCX8@@SUNWprivate_1.1
                 U JVM_SuspendThread@@SUNWprivate_1.1
                 U JVM_Sync@@SUNWprivate_1.1
                 U JVM_TotalMemory@@SUNWprivate_1.1
                 U JVM_TraceInstructions@@SUNWprivate_1.1
                 U JVM_TraceMethodCalls@@SUNWprivate_1.1
                 U JVM_UnloadLibrary@@SUNWprivate_1.1
                 U JVM_Write@@SUNWprivate_1.1
                 U JVM_Yield@@SUNWprivate_1.1
Checking build/linux-amd64/j2sdk-image/jre/lib/amd64/libmanagement.so...
                 U JVM_ActiveProcessorCount@@SUNWprivate_1.1
                 U JVM_GetAllThreads@@SUNWprivate_1.1
                 U JVM_GetManagement@@SUNWprivate_1.1
Checking build/linux-amd64/j2sdk-image/jre/lib/amd64/libnet.so...
                 U JVM_Connect@@SUNWprivate_1.1
                 U JVM_CurrentTimeMillis@@SUNWprivate_1.1
                 U JVM_FindLibraryEntry@@SUNWprivate_1.1
                 U JVM_GetHostName@@SUNWprivate_1.1
                 U JVM_GetSockName@@SUNWprivate_1.1
                 U JVM_GetSockOpt@@SUNWprivate_1.1
                 U JVM_InitializeSocketLibrary@@SUNWprivate_1.1
                 U JVM_Listen@@SUNWprivate_1.1
                 U JVM_Send@@SUNWprivate_1.1
                 U JVM_SetSockOpt@@SUNWprivate_1.1
                 U JVM_Socket@@SUNWprivate_1.1
                 U JVM_SocketAvailable@@SUNWprivate_1.1
                 U JVM_SocketClose@@SUNWprivate_1.1
                 U JVM_SocketShutdown@@SUNWprivate_1.1
Checking build/linux-amd64/j2sdk-image/jre/lib/amd64/librmi.so...
                 U JVM_LatestUserDefinedLoader@@SUNWprivate_1.1
Checking build/linux-amd64/j2sdk-image/jre/lib/amd64/libverify.so...
                 U JVM_FindClassFromClass@@SUNWprivate_1.1
                 U JVM_GetCPClassNameUTF@@SUNWprivate_1.1
                 U JVM_GetCPFieldClassNameUTF@@SUNWprivate_1.1
                 U JVM_GetCPFieldModifiers@@SUNWprivate_1.1
                 U JVM_GetCPFieldSignatureUTF@@SUNWprivate_1.1
                 U JVM_GetCPMethodClassNameUTF@@SUNWprivate_1.1
                 U JVM_GetCPMethodModifiers@@SUNWprivate_1.1
                 U JVM_GetCPMethodNameUTF@@SUNWprivate_1.1
                 U JVM_GetCPMethodSignatureUTF@@SUNWprivate_1.1
                 U JVM_GetClassCPEntriesCount@@SUNWprivate_1.1
                 U JVM_GetClassCPTypes@@SUNWprivate_1.1
                 U JVM_GetClassFieldsCount@@SUNWprivate_1.1
                 U JVM_GetClassMethodsCount@@SUNWprivate_1.1
                 U JVM_GetClassNameUTF@@SUNWprivate_1.1
                 U JVM_GetFieldIxModifiers@@SUNWprivate_1.1
                 U JVM_GetMethodIxArgsSize@@SUNWprivate_1.1
                 U JVM_GetMethodIxByteCode@@SUNWprivate_1.1
                 U JVM_GetMethodIxByteCodeLength@@SUNWprivate_1.1
                 U JVM_GetMethodIxExceptionIndexes@@SUNWprivate_1.1
                 U JVM_GetMethodIxExceptionTableEntry@@SUNWprivate_1.1
                 U JVM_GetMethodIxExceptionTableLength@@SUNWprivate_1.1
                 U JVM_GetMethodIxExceptionsCount@@SUNWprivate_1.1
                 U JVM_GetMethodIxLocalsCount@@SUNWprivate_1.1
                 U JVM_GetMethodIxMaxStack@@SUNWprivate_1.1
                 U JVM_GetMethodIxModifiers@@SUNWprivate_1.1
                 U JVM_GetMethodIxNameUTF@@SUNWprivate_1.1
                 U JVM_GetMethodIxSignatureUTF@@SUNWprivate_1.1
                 U JVM_IsConstructorIx@@SUNWprivate_1.1
                 U JVM_IsInterface@@SUNWprivate_1.1
                 U JVM_IsSameClassPackage@@SUNWprivate_1.1
                 U JVM_ReleaseUTF@@SUNWprivate_1.1
Checking build/linux-amd64/j2sdk-image/jre/lib/amd64/libzip.so...
                 U JVM_Close@@SUNWprivate_1.1
                 U JVM_GetLastErrorString@@SUNWprivate_1.1
                 U JVM_Lseek@@SUNWprivate_1.1
                 U JVM_NativePath@@SUNWprivate_1.1
                 U JVM_Open@@SUNWprivate_1.1
                 U JVM_RawMonitorCreate@@SUNWprivate_1.1
                 U JVM_RawMonitorDestroy@@SUNWprivate_1.1
                 U JVM_RawMonitorEnter@@SUNWprivate_1.1
                 U JVM_RawMonitorExit@@SUNWprivate_1.1

Clearly most of these are found in libjava.so, which is the library that contains the native code for the core classes like those in java.lang. What’s interesting about libjava is that, unlike for example libnet, it isn’t loaded via a call to LoadLibrary in the class library code. Rather, it is expected that the VM will know about and load this. In CACAO, a special OpenJDK case is provided in src/native/vm/nativevm.c to handle this. This needs to happen early in the VM initialisation process before any of the native calls in the core classes are used.

In contrast, GNU Classpath interacts with the VM while still at the Java level. Each call which may need to go to the VM is first handed off to a package-private VM class, for example vm/reference/java/lang/VMObject.java provides methods like wait() and notify(). The reference version of these classes tend to do what the OpenJDK classes do in the original classes like java.lang.Object; define the methods as native. However, a VM can replace these VM* classes as needed. Some are mandatory, as it isn’t possible to provide a generic reference implementation in the class library. In both cases, missing implementations are visible through linking errors at runtime.

To further clarify the difference, let’s trace the path of one such function, java.lang.Object#wait(). In GNU Classpath, java.lang.Object defines it as:

  public final void wait()
    throws IllegalMonitorStateException, InterruptedException
  {
    VMObject.wait(this, 0, 0);
  }

in java/lang/Object.java. In the reference version of VMObject.java, we find:

  static native void wait(Object o, long ms, int ns)
    throws IllegalMonitorStateException, InterruptedException;

In some cases, there is also a reference native implementation under native/jni/${package name}/${package_name}_${class_name}.c (native/jni/java-lang/java_lang_VMObject.c in this case), but this isn’t the case here. Instead, we look to CACAO and find this in src/native/vm/gnuclasspath/java_lang_VMObject.cpp:

JNIEXPORT void JNICALL Java_java_lang_VMObject_wait(JNIEnv *env, jclass clazz, java_lang_Object *o, int64_t ms, int32_t ns)

How about for OpenJDK? Again, we start in java.lang.Object and find this in src/share/classes/java/lang/Object.java:

public final native void wait(long timeout) throws InterruptedException;

Unlike with GNU Classpath, the java.lang.Object code goes straight to the native implementation found in src/share/native/java/lang/Object.c. However, we don’t find an implementation there. Instead, we need to look at another function in Object.c called registerNatives:

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

The array, methods, tells us that wait maps to JVM_MonitorWait. Looking at the list above, we see that JVM_MonitorWait is one of the VM symbols in the java library. And sure enough, we find it in src/share/javavm/export/jvm.h:

JNIEXPORT void JNICALL
JVM_MonitorWait(JNIEnv *env, jobject obj, jlong ms);

and a corresponding implementation in HotSpot’s src/share/vm/prims/jvm.cpp

JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))

and CACAO’s jvm.c:

void JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms)

Clearly, there isn’t that much difference between the two. The main issue is finding the right functions and where and how they are called. One other major difference is that Classpath VMs provide their own launchers (you run ‘cacao’, ‘jamvm’, ‘gij’, ‘kaffe’ etc. binaries) while the ‘java’ in OpenJDK is a standard launcher (also used for other tools) which invokes the VM via JNI and libjvm.so. There’s also a mechanism for selecting VM in this manner through src/(solaris|windows)/bin/${arch}/jvm.cfg, which we’ll look at later.

With the CVMI project, we aim to document these issues and also experiment with providing an OpenJDK interface more at the Java level. Comments and suggestions welcome.

I’ve been meaning to write this blog for a while, but other things have cropped up in the meantime. The topic is something that came up when trying to lower the number of warnings produced by a build of GNU Classpath.

While GNU Classpath has required a 1.5 capable compiler since 0.95, so we could implement things like java.lang.Enum, the use of generics and such has only largely being applied to the creation of new classes (like java.util.ServiceLoader) and the suppression of JAPI differences. The internal code, such as that in the gnu.* packages and the private variables within the java.* and javax.* classes has remained in 1.4 form. As a result, the compiler generates a lot of ‘unchecked’ warnings, mainly due to the use of ‘raw’ collections (I’ll explain what these are shortly). ecj is still our preferred compiler (being the most tested and the one we had available as Free Software first) and it generates far more warnings by default than Sun’s javac. At present count, Classpath CVS generates just over 10,000 warnings with ecj 3.3. We clearly need to cut this down so we can spot real problems. In the past, we’ve simply turned them off but it’s better for the codebase in general if these are properly cleaned up and in some cases it does cause bugs to be discovered.

So what’s the problem? Well, mainly it’s a case of most of the GNU Classpath code still looking like:

List list = new ArrayList();
list.add("Potato");

or:

Map map = new HashMap();
map.put("key", new Value());

Map, List, ArrayList and HashMap are now referred to as raw types because the versions in Java 1.5 and above have one or more type parameters. These type parameters can be used to tell us what is stored inside the collection. We should be using Map<K,V>, List<T>, ArrayList<T> and HashMap<K,V>, the parameterized types. The type parameters, (K, V and T in this case) can be used in methods to specify that the type of an argument or return value depends on the type given when the collection is created. Thus, Map<K,V> has:

V put(K key, V value)

as opposed to:

Object put(Object key, Object value)

K is the type of key used for the map, and V is the value. In our original example, our Map should be replaced with Map<String,Value> because it maps keys of type String to values of type Value. The main advantage of using these is you no longer need to cast elements when they are returned from the collection. So a get call on a List<T> or Map<K,V> returns a value of type T or V respectively, not simply an Object which has to be cast manually by the user. In reality, backwards compatibility means these casts are still being inserted by the compiler, but for it to do this it must have been determined to be safe to do so.

So how do we clean up these warnings? It means going through the code and adding appropriate type parameters to our collections. This isn’t always as easy as it sounds. In some cases, the collection will only be used with one type so it’s simply a matter of determining what that is (usually by looking for the casts when objects are retrieved from the collection — these casts can soon be removed). However, because raw collections take Objects as input, there can be a mix of types so a common supertype has to be found.

In some cases, this has to be Object. So, you may think, what’s the point of turning Map into Map<Object,Object>? Surely they are the same thing. No they’re not, and this is one of the more interesting aspects of collections and one you’ll especially come across when you have to deal with using a raw collection coming from legacy code without generating unchecked warnings. Map the raw type is actually equivalent to Map<?,?>, where ? represents a wildcard. Wildcards allow the use of existential types; instead of having a strict instantiation of a type parameter, we can refer to any type within certain bounds. By default, a wildcard has an upper bound of Object and a lower bound of null i.e. Map<?,?> is the same as Map<? extends Object super null, ? extends Object super null>. What does this say in English? It says that the keys and values of the map are of some type that extends Object but we don’t know exactly what. In contrast, Map<Object,Object> says that the keys and values must be Objects.

We don’t want to work with wildcard types practically because we can’t have variables of type ?. Instead, we use them when importing and exporting from parameterized types. For example, to import objects from a collection, addAll is not defined as:

void addAll(Collection<T> t)

because doing so would mean that we can only take objects from collections of exactly the same type. Instead, we want to also allow collections of some subtype. For example, a collection of Objects should be allowed to be filled from a Collection of Strings. The above signature doesn’t allow this, but:

void addAll(Collection<? extends T> t)

does. This says that the collection from which the elements are to be taken must contain objects of some type which is a subtype of T. A similar example for super is the use of Comparator<? super T>. When we want something that can compare two objects of type T, we can work with both something that can compare two elements of type T but we can also use a more general comparison method that compares some supertype. For example, a Comparator<Object> can be used to compare Strings. In this case, we can’t go the other way; it would be inappropriate to try using a Comparator for Integers to compare general Number instances.

The most confusing aspect of dealing with generics is how to handle legacy code where you can’t make the incoming type a parameterized type. Take the following legacy method:

  public List createFruits()
  {
    List x = new ArrayList();
    x.add("Strawberry");
    x.add("Banana");
    x.add("Pineapple");
    return x;
  }

This returns a raw type, List. Now imagine we can’t see the body of the method. All we know is that the method returns a List; we don’t know what is in that list. This is how the compiler sees the method.

We of course know that it contains String objects. So we try and pass it to a method that takes a List<String>:

  public void printList(List<String> l)
  {
    for (String s : l)
      System.out.println(s);
  }

The obvious solution to do this is:

printList(testFruits())

and this will compile, but it produces an unchecked warning:

warning: [unchecked] unchecked conversion
found   : java.util.List
required: java.util.List<java.lang.String>
    printList(createFruits());

So we take the obvious solution to this from the old 1.4 days and cast it:

printList((List<String>) testFruits())

Again, this compiles but we get a different unchecked warning:

warning: [unchecked] unchecked cast
found   : java.util.List
required: java.util.List<java.lang.String>
    printList((List<String>) createFruits());

So we get a warning with the cast, and one without. What do we do?

The answer is to take a step back and think about what this incoming List really is. As we noted before, the equivalent of List in the new 1.5 world is List<?> so:

List<?> l = (List<?>) testFruits();

No warnings, so far so good. This is deemed a safe cast because we are merely telling the compiler to move from the 1.4 to 1.5 version of the same thing. But how do we change this into a List<String>?

Remember that the ? wildcard without explicit bounds is telling us that the most we know about the contents of the list is that they are some subtype of Object. As such, the only thing we can safely retrieve them as is Objects:

    List<Object> newList = new ArrayList<Object>();
    for (Object o : l)
      newList.add(o);
    printList(newList);

This takes each Object from the list and puts it in a new list which holds Objects. By doing so we have removed the doubt about what is in the collection and telling the compiler to simply treat them all as Objects. What we have now is equivalent to what we thought we had to start with. However, this still won’t work with our printList method:

printList(java.util.List<java.lang.String>) in Test cannot be applied to (java.util.List<java.lang.Object>)
    test.printList(newList);

This is an error so the code will now not even compile. This is good; we don’t want the compiler to allow us to pass collections of mere Objects to methods requiring collections of Strings. This would take us straight back to Java 1.4 days. The solution is to create a List<String> instead of a List<Object> and check that each objects is a String in the body of the loop which adds them to the collection.

    List<String> newList = new ArrayList<String>();
    for (Object o : l)
      if (o instanceof String)
        newList.add((String) o);
    printList(newList);

Finally we have working code which has no warnings, while still using the legacy code. This however can be quite inefficient; in some cases, we want to avoid iterating over the entire collection when we know this is going to happen anyway. A common place where this happens is using the addAll method of collections. The addAll will take each object and cast it in adding it to the list anyway (the retrieval from the producer list will generate such a cast). In these cases, we can use the annotation @SuppressWarnings(“unchecked”) to turn off the warning we know is superfluous.

This should be used with care. It should also cover the minimum area possible to avoid suppressing other warnings. Annotations can go on individual assignments so there is no need to suppress warnings for the entire method. For example, here is Classpath’s getAnnotation method:

  public <T extends Annotation> T getAnnotation(Class>T> annotationClass)
  {
    // Inescapable as the VM layer is 1.4 based.
    @SuppressWarnings("unchecked")
      T ann = (T) cons.getAnnotation(annotationClass);
    return ann;
  }

cons.getAnnotation will return something of type Annotation as our VM layer is strictly 1.4 only. As we know from the input class that the annotation will be of type T, we can forcibly apply a cast and disable the warning. Note that the suppression applies only to the one line, and the explicit assignment of ann is used to allow this (an annotation can’t be attached to a return statement). We also add a comment to explain the reasoning behind adding this annotation. This should be used sparingly and where possible generics should be used properly. In some cases, we don’t even need to convert the collection; retrieving the size can be achieved simply from a List<?> or similar.

My thanks to Joshua Bloch and his ‘Effective Java’ book for finally explaining some of the solutions documented here. I didn’t realise until reading this that annotations could be applied to such a narrow scope or that it was safe to cast to a wildcard type from a raw type. This has enabled me to clean up a lot of the Classpath code.

Congratulations also to the IcedTea team, especially the OpenJDK Debian Team, for getting openjdk-6 into sid!

And finally, congratulations to Mark and Petri on the birth of their son, Jonas :)