Monday, September 7, 2009

Java Arrays: Copying, String Representations, and Collections

Since the introduction of the Java Collections Framework with JDK 1.2, I have used Java arrays significantly less frequently than I used to. However, I still use arrays occasionally, often because a library or API I am using makes heavy use of arrays. When working with arrays, the Arrays class can be particularly helpful. In this posting, I'll look at how this class simplifies the copying of arrays and providing String representation of arrays. I'll also look at how the use of the method Collection.toArray(T[]) is often used to access the first element of a Java collection without explicit iterating.


Copying Arrays

As demonstrated in the Arrays section of the Java Tutorials, the System class provides an arraycopy method that can be used to copy the contents from one array into another.

The method below shows use of System.arraycopy to copy an entire source array to a destination array. The parameters to this method allow for various ranges of the original array to be copied to the target array.


/**
* Copies the contents of the provided array into a new array that this method
* returns to the caller. This method is implemented with System.arraycopy.
*
* @param originalArray Array to be copied.
* @return Copy of the provided array.
*/
public static String[] copyCompleteStringArrayOldFashionedWay(final String[] originalArray)
{
final int originalArraySize = originalArray.length;
String[] destinationArray = new String[originalArraySize];
System.arraycopy(originalArray, 0, destinationArray, 0, originalArraySize);
return destinationArray;
}


This method works fine, but I prefer to use one of the overloaded Arrays.copyOf or Arrays.copyOfRange methods for copying array contents. I'll look briefly at my reasons for preferring the approach after demonstrating their use. The following method shows copying an entire array using Array.copyOf.


/**
* Copies the contents of the provided array into a new array that this method
* returns to the caller. This method is implemented with Arrays.copyOf.
*
* @param originalArray Array to be copied.
* @return The copy of the provided array.
*/
public static String[] copyCompleteStringArrayNewFangledWay(final String[] originalArray)
{
return Arrays.copyOf(originalArray, originalArray.length);
}


The several overloaded versions of Arrays.copyOf were introduced with Java SE 6. This is just one of the many advances Java SE 6 has brought to Java development and I have covered several of them in this blog.

As the code snippets above demonstrate, the Arrays.copyOf approach results in leaner, more concise code than use of System.arraycopy. Perhaps most important in this difference in terms of required code is that Arrays.copyOf and Arrays.copyRange do NOT require the developer to pre-allocate memory for the array that is the destination of the copying procedure. This may seem like a minor thing, but it reduces the possibility of running into a NullPointerException or other problem associated with improper sizing of the destination array. This appeals to me and my general aversion to unnecessary NullPointerExceptions.

Another thing that I prefer about use of Arrays.copyOf or Arrays.copyRange over System.arraycopy is the location (class) in which these utility methods are found. Functionality for copying arrays just feels out of place in the System class while array copying seems a perfect fit for the Arrays class. The Arrays class's Javadoc-based documentation starts with this sentence in its class-level comments: "This class contains various methods for manipulating arrays (such as sorting and searching)." Copying of arrays seems to fit naturally here and, in fact, I think adding "copying" to "sorting and searching" in the Javadoc sentence would be appropriate.


Printing Contents of Arrays

Once one gets used to easily printing the contents of a Java Collection (or Map) by passing that Collection (including Map in this context of "Collection") to System.out, System.err, or other stream, it can be a little bit disappointment to return to what is printed out for an array when trying to do the same thing. To illustrate, the code snippet below shows methods for printing the contents of an array, of a List, of a Set, and of a Map. Although they all look very similar in code, the output for the array is quite different from that of the Collections.


/**
* Print contents of provided Array directly to standard output using
* System.out directly.
*
* @param array Array to be printed directly to standard output with
* System.out.
*/
public static void printStringArrayDirectly(final String[] array)
{
out.println(INDENT + "DIRECT: " + array);
}

