Nils
Nils

Reputation: 13777

Java use generics to set the primitive type of an array later

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.

Upvotes: 1

Views: 1518

Answers (5)

Aaron McDaid
Aaron McDaid

Reputation: 27153

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. :-)

Upvotes: 1

Amir Pashazadeh
Amir Pashazadeh

Reputation: 7322

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.

Upvotes: 0

meriton
meriton

Reputation: 70584

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.)

Upvotes: 1

Jeffrey
Jeffrey

Reputation: 44808

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.

Upvotes: 8

Malcolm
Malcolm

Reputation: 41510

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.

Upvotes: 1

Related Questions