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



我正在尝试用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知识相当生疏(尤其是在泛型方面),我想知道为什么新运算符不能与泛型数组类型一起使用。当然,我也对如何在Java中解决这个问题感兴趣。

由于类型擦除,您无法在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];
}

当然,编译器会发出未经检查的警告,但您可以取消它。

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

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

对不起,您将不得不采取另一种方法:

  1. 类型参数必须是引用类型,不能是基元类型
  2. 只有引用类型支持多态性,并且只支持实例方法。基元类型则不然。float和double没有共同的超类型;不能像a+b那样编写表达式,并在运行时选择执行浮点加法还是双加法。由于Java(与C++或C#不同,它们为每个类型参数发出新代码)对泛型类型的所有实例使用相同的字节码,因此需要多态性来使用不同的运算符实现

如果你真的需要,我会研究代码生成,也许是作为自动化构建的一部分。(对源代码进行简单的搜索和替换应该能够将运行在double上的库转换为运行在float上的库。)

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

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

这会给你一个警告,但这并不直接需要担心。它以这种特殊的形式工作——它在一个泛型构造函数中,data是对这个新构造的对象的唯一引用。请参阅本页。

您将无法以您可能喜欢的方式公开访问此数组对象。您需要在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访问它,那么一切都会好起来。

有些示例代码是表意的。

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++)中,无论有没有泛型,都只对数组说不。:-)

有效Java第二版中的第25项讨论了这个问题:

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

相关内容

  • 没有找到相关文章

最新更新