/**
* Print the provided List of Strings to standard output.
*
* @param stringsToPrint List of Strings to be printed.
*/
public static void printStringList(final List<String> stringsToPrint)
{
printHeader("List Directly (" + stringsToPrint.getClass().getCanonicalName() + ")");
out.println(INDENT + stringsToPrint);
}

/**
* Print the provided Set of Strings to standard output.
*
* @param stringsToPrint Set of Strings to be printed.
*/
public static void printStringSet(final Set<String> stringsToPrint)
{
printHeader("Set Directly");
out.println(INDENT + stringsToPrint);
}

/**
* Print the provided Map of Strings to Strings to standard output.
*
* @param stringsToPrint Map of String to String to be printed.
*/
public static void printStringMap(final Map<String,String> stringsToPrint)
{
printHeader("Map Directly");
out.println(INDENT + stringsToPrint);
}


The output for the Map, Set, and List appear in a reasonable and readable output format. The arrays's output is not nearly so readable or useful. Instead, it looks something like [Ljava.lang.String;@42e816. This output does tell us that it is an array of Strings and that its "unsigned hexadecimal representation of the hash code" (see Object.toString Javadoc for more on this) is 42e816 for this particular array instance.

One option for printing the contents of an array is to iterate over the array explicitly and print each element as iteration occurs over that element. Fortunately, an easy convenience method (Arrays.toString()) was added to the previously discussed Arrays class in J2SE 5 to make printing of array contents easier. This is demonstrated in the next code snippet.


/**
* Print contents of provided Array directly to standard output using
* System.out with results of Arrays.toString() handling of provided array.
*/
public static void printStringArrayUsingArraysToString(final String[] array)
{
out.println(INDENT + "ARRAYS'S TOSTRING: " + Arrays.toString(array));
}


As shown in the above code snippet, it is easy to invoke Arrays.toString on the array. In fact, it is an overloaded method that supports arrays of all of the primitive types as well as general objects as used in this example. The output of this static method is strikingly similar to the output when a List of Strings is printed directly.

Although my preference is to use Arrays.toString to print contents of an array, another approach one could take is to first convert the array to a Collection and then take advantage of the fact that Collections are converted to a readable String format implicitly. This is done with another static Arrays method, Arrays.asList(). This method directly provides a List based on a provided array. If a Set is desired, the resultant List can be passed to the constructor of a Set implementation that accepts a Collection as a parameter (such as HashSet).


Using Arrays to Retrieve Specific Element of Collection

Although I predominately use collections today rather than arrays, there are times when array syntax is highly useful. A common example of this is when I need to access a specific element in a collection. This seems to occur for me mostly when in a situation in which I know there is only one element in the collection and I need to access it directly. I don't want to iterate over the collection to simply obtain the first element. An easy way to get this first element without explicit iteration over the collection is to use the toArray methods on List and Set to first get an array handle to the elements underlying the collection and then to access the element by array index. The following code shows how this can be done. As noted in the comments, Java arrays employ C-like zero-based indexes and so using an index of 0 obtains the first element.


/**
* Extract the first element from the provided List of Strings. This is
* accomplished by using List's toArray method to access the List as an
* array and then the first element of that array is simply accessed with
* zero for the index (zero-based arrays means zero is index of first element
* in array).
*
* @param list List of Strings from which to extract first String element.
* @return First String element of the provided List of Strings.
*/
public static String extractFirstStringElementFromList(final List<String> list)
{
return list.toArray(new String[0])[0];
}

/**
* Extract the first element from the provided Set of Strings. This is
* accomplised by using Set's toArray method to access the Set as an array
* and then the first element of that array is simply accessed with zero for
* the index (zero-based arrays means zero is index of first element in array).
* WARNING: 'Order' in a Set is a tricky business. The use of the "first
* element" of a Set is probably mostly useful in situations where the calling
* code happens to know that the Set is a single element Set, perhaps because
* it is returned from a call using Collections.singleton().
*/
public static String extractFirstStringElementFromSet(final Set<String> set)
{
return set.toArray(new String[0])[0];
}


