我正在尝试用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
。
对不起,您将不得不采取另一种方法:
- 类型参数必须是引用类型,不能是基元类型
- 只有引用类型支持多态性,并且只支持实例方法。基元类型则不然。float和double没有共同的超类型;不能像a+b那样编写表达式,并在运行时选择执行浮点加法还是双加法。由于Java(与C++或C#不同,它们为每个类型参数发出新代码)对泛型类型的所有实例使用相同的字节码,因此需要多态性来使用不同的运算符实现
如果你真的需要,我会研究代码生成,也许是作为自动化构建的一部分。(对源代码进行简单的搜索和替换应该能够将运行在double上的库转换为运行在float上的库。)
只要使用Float
和Double
而不是float
和double
,这是可能的,因为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
保持为私有,并且只通过get
和set
访问它,那么一切都会好起来。
有些示例代码是表意的。
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;
。
简而言之,get
和set
接口提供了一个安全的接口。但是,如果您在使用数组时遇到这么多麻烦,那么您应该只使用ArrayList<T>
。故事的寓意:在任何语言(Java或C++)中,无论有没有泛型,都只对数组说不。:-)
有效Java第二版中的第25项讨论了这个问题:
数组是协变的和具体化的;泛型是不变的和可擦除的。因此,数组提供运行时类型安全性,但不提供编译时类型安全,反之亦然。一般来说,数组和泛型不能很好地混合。