tags:

views:

899

answers:

5

I have a class that maps incoming messages to matching readers based on the message's class. All message types implement the interface message. A reader registers at the mapper class, stating which message types it will be able to handle. This information needs to be stored in the message reader in some way and my approach was to set a private final array from the constructor.

Now, it seems I have some misunderstanding about generics and / or arrays, that I can't seem to figure out, see the code below. What is it?

public class HttpGetMessageReader implements IMessageReader {
    // gives a warning because the type parameter is missing
    // also, I actually want to be more restrictive than that
    // 
    // private final Class[] _rgAccepted;

    // works here, but see below
    private final Class<? extends IMessage>[] _rgAccepted;

    public HttpGetMessageReader()
    {
        // works here, but see above
        // this._rgAccepted = new Class[1];

        // gives the error "Can't create a generic array of Class<? extends IMessage>"
        this._rgAccepted = new Class<? extends IMessage>[1];

        this._rgAccepted[0] = HttpGetMessage.class;
    }
}

ETA: As cletus correctly pointed out, the most basic googling shows that Java does not permit generic arrays. I definitely understand this for the examples given (like E[] arr = new E[8], where E is a type parameter of the surrounding class). But why is new Class[n] allowed? And what then is the "proper" (or at least, common) way to do this?

+11  A: 

Java does not permit generic arrays. More information in the Java Generics FAQ.

To answer your question, just use a List (probably ArrayList) instead of an array.

Some more explanation can be found in Java theory and practice: Generics gotchas:

Generics are not covariant

While you might find it helpful to think of collections as being an abstraction of arrays, they have some special properties that collections do not. Arrays in the Java language are covariant -- which means that if Integer extends Number (which it does), then not only is an Integer also a Number, but an Integer[] is also a Number[], and you are free to pass or assign an Integer[] where a Number[] is called for. (More formally, if Number is a supertype of Integer, then Number[] is a supertype of Integer[].) You might think the same is true of generic types as well -- that List<Number> is a supertype of List<Integer>, and that you can pass a List<Integer> where a List<Number> is expected. Unfortunately, it doesn't work that way.

It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer> to a List<Number>. Then the following code would allow you to put something that wasn't an Integer into a List<Integer>:

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

Because ln is a List<Number>, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.

cletus
That FAQ link was very helpful, thanks. Ironically, according to some articles I found, Collections like ArrayList seem to use unchecked type conversion behind the scenes, so I could just do @SuppressWarnings("unchecked") myself.
Hanno Fietz
Yes, that's perfectly reasonable. I try to comment every use of @SuppressWarnings so I later understand what the reasoning behind that was.
jrudolph
BTW: using a collection type just for compile-time reasons, in a case when you might definitely want run-time array semantics, seems not the general answer for me... In many cases one has to find a balance between static-type safety and performant run-time behaviour. (Even if the cost of ArrayLists in contrast to plain arrays is minor)
jrudolph
A: 

Arrays are always of a specific type, unlike how Collections used to be before Generics.

Instead of

Class<? extends IMessage>[] _rgAccepted;

You should simply write

IMessage[] _rgAccepted;

Generics don't enter into it.

Kris
These two lines are not really equivalent. I actually do want to pass class objects.
Hanno Fietz
Then, as cletus suggested, you are going to want to use ArrayList (or another collection object) instead of an array.
Kris
+1  A: 

And what then is the "proper" (or at least, common) way to do this?

@SuppressWarnings(value="unchecked")
public <T> T[] of(Class<T> componentType, int size) {
    return (T[]) Array.newInstance(componentType, size);
}

public demo() {
    Integer[] a = of(Integer.class, 10);
    System.out.println(Arrays.toString(a));
}
dfa
If you don't mind ending up with Object[] instead of the raw type of your (possibly generic) component type, casting new Object[size] to T[] would be faster. Array.newInstance has to pay the usual reflection cost.
jrudolph
you cannot allocate an Object[size] array and then use as T[]. Array.newInstance(Foo.class, N) is something like malloc(sizeof(struct foo) * N). return (T[]) new Object[size]; cannot be assigned to an Integer[] it is a java.lang.ClassCastException
dfa
+2  A: 

It is right what cletus said. There is a general mismatch between the usually enforced invariance of generic type parameters vs. covariance of Java arrays.

(Some background: Variance specifies how types relate to each other concerning subtyping. I.e. because of generic type parameters being invariant Collection <: Collection does not hold. So, concerning the Java type system, a String collection is no CharSequence collection. Arrays being covariant means that for any types T and U with T<:U, T[] <: U[]. So, you can save a variable of type T[] into a variable of type U[]. Since there is a natural need for other forms of variance, Java at least allows wildcards for these purposes.)

The solution (the hack, actually) I often use, is declaring a helper method which generates the array:

public static <T> T[] array(T...els){
    return els;
}
void test(){
    // warning here: Type safety : A generic array of Class is created for a varargs parameter
    Class<? extends CharSequence>[] classes = array(String.class,CharSequence.class);
}

Because of erasure the generated arrays will always be of type Object[].

jrudolph
+1  A: 

IMHO,

this._rgAccepted = (Class<? extends IMessage>[])new Class[1];

is the appropriate way to handle this. Array component types have to be reified types, and Class is the closest reified type to Class<whatever>. It'll work just as you would expect a Class<whatever>[] to work.

Yes, technically it is unchecked and might cause issues later if you cast it to another more general array type and put wrong stuff in it, but since this is a private field in your class, I presume you can make sure it is used appropriately.

newacct