September 1, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Copying Arrays in Java 6

  • May 29, 2007
  • By Jeff Langr
  • Send Email »
  • More Articles »

The Java language contains some wonderful antiquities. Switch statements, for example, still don't support the notion of switching on String objects. (The switch statement has, however, been updated to support enum types.) The reason for this appears to date back to the notion of how switch statements were originally designed in C. Whatever the reason, I find it unfortunate that I can't write switch statements around Strings, something that I've wished I could do a few times.

System.arraycopy, the method used to rapidly copy the contents of one array to another, appears to have similar legacy underpinnings. Even the name of the method looks like an old style C function such as memcpy. This preferred means of copying arrays in Java hasn't changed since the first release.

The arraycopy method, defined as a static method on System, is certainly powerful enough. The System class provides a single general-purpose method:

System.arraycopy(sourceArray, sourceStartIndex, targetArray, targetStartIndex, length);
The arraycopy method supports more than just copying the entire contents of a source array into a target. In addition, arraycopy allows for a source and target start index, as well as a length that represents the number of elements to copy.

This additional flexibility, while it may not be what I need most of the time, is commonly used. The ArrayList class, for example, uses arraycopy to shift the contents of an array when someone inserts an element before its last element. Sun implemented arraycopy to be very fast: It's not implemented in Java, like most of the API, but instead as a native operation that gets executed directly by the JVM.

Most of the time, though, I want to copy an entire array, not subsections. Nor do I need to shift elements in an array very often. Most of my invocations of System.arrayCopy require two lines of code:

      String[] source = { "alpha", "beta", "gamma" };
      String[] target = new String[source.length];
      System.arraycopy(source, 0, target, 0, source.length);
For this simple, common need, it seems like Java requires too much code. If I use this construct a couple or more times in a given application, I want a nice reusable method:
private final <T> T[] copy(T[] source) {
   T[] target = new T[source.length]; // This will not compile!
   System.arraycopy(source, 0, target, 0, source.length);
   return target;
}
Well, that won't work. Compilation fails with the error message:
Cannot create a generic array of T
I can get past this error using reflection. Here's the code that makes the copy method work:
private final <T> T[] copy(T[] source) {
   Class type = source.getClass().getComponentType();
   T[] target = (T[])Array.newInstance(type, source.length);
   System.arraycopy(source, 0, target, 0, source.length);
   return target;
}
The Java compiler shows some concern about the call to newInstance, giving me the warning "Type safety: the case from Object to T[] is actually checking against the erased type Object[]." Using reflection means that there's not sufficient information for the compiler to know whether or not this is a bad cast. While there might be a better solution, I take the simple route and suppress that pesky warning:
@SuppressWarnings("unchecked")
private final <T> T[] copy(T[] source) {
   Class type = source.getClass().getComponentType();
   T[] target = (T[])Array.newInstance(type, source.length);
   System.arraycopy(source, 0, target, 0, source.length);
   return target;
}
Using this copy method, my client code is as simple as it should be:
String[] source = { "alpha", "beta", "gamma" };
String[] target = copy(source);

As demonstrated, I can build my own general-purpose array copy utility method in short order.

Introducing copyOf

In Java 6, I no longer need to roll my own utility method: Sun has introduced direct support for copying an array. Sun has defined a new overloaded method, copyOf, on the Arrays class.

The basic form of copyOf is:

targetArray = Arrays.copyOf(sourceArray, length);
I wrote a number of JUnit tests to demonstrate behavior of the copyOf method (and of its variants). Here's the first test, showing basic functionality:
@Test
public void genericArrayCopyOf() {
   Number[] source = { new Double(5.0), new Double(10.0) };
   Number[] target = Arrays.copyOf(source, source.length);
   assertEquals(source, target);
}
One nice new feature of JUnit 4 is its ability to compare two arrays using assertEquals. When doing so, JUnit first compares the lengths of each array. If both array lengths are equal, JUnit compares each element in the array using equals. If the arrays differ, JUnit provides a failure message that shows the unequal values.

Java overloads copyOf to support primitive types, as well as a couple other variants. Another test shows how I can supply a source range:

@Test
public void copyOfWithRange() {
   String[] source = { "0", "1", "2", "3", "4" };
   String[] target = Arrays.copyOfRange(source, 2, 4);
   assertEquals(new String[] { "2", "3" }, target);
}
Here is a test that demonstrates copying a primitive range:
@Test
public void primitives() {
   int[] source = { 0, 1, 2, 3, 4 };
   int[] target = Arrays.copyOfRange(source, 4, 5);
   assertEqualsPrim(new int[] { 4 }, target);
}
I wrote the method assertEqualsPrim because JUnit 4 does not cover comparing two arrays containing primitive objects. It contains only a version that compares two Object arrays. Comparing two primitive arrays using assertEquals means that Java looks for memory equivalence. No matter, assertEqualsPrim is pretty easy to write:
static void assertEqualsPrim(int[] expected, int[] actual) {
   if (expected.length != actual.length)
      fail(String.format("expected length = %s, actual length = %s",
            expected.length, actual.length));
   for (int i = 0; i < expected.length; i++) {
      if (expected[i] != actual[i])
         fail(String.format(
               "mismatch at index %d: expected [%s] but was [%s]", i,
               expected[i], actual[i]));
   }
}

What if I want the new array to be of a new, subclass type? The following test fails:

@Test
public void genericArrayCopyOfWithNewType() {
   Number[] source = { new Double(5.0), new Double(10.0) };
   Double[] target = (Double[])Arrays.copyOf(source, source.length);
   assertEquals(source, target); // fail!
}
But Java 6 allows me to declare a new type for the target array on a copy:
@Test
public void genericArrayCopyOfWithNewType() {
   Number[] source = { new Double(5.0), new Double(10.0) };
   Double[] target = Arrays.copyOf(source, source.length, Double[].class);
   assertEquals(source, target);
}





Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel