Copying Generic Arrays in Java
Intro
Copying an array.
It sounds so simple - run a quick for
loop,
or better yet, System.arraycopy
.
System.arraycopy(array, 0, tmp, 0, array.length);
What if you need to perform a destructive operation? [1]
In my Data Structures class, we’re asked to implement heap sort
without modifying the original array.
This is simple enough: use array.clone
.
It preserves the order of the original array while giving you a new instance.
T[] tmp = array.clone();
Remember that this is a destructive operation, however; you need a new third array in which to store the result. Only one problem: Java doesn’t allow generic arrays to be created. Trying to do so is a compile-time error:
class Generic<T> {
Generic () {
System.out.println(new T[0]);
}
public static void main(String[] args) {
new Generic<String>();
}
}
% javac Generic.java
Generic.java:3: error: generic array creation
System.out.println(new T[0]);
^
1 error
Fine, let’s do some ugly casting.
T[] array = (T[]) new Object[0];
Strange happenings
So far, this has all been pretty standard. Java has type erasure, Java generics are a pain, yadda yadda. Now we get to the strange part of this post.
class Copy<T> {
private final T[] array;
Copy(T[] array) {
this.array = array;
}
T[] getNew() {
T[] result = (T[]) new Object[array.length];
for (int i = 0; i < array.length; i++) {
result[i] = array[i];
}
return result;
}
public static void main(String[] args) {
Integer[] arr = new Copy<Integer>(new Integer[] {1, 2, 3}).getNew();
}
}
% javac copy.java
Note: copy.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
% java copy
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at copy.main(copy.java:16)
What?!‽
Not only are we getting a ClassCastException
trying to convert Integer
to Integer
,
but we’re getting it at runtime, not compile-time!
Worse yet, there’s no traceback - the error pointed to main
, not to getNew
!
My solution
What happens when you declare a new array in Java?
You might assume that this is translated directly to Java bytecode, but you would be incorrect.
javac
actually translates this to java.lang.reflect.Array.newInstance
, which in turn
calls
the native method java.lang.reflect.Array.newArray
.
In fact, you can call this method yourself, but only if you have the class type.
Type parameters don’t cut it.
Well, if you have an instance of the class, you can call instance.getClass()
.
If you don’t, you can (in this particular case) return a null pointer,
since the array must be empty.
% cat copy.java
class Copy<T> {
private final T[] array;
Copy(T[] array) {
this.array = array;
}
T[] getNew() {
if (array.length == 0) return null;
T[] result = (T[]) java.lang.reflect.Array.newInstance(array[0].getClass(), array.length);
for (int i = 0; i < array.length; i++) {
result[i] = array[i];
}
return result;
}
public static void main(String[] args) {
System.out.println(new Copy<Integer>(new Integer[] {1, 2, 3}).getNew());
}
}
% javac $_
Note: copy.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
[email protected] ~/Documents/Programming/Java/test
% java copy
[Ljava.lang.Integer;@28d93b30
Conclusion
I still have no idea what’s going on here - my best guess is that due to type erasure,
the JIT compiler doesn’t know that the objects in
array
are actually valid Integer
s.