我已经在学校用C ++编写了3年的编码。我刚开始在2天前用Java编写代码;我的问题是:
制作通用数组是不好的做法吗?会有什么替代方案?
我很难过,除了做一些奇怪的事情之外,我似乎无法创建一个通用数组,例如:
//Class implementing the MergeSort algorithm with generic types
// Revised by Doina January 2014
package Sorting;
import java.lang.*;
public class MergeSort {
// Wrapper method for the real algorithm
// T is the generic type which will be instantiated at runtime
// elementas are required to be comparable
public static <T extends Comparable<T>> void sort(T[] a) {
mergesort(a, 0, a.length - 1);
}
// Recursive mergesort method, following the pseudocode
private static <T extends Comparable<T>> void mergesort(T[] a, int i, int j) {
if (j - i < 1) return;
int mid = (i + j) / 2;
mergesort(a, i, mid);
mergesort(a, mid + 1, j);
merge(a, i, mid, j);
}
// Merge method
// Here we need to allocate a new array, but Java does not allow allocating arrays of a generic type
// As a work-around we allocate an array of type Object[] the use type casting
// This would usually generate a warning, which is suppressed
@SuppressWarnings("unchecked")
private static <T extends Comparable<T>> void merge(T[] a, int p, int mid, int q) {
Object[] tmp = new Object[q - p + 1];
int i = p;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= q) {
if (a[i].compareTo(a[j]) <= 0)
tmp[k] = a[i++];
else
tmp[k] = a[j++];
k++;
}
if (i <= mid && j > q) {
while (i <= mid)
tmp[k++] = a[i++];
} else {
while (j <= q)
tmp[k++] = a[j++];
}
for (k = 0; k < tmp.length; k++) {
a[k + p] = (T) (tmp[k]); // this is the line that woudl generate the warning
}
}
// Main methos to test the code, using Integer Objects
public static void main(String[] args) {
Integer[] a = new Integer[5];
a[0] = new Integer(2);
a[1] = new Integer(1);
a[2] = new Integer(4);
a[3] = new Integer(3);
a[4] = new Integer(-1);
// T will be instantiated to Integer as a resutl of this call
MergeSort.sort(a);
// Print the result after the sorting
for (int i = 0; i < a.length; i++)
System.out.println(a[i].toString());
}
}
答案 0 :(得分:21)
这本身并不是一个坏主意;只是泛型和数组不能很好地混合。
原因是协方差和不变性。数组是协变的(Integer[]
是Object[]
,因为Integer
是Object
,但是泛型类不变(List<Integer>
是<即使List<Object>
是Integer
,也不会<{1}}。
你还必须处理未经检查的强制转换,这会破坏泛型的整个目的。创建通用数组的最常用方法 - Object
- 不是类型安全的,无法在编译时强制执行。可以在运行时对其进行推理,但是那时泛型带来的编译时检查会丢失。
要直接回答这个问题,您可以在何时何地使用Java Collections代替,因为他们非常适合使用泛型
。只要看一下你提供的代码,我想使用E[] foo = (E[]) new Object[10];
代替List<T>
可以解决你的大多数问题(我希望你传递的是T[]
因为链接列表会使这些操作变得昂贵。
答案 1 :(得分:4)
创建一个通用数组并不是一种坏习惯,但正确地这样做是非常麻烦的,人们通常会避免它。
它很麻烦的原因是擦除了泛型,而阵列则被强化了。也就是说,类型参数在编译期间被擦除,而数组的组件类型被保留。因此,运行时知道每个数组的组件类型,但忘记了所有对象的类型参数,即行
E[] array = new E[10];
无法编译,因为运行时需要知道新数组的组件类型,但遗忘的是E
。
Makoto回答的解决方法:
E[] array = (E[]) new Object[10];
不是一个好主意,因为它实际上创建了一个Object[]
,但随后伪装成E[]
的编译器。由于运行时遗忘的是E
,因此即使它的类型不正确,此转换也会在运行时成功。但是,运行时仍然通过一旦能够执行附加检查来强制执行存储器安全,即当对象存储在其类型不是通用的变量中时。例如:
static <E> E[] createArray(int size) {
return (E[]) new Object[size];
}
public static void main(String[] args) {
String[] array = createArray(size); // throws ClassCastException
for (String s : array) {
// whatever
}
}
也就是说,这种解决方法是只在某些情况下才能工作的黑客攻击,否则会导致非常令人费解的行为(在一行代码中不包含强制转换的ClassCastException ...)。
创建E[]
的唯一方法是通过反射提供我们所需组件类型的类对象:
Class<E> eClass = ...;
E[] array = Arrays.newInstance(eClass, 10);
但我们怎样才能得到这个类对象?如果我们的调用者知道,他们可以传递给我们一个类文字(如Integer.class
),或者我们可以在其他对象上使用反射。在您的情况下,您手头有另一个E[]
,因此您可以询问该数组E
是什么:
E[] originalArray = ...;
Class<E> eClass = (Class<E>) originalArray.getClass().getComponentType();
E[] newArray = (E[]) Array.newInstance(eClass, size);
这将确保新数组与旧数组的类型相同,即E[]
,除非有人使用Makoto的解决方法向我们说明该数组的类型。
正如您所看到的,可以创建一个通用数组,但它非常麻烦,人们通常会竭尽全力避免它。通常的替代方案是使用一些超类型的数组(在合并排序中,Comparable[]
可能比Object[]
更好,因为您不必强制转换),或使用ArrayList
代替。
答案 2 :(得分:1)
添加到 Makoto的回答,我会说Arrays
是协变的,因为它们的类型信息在运行时可用,而泛型类是不变的,因为类型信息不可用到期在编译时键入擦除。
协变数组: -
Object[] covariantArrays = new String[5];
covariantArrays[0] = new Dog(); // will give java.lang.ArrayStoreException
不变数组: -
List invariantArrays = new List<String>();
invariantArrays.add(new Dog()); // Works fine as type information is not available
由于这个原因,通用数组不顺利,因为泛型仅限于编译类型安全性,并且数组甚至在运行时也有可用的实际类型信息