1

I am trying to write some simple numerical code in Java where one can choose between a float and double later. A simplified version of my class looks like the example below:

public class UniformGrid<T> {

    public T[] data;

    public UniformGrid(int arrayDim) {

        data = new T[arrayDim];

    }
}

This didn't work I got a generic array creation error when trying to compile. Googling and reading some SO answers I learned about java.lang.reflect.Array and tried to use

    data = (T[]) Array.newInstance(T.class, arrayDim);

Which also didn't work, since T is (probably) a primitive type. My Java knowledge is quite rusty (especially when it comes to generics) and I would like to know why the new operator cannot be used with a generic array type. Also of course I am interested in how one would solve this problem in Java.

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
Nils
  • 13,319
  • 19
  • 86
  • 108
  • On the right hand side when you are instantiating an array you should specify the type always..You can't use T at that time – Sunny Gupta Jan 21 '12 at 19:00
  • 1
    Did you expect to be able to do `UniformGrid` and `UniformGrid`? One of the issues with Java is that not everything is an object. Primitives, like `float` and `double` are not first-class citizens and can't be used in generics. – Aaron McDaid Jan 21 '12 at 19:08
  • Duplicate question: http://stackoverflow.com/questions/529085/java-how-to-generic-array-creation (There are some good answers there too.) – Aaron McDaid Jan 21 '12 at 19:56

5 Answers5

8

You cannot create a generic array in Java because of type erasure. The easiest way to get around this would be to use a a List<T>. But if you must use an array, you can use an Object[] for your array and ensure that only T objects are put into it. (This is the strategy ArrayList takes.)

Ex:

private Object[] data = new Object[10];
private int size = 0;

public void add(T obj) {
    data[size++] = obj;
}

public T get(int i){
    return (T) data[i];
}

Of course you'll get an unchecked warning from your compiler, but you can suppress that.

