views:

12178

answers:

6

Due to the implementation of Java Generics you can't have code like this. How can I implement this while maintaining type safety?

public class GenSet<E> {
    private E a[];
    public GenSet()
    {
        a = new E[INITIAL_ARRAY_LENGTH];
    }
}

I saw a solution on the java forums that goes like this:

import java.lang.reflect.Array;

class Stack<T> {
  public Stack(Class<T> clazz,int capacity) {
     array=(T[])Array.newInstance(clazz,capacity);
  }

  private final T[] array;
}

But I really don't get what's going on. Can anyone help? Thanks in advance.

+4  A: 

The example is using Java reflection to create an array. Doing this is generally not recommended, since it isn't typesafe. Instead, what you should do is just use an internal List, and avoid the array at all.

Ola Bini
The second example (using Array.newInstance()) *is* in fact typesafe. This is possible because the type T of the Class object needs to match the T of the array. It basically forces you to provide the information that the Java runtime discards for generics.
Joachim Sauer
+1  A: 

You could create an Object array and cast it to E everywhere. Yeah, it's not very clean way to do it but it should at least work.

Esko
+2  A: 

This is covered in Chapter 5 (Generics) of Effective Java, 2nd Edition, item 25...Prefer lists to arrays

Your code will work, although it will generate an unchecked warning (which you could suppress with the following annotation:

@SuppressWarnings({"unchecked"})

However, it would probably be better to use a List instead of an Array.

There's an interesting discussion of this bug/feature on Sun's site.

Jeff Olson
+9  A: 

I have to ask a question in return: is your GenSet "checked" or "unchecked"? What does that mean?

  • checked: strong typing. GenSet knows explicitly what type of objects it contains (i.e. its constructor was explicitly called with a Class argument, and methods will throw an exception when they are passed arguments that are not of type E. See http://java.sun.com/javase/6/docs/api/java/util/Collections.html#checkedCollection(java.util.Collection, java.lang.Class)

    -> in that case, you should write

    private E[] a
    
    
    @SuppressWarnings({"unchecked"})
    public GenSet(Class<E> c, int s) {
        // Use Array native method to create arra of a type only known at run time
        a = (E[]) Array.newInstance(c,s);
    }
    
    
    E get(int i) {
        return a[i];
    }
    
  • unchecked: weak typing. No type checking is actually done on any of the objects passed as argument.

    -> in that case, you should write

    private Object[] a
    
    
    public GenSet(int s) {
        a = new Object[s];
    }
    
    
    @SuppressWarnings({"unchecked"})
    E get(int i) {
        return (E) a[i];
    }
    

All of this results from a known, and deliberate, weakness of generics in Java: it was implemented using erasure, so "generic" classes don't know what type argument they were created with at run time, and therefore can not provide type-safety unless some explicit mechanism (type-checking) is implemented.

Varkhan
I suggest to move the @SuppressWarnings to the assignment.
Aaron Digulla
+1  A: 

Java generics work by checking types at compile time and inserting appropriate casts, but erasing the types in the compiled files. This makes generic libraries usable by code which doesn't understand generics (which was a deliberate design decision) but which means you can't normally find out what the type is at run time.

The public Stack(Class<T> clazz,int capacity) constructor requires you to pass a Class object at run time, which means class information is available at runtime to code that needs it. And the Class<T> form means that the compiler will check that the Class object you pass is precisely the Class object for type T. Not a subclass of T, not a superclass of T, but precisely T.

This then means that you can create an array object of the appropriate type in your constructor, which means that the type of the objects you store in your collection will have their types checked at the point they are added to the collection.

Bill Michell
+2  A: 

A quick test confirms you can do this also:

E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH];

I don't know why no one suggested this previously. Am I missing something here?

My test:

public class ArrTest<E> {
  public static void main(String[] args){
    ArrTest<String> t = new ArrTest<String>();
    t.test("Hello World");
  }

  public void test(E a){
    E[] b = (E[])new Object[1];
    b[0] = a;
    System.out.println(b[0]);
  }
}

No warnings, no type errors, no need to cast the array repeatedly. The reflection Array class you mention also works just fine, but I thought I'd throw this out since you, and likely many other people, were confused by it.

dimo414
Too bad nobody's commented on this. This is what I do too. I'd love to know if it's wrong or dangerous somehow.
Rich
This will not work if the array is treated as a typed array of any kind, such as `String[] s=b;` in the above `test()` method. That's because the array of E isn't really, it's Object[]. This matters if you want, e.g. a `List<String>[]` - you can't use an `Object[]` for that, you must have a `List[]` specifically. Which is why you need to use the reflected Class<?> array creation.
Software Monkey