Java使用泛型来稍后设置数组的基本类型

时间:2012-01-21 18:55:43

标签: java generics reflection

我正在尝试用Java编写一些简单的数字代码,以后可以在float和double之间进行选择。我班级的简化版本如下例所示:

public class UniformGrid<T> {

    public T[] data;

    public UniformGrid(int arrayDim) {

        data = new T[arrayDim];

    }
}

这不起作用我在尝试编译时遇到generic array creation错误。谷歌搜索和阅读一些SO答案我学到了java.lang.reflect.Array并试图使用

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

哪个也行不通,因为T(可能)是原始类型。我的Java知识非常生疏(特别是涉及泛型时),我想知道为什么new运算符不能与泛型数组类型一起使用。当然,我也对如何用Java解决这个问题感兴趣。

5 个答案:

答案 0 :(得分:7)

由于type erasure,您无法在Java中创建通用数组。解决这个问题的最简单方法是使用List<T>。但是,如果必须使用数组,则可以对数组使用Object[],并确保仅将T个对象放入其中。 (这是ArrayList采取的策略。)

例如:

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];
}

当然,你会从你的编译器那里得到一个未经检查的警告,但你可以抑制它。

答案 1 :(得分:1)

创建数组时不能使用泛型,因为您在运行时不知道T是什么类型。这称为type erasure

解决方案很简单:使用List<T> data

答案 2 :(得分:1)

对不起,您必须采取另一种方法:

  1. 类型参数必须是引用类型,它们不能是原始类型。
  2. 只有引用类型支持多态,并且仅适用于实例方法。原始类型没有。浮动和双重不具有共同的超类型;你不能写一个类似a + b的表达式,并在运行时选择是执行浮点加法还是双加法。而且,由于Java(与C ++或C#不同,它为每个类型参数发出新代码)对泛型类型的所有实例使用相同的字节码,因此您需要使用多态来使用不同的运算符实现。
  3. 如果你真的需要这个,我会研究代码生成,也许是自动构建的一部分。 (对源代码的简单搜索和替换应该能够将在double上运行的库转换为在float上运行的库。)

答案 3 :(得分:1)

只要您使用FloatDouble而不是floatdouble,这是可能的,因为Java Generics中不允许使用基本类型。当然,这可能会非常缓慢。并且,您将无法(安全地)允许直接公共访问阵列。所以这个答案不是很有用,但理论上可能很有趣。无论如何,如何构建数组...

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

这会给你一个警告,但这并不是什么值得担心的。它以这种特殊形式工作 - 它位于通用构造函数中,data是对这个新构造对象的唯一引用。 See this page about this.

您将无法以您希望的方式公开访问此数组对象。您需要在UniformGrid<T>中设置方法来获取和设置对象。这样,编译器将确保类型安全,运行时不会给您带来任何问题。

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

在这种情况下,set的接口将(在编译时)强制执行正确的类型。底层数组的类型为Object[]但是没关系,因为它可以采用任何引用类型 - 并且所有泛型类型在运行时都是有效的List<Object>或类似的东西。

有趣的是吸气剂。编译器“知道”data的类型是T[],因此getter将干净地编译并承诺返回T。因此,只要您将data设为私有,并且只能通过getset访问它,那么一切都会好的。

一些示例代码位于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;
}

如您所愿,uf.insert(0, new Object())B o2= uf.get(0);

存在编译错误

但是你不应该让data成员公开。如果你这样做,你可以编写和编译A via_array1 = uf.data[0];。该行看起来应该没问题,但是您得到了运行时异常:Ljava.lang.Object; cannot be cast to [LA;

简而言之,getset接口提供了一个安全的界面。但是如果你使用数组这么麻烦,你应该只使用ArrayList<T>。故事的道德:在任何语言(Java或C ++)中,使用泛型或没有泛型,只对数组说。 : - )

答案 4 :(得分:1)

Effective Java,2nd Edition 中的第25项讨论了这个问题:

  

阵列是协变的和具体化的;泛型是不变的和擦除的。   因此,数组提供运行时类型安全性,但不提供编译时类型安全性,反之亦然。一般而言,数组和泛型不能很好地混合。