我的目标是实现一个方法,将任意数量的数组连接到它们的公共超类型的单个数组中,返回生成的(类型化的)数组。我有两个实现。
第一个(这个不需要简化):
public static <T> T[] concatArrays(Class<T> type, T[]... arrays) {
int totalLen = 0;
for (T[] arr: arrays) {
arr.getClass().getCom
totalLen += arr.length;
}
T[] all = (T[]) Array.newInstance(type, totalLen);
int copied = 0;
for (T[] arr: arrays) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return all;
}
让我们创建一些数组:
Long[] l = { 1L, 2L, 3L };
Integer[] i = { 4, 5, 6 };
Double[] d = { 7., 8., 9. };
我们的方法调用:
Number[] n = concatArrays(Number.class, l, i, d);
这是有效的并且完全是类型安全的(例如,concatArrays(Long.class, l, i, d)
是编译器错误),但是如果没有必要指定Number.class
则有点烦人。所以我实现了以下方法( 这是我想简化的 ):
public static <T> T[] arrayConcat(T[] arr0, T[]... rest) {
Class commonSuperclass = arr0.getClass().getComponentType();
int totalLen = arr0.length;
for (T[] arr: rest) {
totalLen += arr.length;
Class compClass = arr.getClass().getComponentType();
while (! commonSuperclass.isAssignableFrom(compClass)) {
if (compClass.isAssignableFrom(commonSuperclass)) {
commonSuperclass = compClass;
break;
}
commonSuperclass = commonSuperclass.getSuperclass();
compClass = compClass.getSuperclass();
}
}
T[] all = (T[]) Array.newInstance(commonSuperclass, totalLen);
int copied = arr0.length;
System.arraycopy(arr0, 0, all, 0, copied);
for (T[] arr: rest) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return all;
}
从客户的角度来看,这样做更好:
Number[] n = arrayConcat(l, i, d);
同样,编译器足够聪明,可以在Long[] all = arrayConcat(l, i, d)
上给出适当的错误。由于编译器能够识别此错误,因此很明显我在运行时执行工作(确定给定数组的公共超类),编译器能够在编译时执行。有没有办法实现我的方法而不使用基于反射的方法来确定数组创建步骤的公共超类?
我试过这种方法:
public static <T> T[] arrayConcat(T[]... arrays) {
int totalLen = 0;
for (T[] arr: arrays) {
totalLen += arrays.length;
}
Object[] all = new Object[totalLen];
int copied = 0;
for (T[] arr: arrays) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return (T[]) all;
}
但是这会在返回时抛出ClassCastException。显然new T[totalLen]
也出来了。有没有人有其他想法?
答案 0 :(得分:9)
您可以这样做:
public static <T> T[] arrayConcat(T[]... arrays) {
int totalLen = 0;
for (T[] arr: arrays) {
totalLen += arr.length;
}
T[] all = (T[])Array.newInstance(
arrays.getClass().getComponentType().getComponentType(), totalLen);
int copied = 0;
for (T[] arr: arrays) {
System.arraycopy(arr, 0, all, copied, arr.length);
copied += arr.length;
}
return all;
}
这利用了以下事实:当使用varargs时,编译器构造组件的数组,并且正确设置数组的类型,使得组件类型是vararg元素类型。在这种情况下,数组的类型为T[][]
,因此我们可以提取T
并使用它来构建我们的T[]
。
(一个例外是如果调用者使用泛型类型作为varargs类型调用它,则编译器无法构造正确的数组类型。但是,如果调用者执行此操作,它将在调用者代码中生成警告(臭名昭着的varargs泛型警告),所以呼叫者被警告会发生坏事,所以这不是我们的错。)
这个解决方案的一个令人惊奇的事情是即使用户传递零数组也不会产生错误的答案! (只要编译器成功编译它,它就会推断(或明确指定)某个具体类型的类型给我们显然在这种情况下,编译器无法正确推断。T
,使T[]
是一个有效的返回类型。给出类型T
以arrays
)
请注意,调用者可以手动传递“arrays”参数,在这种情况下,它可以具有U[][]
的运行时类型,其中U
是T
的子类型。在这种情况下,我们的方法将返回运行时类型U[]
的数组,这仍然是正确的结果,因为U[]
是T[]
的子类型。
答案 1 :(得分:3)
简单回答:不。尽管类型检查器在推断T
方面做了一些工作,但是这个信息在到达字节码时就会被删除。擦除是Java泛型的核心;理解它,你就会理解泛型。
顺便说一句,泛型和数组不能很好地混合。如果您尝试将一堆List<String>[]
连接到单个List<String>[]
中,您将获得编译器警告(并且通常缺少类型安全性)。
您的示例#2实际上是在运行时更多工作,而不是编译器能够做到的。考虑:
Number[] longs1 = new Long[] { 1L, 2L, 3L };
Number[] longs2 = new Long[] { 4L, 5L, 6L };
Number[] concatted = arrayConcat(longs1, longs2);
编译器只知道concatted
是Number[]
,但您的方法将(在运行时)确定公共类型实际上是Long[]
。