Jeffrey
  • 44,417
  • 8
  • 90
  • 141
  • Soo.. I simply can't use generics for primitive types?! – Nils Jan 21 '12 at 19:06
  • @Nils You cannot use a primitive type with generics (Try `new ArrayList();`, it doesn't work). You can, however, use the wrapper classes (`Integer`, `Double`, `Float`, `Short`, `Byte`, `Boolean`, `Long`) and let autoboxing do its magic. – Jeffrey Jan 21 '12 at 19:19
  • @Nils: yes. Jeffrey: And how would you perform computations on those instances of T? And how would you store the result? – meriton Jan 21 '12 at 19:23
  • @meriton If you knew ahead of time that all `T`s were going to be a `Number`, you could restrict the generic parameter to any [`Number`](http://docs.oracle.com/javase/7/docs/api/java/lang/Number.html) (``) and use the `xxxValue()` methods to perform the calculations. Storing the result would probably require an abstract method `protected T getT(Number n)` or something similar. – Jeffrey Jan 21 '12 at 19:28
  • That would work. But you'd have the overhead of boxing and unboxing for every operation. ==> If performance is not critical that's a good solution, but if performance matters I'd take another approach. – meriton Jan 21 '12 at 19:33
1

Generics can't be used when creating an array because you don't know at runtime what type T is. This is called type erasure.

The solution is simple: use List<T> data.

Malcolm
  • 41,014
  • 11
  • 68
  • 91
  • No I stated above I want arrays, list would be slower I think. – Nils Jan 21 '12 at 19:04
  • I can't see any reason in principle why type erasure should cause a problem for arrays. I think the reason they aren't allowed is mainly "because the language designers decided it". Now, type erasure *does* cause a problem for primitives. But an array of references should, in principle, be OK. I think. – Aaron McDaid Jan 21 '12 at 19:21
  • @Nils, you're right. LinkedList would be slower. You should consider an ArrayList instead. – Aaron McDaid Jan 21 '12 at 19:22
  • Well Aaron, that you don't see it doesn't mean it's not there ;-) Array know their component type at runtime, and must be told it upon creation. Generics don't know their type at runtime. So a generic type can not tell the array which type it should have, and that's why generic creation is impossible. – meriton Jan 21 '12 at 19:29
  • @meriton, I think you're missing the point a little. I did some research and made my own answer. Your observation on `T[]` applies just as well to `List`. Arrays do *not* need to know their type, *as long as its a reference type*. – Aaron McDaid Jan 21 '12 at 19:46
  • @AaronMcDaid It is not true that this observation applies to `List`. During runtime `List` becomes `List`, so the class of `data` is clear. You can call the same methods on it, nothing is really changed. But what class should `T[]` become? – Malcolm Jan 21 '12 at 20:14
  • The array would be effectively `Object[]`, just as members a plain `List` are just Object. Plain old [`List` is quite similar to `List`](http://docs.oracle.com/javase/1.4.2/docs/api/java/util/List.html) – Aaron McDaid Jan 21 '12 at 20:53
  • @AaronMcDaid Here's an example based on one from Effective Java. Say, T extends List. `T[] stringLists = new T[1];` Let's suppose it's legal, though it's not. Now what if I do this: `List intList = Arrays.asList(42); Object[] objects = stringLists; objects[0] = intList;` Now `stringLists` contains a reference to an object which is not of type T. T is now Object, so everything can be stored in this array, and you can't check it. Then we call `String str = stringLists[0].get(0)` and get a runtime error. So what would be the point of generics if they didn't guarantee type safety? – Malcolm Jan 21 '12 at 21:41
  • I agree, that is a problem. I described a similar example on another thread on this question. Even without generics, the ability to cast array types all over the place is a problem. So yeah, I'm going to heavily change my answer in order not to expose the `data` array publicly. – Aaron McDaid Jan 21 '12 at 22:31
  • @AaronMcDaid Yes, if you control the access to the array and can guarantee the type checking, then it should work fine. I agree that such behavior of arrays can be considered deficient. So does Bloch, by the way, that's why he advises in Effective Java to prefer Lists over arrays when given a choice. – Malcolm Jan 21 '12 at 22:39
  • When I first answered this question, I didn't appreciate all that and I hadn't done my experiments thoroughly. So thanks for keeping me on my toes, @Malcolm! – Aaron McDaid Jan 21 '12 at 22:49
1

Sorry, you'll have to take another approach:

  1. Type parameters must be reference types, they can't be primitive types.
  2. Only reference types support polymorphism, and only for instance methods. Primitive types do not. float and double don't have a common supertype; you can not write an expression like a + b and choose at runtime whether to perform float addition or double addition. And since Java (unlike C++ or C#, which emit new code for each type parameter) uses the same bytecode for all instances of a generic type, you'd need polymorphism to use a different operator implementation.

If you really need this, I'd look into code generation, perhaps as part of an automated build. (A simple search & replace on the source ought to be able to turn a library operating on double into a library operating on float.)

meriton
  • 68,356
  • 14
  • 108
  • 175
1

This is possible, as long as you use Float and Double instead of float and double, as primitive types are not allowed in Java Generics. Of course, this will probably be quite slow. And, you won't be able to (safely) allow direct public access to the array. So this answer is not very useful, but it might be theoretically interesting. Anyway, how to construct the array ...

data = (T[]) new Object[arrayDim];

This will give you a warning, but it's not directly anything to worry about. It works in this particular form - it's inside a generic constructor and data is the only reference to this newly constructed object. See this page about this.

You will not be able to access this array object publicly in the way you might like. You'll need to set up methods in UniformGrid<T> to get and set objects. This way, the compiler will ensure type-safety and the runtime won't give you any problems.

private T[] data;
public void set(int pos, T t) {
        data[pos] = t;
}
public T get(int pos) {
        return data[pos];
}

In this case, the interface to set will (at compile-time) enforce the correct type is passed. The underlying array is of type Object[] but that's OK as it can take any reference type - and all generic types are effectively List<Object> or something like that at runtime anyway.

The interesting bit is the getter. The compiler 'knows' that the type of data is T[] and hence the getter will compile cleanly and promises to return a T. So as long as you keep the data private and only access it through get and set then everything will be fine.

Some example code is on ideone.

public static void main(String[] args) {
        UniformGrid<A> uf = new UniformGrid<A>(1);
        //uf.insert(0, new Object()); // compile error
        uf.insert(0, new A());
        uf.insert(0, new B());
        Object o1= uf.get(0);
        A      o2= uf.get(0);
        // B      o2= uf.get(0); // compiler error
        System.out.println(o1);
        System.out.println(o2);
        System.out.println("OK so far");
        // A via_array1 = uf.data[0]; // Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [LA;
}

As you would desire, there are compilation errors with uf.insert(0, new Object()) and B o2= uf.get(0);

But you shouldn't make the data member public. If you did, you could write and compile A via_array1 = uf.data[0];. That line looks like it should be OK, but you get a runtime exception: Ljava.lang.Object; cannot be cast to [LA;.

In short, the get and set interface provide a safe interface. But if you go to this much trouble to use an array, you should just use an ArrayList<T> instead. Moral of the story: in any language (Java or C++), with generics or without generics, just say no to arrays. :-)

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
0

Item 25 in Effective Java, 2nd Edition talks about this problem:

Arrays are covariant and reified; generics are invariant and erased. As a consequence, arrays provide run-time type safety but not compile-time type safety and vice versa for generics. Generally speaking arrays and generics don't mix well.

Amir Pashazadeh
  • 7,170
  • 3
  • 39
  • 69