The Source for Java Technology Collaboration
User: Password:



Rémi Forax's Blog

May 2008 Archives


Parameterized type are NOT inherently unsafe

Posted by forax on May 27, 2008 at 06:17 AM | Permalink | Comments (6)

Time to time, i heard that sentence "array of generics a inherently unsafe" or a variation.
That is not true and i would like to explain why and even better propose a way to improve the user experience.

Array of generics ?

First, there are two kinds of "generics", type variable and parameterized type, and they behave differently.

An example of array of type variable:

  class A<T> { // T is a type variable
    T[] m() {        // T[] is an array of type variable
      ... 
    }
  }
  ...
  A<String> a =new A<String>();

Here, T is a type variable. A>String> is a parameterized type, it's an instantiation of A<T> with T = String.

As Gilad Bracha in the generics tutorial wrote "The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type",
currently the specification doesn't allow to create an array of type varaible and array of parameterized type.

Why ?

Let's take the example given by Gilad:

  List<String>[] lsa = new List<String>[10]; // not really allowed
  Object o = lsa;
  Object[] oa = (Object[]) o;
  List<Integer> li = new ArrayList<Integer>();
  li.add(new Integer(3));
  oa[1] = li;                     // problem here
  String s = lsa[1].get(0);

Generics in Java are not reified, i.e. at runtime there is no difference between a List<String> and a List<Integer>, they are all List. So the VM can perform runtime check to distinguish between the two parameterized type.
Just before the bytecode generation, in a pass named erasure, the compiler transform all parameterized types to types without type arguments and replace type variables by their first bound.

So our example is erased to:

  List[] lsa = new List[10]; // not really allowed
  Object o = lsa;
  Object[] oa = (Object[]) o;
  List li = new ArrayList();
  li.add(new Integer(3));
  oa[1] = li; // problem but not detected here
  String s = lsa[1].get(0); // run-time error - ClassCastException

So when VM execute the line oa[1] = li; it doesn't detect a problem. The VM generate a ClassCastException later here at the next line.

To avoid to have a code that can generate a CCE without any warning, the JSR15 experts decide that array of parameterized type can not be created.

Here comes the pain

Because you often need to create an array of parametrized type, you have to use painful workaround.
In a recent interview about Effective Java, Joshua Bloch talk about "The "impedance mismatch" between arrays and generics can be a pain."

An example of such tricks, the creation of an array of parametrized type is replaced by the creation of an array of wildcard then a cast.

  List<String>[] list =
      (List<String>[])new ArrayList<?>[1]; // warning

These workaround codes are very dangerous because if one day in the future generics are reified (JDK8 :), these codes will not work anymore and throw a ClassCastException.

So what ?

So array creation is prohibited because it can lead to unsafe code. I think it's time to say:
Sorry we goof, we can do better.
Instead of preventing to create array of parametrized type we can just generate a warning where needed.
What is the problem in our running example ? The line oa[1] = li; is unsafe because a List of Integer is store in an Object.

In general terms, the problem is that when converting an array of parameterized type to a type without type argument, the type is lost (by definition) so neither the compiler nor the VM (because of the erasure) can garantee the type safety.

Unsafe convertion

Using a JLS like wording, let T and U such as U is the declared type of u and T is defined by, T t=u;
If U is an array of parametrized type, the compiler may emit a warning, if :

  • T is Object, Cloneable, Serializable or
  • T is an array of V which is not parametrized or
  • T is an array of raw type or
  • T is an array of unbunded wilcard.

If this rule is added, creation of array of parameterized type is safe and then can be allowed.

And what about the backward compatibility ?

With this new rule, program that compile will continue to compile. Workarounds are not needed any more. But some code that currently don't raise a warning can now raise a warning.
There are in my opinion two different cases:

First, the code is really unsafe so it had to generate a warning. By example, the attachment of a java.nio.channels.SelectionKey is typed Object.

  SelectionKey key = ...
  List<String> list = ...
  
  key.attach(list); // will generate a new warning

It's not a big deal because the following code already generate a warning.

 List<String> list = 
    (List<String>)key.attachment(); // already generate a warning

The second case is more problematic

  List<String> list = ...
  System.out.println(list);   // warning

Here, println take an Object, so the parameterized list is assigned to an Object. Hum, such warning is less desirable.
Here, the compiler doesn't know that the only things done to that object is to call toString() on it so it's safe.
To avoid such warning, i propose to add a new key to @SuppressWarnings, @SuppressWarnings("parameterized-type") in order to indicate that assign/convert a parameterized type is safe in that case.

  public void println(@SuppressWarnings("parameterized-type") Object x) {

I wish and hope, these changes can be included in JDK7.

Is array of type variable inherently unsafe ?

Yes, array of type variable are inherently unsafe. See the following code.

  class A<T> {
    T[] m() {
      return new T[1]; // erasure create a, array of Object 
    }
  }
  ...
  A<String> a = new A<String>();
  String[] s = a.m(); // oups

cheers,
Rémi



Javac + invokedynamic

Posted by forax on May 22, 2008 at 06:37 AM | Permalink | Comments (0)

Just for fun, this morning, i've patched the java compiler to be able to generate classes that use invokedynamic instead of invokevirtual/invokeinterface when invoking a method. following the JSR292 EDR

The patch is based on the source of the langtools repository of the hotspot project, so to apply the patch, first clone the repository

  hg clone http://hg.openjdk.java.net/jdk7/hotspot/langtools/
 
An then apply the following patch invokedynamic.patch then run ant in langtools/make.
Now, you have a patched javac that will insert an invokedynamic instead of an invokevirtual when calling a method tagged by @InvokeDynamic.

import java.dyn.InvokeDynamic;

public class Test {
  @InvokeDynamic
  public void m(String message) {
    System.out.println(message);
  }
  
  public static void main(String[] args) {
    // here m is invoked dynamically
    new Test().m("hello invoke dynamic");
  }
}

And now, how to run it.
Hum, good question, i'm afraid we have to wait the RI implementation of JSR 292 :)
Or perhaps an enhanced version of the IKVM Proof of Concept.

Cheers,
Rémi





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds