Rea*_*ted 3 java arrays generics
我的目标是实现一个方法,将任意数量的数组连接到它们的公共超类型的单个数组中,返回生成的(类型化的)数组.我有两个实现.
第一个(这个不需要简化):
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;
}
Run Code Online (Sandbox Code Playgroud)
让我们创建一些数组:
Long[] l = { 1L, 2L, 3L };
Integer[] i = { 4, 5, 6 };
Double[] d = { 7., 8., 9. };
Run Code Online (Sandbox Code Playgroud)
我们的方法调用:
Number[] n = concatArrays(Number.class, l, i, d);
Run Code Online (Sandbox Code Playgroud)
这是有效的,并且完全是类型安全的(例如,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;
}
Run Code Online (Sandbox Code Playgroud)
从客户的角度来看,这更好用:
Number[] n = arrayConcat(l, i, d);
Run Code Online (Sandbox Code Playgroud)
而且,编译器足够聪明,可以给出适当的错误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;
}
Run Code Online (Sandbox Code Playgroud)
但是这会在返回时抛出ClassCastException.显然new T[totalLen]也是出来的.有没有人有任何其他想法?
你可以这样做:
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;
}
Run Code Online (Sandbox Code Playgroud)
这利用了以下事实:当使用varargs时,编译器构造组件的数组,并且正确设置数组的类型,使得组件类型是vararg元素类型.在这种情况下,数组具有类型T[][],因此我们可以提取T并使用它来构造我们的类型T[].
(一个例外是如果调用者使用泛型类型作为varargs类型调用它,则编译器无法构造正确的数组类型.但是,如果调用者执行此操作,它将在调用者代码中生成警告(臭名昭着的varargs泛型警告),所以来电者被警告会发生坏事,所以这不是我们的错.)
这个解决方案的一个令人惊奇的事情是即使用户传递零数组也不会产生错误的答案!(只要编译器编译它成功,它会推断(或已明确指定)的一些具体类型显然编译器不会永远推断在这种情况下正确.T,从而T[]为有效的返回类型.该类型T是给我们的类型arrays)
请注意,调用者可以手动传递"arrays"参数,在这种情况下,它可以具有运行时类型U[][],其中U是子类型T.在这种情况下,我们的方法将返回一个运行时类型的数组U[],这仍然是一个正确的结果,因为它U[]是一个子类型T[].