Of course, the array syntax allows any element in the Collection-turned-array to be accessed with the integer representing the array index. I just used zero here for the first element because it is the case I most commonly run into. It is also worth noting that List.get(int) already allows one to easily access a specific element in the List (my preferred approach). There is no similar method for Set presumably because this often doesn't make any sense in a Set context because some Set implementations (most notably HashSet) do not have the "predictable iteration order" of the LinkedHashSet.

I have stated previously that I'm a little disappointed with the list of features that appear to have made it into Java SE 7. However, it is interesting to see that it Index Access for Syntax for Lists and Maps appears on its ways into Java SE 7.


Conclusion

The Arrays class makes use of arrays in Java more convenient and easier than direct manipulation of those arrays. In this blog post, I have focused on use of overloaded Java SE 6 methods Arrays.copyOf (and corresponding Arrays.copyRange), J2SE 5's overloaded Arrays.toString() methods, and the Arrays.asList() method. I include the complete class containing above code snippets below.


Example Code - JavaArrays.java


package dustin.examples;

import static java.lang.System.out;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* This class demonstrates some Java utility functions related to Java arrays.
*/
public class JavaArrays
{
/** New line/carriage return/form feed. */
private static final String NEW_LINE = System.getProperty("line.separator");

/** Represents tab or indentation for output. */
private final static String INDENT = "\t";

/**
* Copies the contents of the provided array into a new array that this method
* returns to the caller. This method is implemented with System.arraycopy.
*
* @param originalArray Array to be copied.
* @return Copy of the provided array.
*/
public static String[] copyCompleteStringArrayOldFashionedWay(final String[] originalArray)
{
final int originalArraySize = originalArray.length;
String[] destinationArray = new String[originalArraySize];
System.arraycopy(originalArray, 0, destinationArray, 0, originalArraySize);
return destinationArray;
}

/**
* Copies the contents of the provided array into a new array that this method
* returns to the caller. This method is implemented with Arrays.copyOf.
*
* @param originalArray Array to be copied.
* @return The copy of the provided array.
*/
public static String[] copyCompleteStringArrayNewFangledWay(final String[] originalArray)
{
return Arrays.copyOf(originalArray, originalArray.length);
}

public static Map<String, String> generateMapToUppercase(final Collection<String> keys)
{
final Map<String, String> map = new HashMap<String, String>();
for (final String key : keys)
{
map.put(key, key.toUpperCase());
}
return map;
}

/**
* Extract the first element from the provided List of Strings. This is
* accomplished by using List's toArray method to access the List as an
* array and then the first element of that array is simply accessed with
* zero for the index (zero-based arrays means zero is index of first element
* in array).
*
* @param list List of Strings from which to extract first String element.
* @return First String element of the provided List of Strings.
*/
public static String extractFirstStringElementFromList(final List<String> list)
{
return list.toArray(new String[0])[0];
}

/**
* Extract the first element from the provided Set of Strings. This is
* accomplised by using Set's toArray method to access the Set as an array
* and then the first element of that array is simply accessed with zero for
* the index (zero-based arrays means zero is index of first element in array).
* WARNING: 'Order' in a Set is a tricky business. The use of the "first
* element" of a Set is probably mostly useful in situations where the calling
* code happens to know that the Set is a single element Set, perhaps because
* it is returned from a call using Collections.singleton().
*/
public static String extractFirstStringElementFromSet(final Set<String> set)
{
return set.toArray(new String[0])[0];
}

/**
* Print contents of provided Array directly to standard output using
* System.out directly.
*
* @param array Array to be printed directly to standard output with
* System.out.
*/
public static void printStringArrayDirectly(final String[] array)
{
out.println(INDENT + "DIRECT: " + array);
}

/**
* Print contents of provided Array directly to standard output using
* System.out with results of String.valueOf() handling of provided array.
*/
public static void printStringArrayUsingStringValueOf(final String[] array)
{
out.println(INDENT + "STRING'S VALUEOF: " + String.valueOf(array));
}

/**
* Print contents of provided Array directly to standard output using
* System.out with results of Arrays.toString() handling of provided array.
*/
public static void printStringArrayUsingArraysToString(final String[] array)
{
out.println(INDENT + "ARRAYS'S TOSTRING: " + Arrays.toString(array));
}

/**
* Print provided array to standard output using several different mechanisms
* of accessing String contents of array.
*
* @param array Array to be printed.
* @param label Label to be printed above printed array contents.
*/
public static void printStringArray(final String[] array, final String label)
{
printHeader(label);
printStringArrayDirectly(array);
printStringArrayUsingStringValueOf(array);
printStringArrayUsingArraysToString(array);
}

/**
* Print the provided List of Strings to standard output.
*
* @param stringsToPrint List of Strings to be printed.
*/
public static void printStringList(final List<String> stringsToPrint)
{
printHeader("List Directly (" + stringsToPrint.getClass().getCanonicalName() + ")");
out.println(INDENT + stringsToPrint);
}

/**
* Print the provided Set of Strings to standard output.
*
* @param stringsToPrint Set of Strings to be printed.
*/
public static void printStringSet(final Set<String> stringsToPrint)
{
printHeader("Set Directly");
out.println(INDENT + stringsToPrint);
}

/**
* Print the provided Map of Strings to Strings to standard output.
*
* @param stringsToPrint Map of String to String to be printed.
*/
public static void printStringMap(final Map<String,String> stringsToPrint)
{
printHeader("Map Directly");
out.println(INDENT + stringsToPrint);
}

/**
* Print the single provided String along with a header using the provided
* label.
*
* @param stringToPrint String to be printed.
* @param label Label to be used in header printed with this String.
*/
public static void printSingleString(final String stringToPrint, final String label)
{
printHeader(label);
out.println(INDENT + stringToPrint);
}

/**
* Prints provided label String as part of simple header to standard output.
*
* @param label Label to be included in header.
*/
public static void printHeader(final String label)
{
out.println(NEW_LINE);
out.println(
"====================================================================");
out.println("===== " + label + " ======");
out.println(
"====================================================================");
}

/**
* Main function for running demonstrations of Java array utility functions.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
// Use fruits-oriented String array to demonstrate copying of arrays with
// System.arraycopy. Will also demonstrate multiple ways to print array.
final String[] fruitsSourceArray =
{"Apple", "Banana", "Grape", "Orange", "Strawberry", "Tomato", "Watermelon"};
final String[] fruitsDestinationArray =
copyCompleteStringArrayOldFashionedWay(fruitsSourceArray);
printStringArray(fruitsSourceArray, "Fruits SOURCE Array");
printStringArray(fruitsDestinationArray, "Fruits DESTINATION Array");

// Use vegetables-oriented String array to demonstrate copying of arrays
// with Arrays.copyOf. Will also demonstrate multiple ways to print array.
final String[] vegetablesSourceArray =
{"Asparagus", "Broccoli", "Carrot", "Green Bean", "Pea", "Spinach", "Tomato"};
final String[] vegetablesDestinationArray =
copyCompleteStringArrayNewFangledWay(vegetablesSourceArray);
printStringArray(vegetablesSourceArray, "Vegetables SOURCE Array");
printStringArray(vegetablesDestinationArray, "Vegetables DESTINATION Array");

// Demonstrate simplified printing of Java collections
final List<String> fruitsList = Arrays.asList(fruitsSourceArray);
printStringList(fruitsList);
final Set<String> fruitsSet = new HashSet<String>(Arrays.asList(fruitsSourceArray));
printStringSet(fruitsSet);
final Map<String, String> fruitsMap = generateMapToUppercase(fruitsList);
printStringMap(fruitsMap);

// Extract 'first' element of List and Set using toArray
final String firstListElement = extractFirstStringElementFromList(fruitsList);
printSingleString(firstListElement, "First Element from List");
final String firstSetElement = extractFirstStringElementFromSet(fruitsSet);
printSingleString(firstSetElement, "First Element from Set");
}
}

No comments: