The JDK is evolving with every new release, adding more and improving existing features. Beneath the surface are hidden gems that can make our lives as developers much easier.

Missing pieces of functionality are often complemented by third-party-libraries, like the different Apache Commons libraries, or Google Guava. But over time, some of their functionality and ideas become incorporated into the JDK.

The first article highlights the utility classes with their static methods for dealing with collections and arrays.

At least Java 8 is assumed.
Some methods might not be available in earlier versions. All listed methods omit the static keyword and generic type definitions to reduce visual clutter.


Working with Collections

The utility class java.util.Collections, introduced with Java 2, and improved with generics thanks to Java 5, provides a lot of static methods to operate on or return collections.

Immutable empty collections

When we need to return an empty collection, or a collection-related type, we could create a new empty instance. If our code design allows this instance to be immutable, we can use Collections instead for an empty, static instance:

Singletons

Instead of an empty collection, we can also return an immutable collection with exactly one element:

Immutability

Creating an unmodifiable view backed by an existing collection. This way we can make a pre-existing collection immutable:

Sorting and (re)ordering

Sorting a given collection by either natural order, or uses the provided Comparator:

Non-sort-related (re-)ordering is also supported, like reversing the order, rotating or moving all elements around, and randomly shuffling the content:

Instead of iterating over collections, the index of a specific element can be located using the binary search algorithm:

Thread-safety

Creating a synchronized (thread-safe) view of an existing collection:

To remain thread-safe, all access to the backing collection must be by the returned synchronized collection.

Another restriction is the need for manual synchronization if the collection is traversed by an Iterator, Spliterator, or Stream. The creation of the traversal-object must be inside a synchronized block:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
List<String> threadSafe =
    Collections.synchronizedCollection(data);

synchronized (threadSafe) {
    // This call MUST be inside a synchronized block
    Iterator<String> i = c.iterator();
    while (i.hasNext()) {
        // ...
    }
}

Type-safe non-generic collections

Before the introduction of generics, collection types had to deal with the lack of type-safety and everything was an Object. If we have legacy code with such a non-generic collection, we can create a type-safe view of that collection:

Here’s a quick example how to use make a List` type-safe:

1
2
3
4
5
6
7
8
// UNTYPED COLLECTION
List untyped = new ArrayList();
untyped.add("A");
untyped.add("B");
// untyped.add(1); <-- this would be valid, but is undesired

// CREATE TYPE-SAFE VIEW
List<String> typed = Collections.checkedList(untyped, String.class);

Working with Arrays

The utility class java.util.Arrays is kind of the array-based counterpart to java.util.Collections, providing static methods for operating on, sorting, copying, searching arrays, etc.

Due to value-types, and the generality of Object, most methods are available for value-types, specifically Object, and generics.

To not list every single method, the placeholder type[] will be used as a stand-in for the different types. The type boolean is a special case and won’t support a method unless it’s mentioned.

boolean[]  <-- Only available if explicitly stated
byte[]
char[]
int[]
long[]
short[]
long[]
float[]
double[]
Object[]

List creation

The easiest way to create a list from an arbitrary amount of elements:

Thanks to the varargs, we can either provide multiple elements or an array:

1
2
3
4
5
String[] array = new String[] { "DE", "EN" };

List<String> fromArray = Arrays.asList(array);

List<String> fromElements = Arrays.asList("DE", "EN");

Be aware that the List instance will be immutable. To create a mutable view again, we can wrap it in another ArrayList:

1
2
3
4
5
List<String> lang = Arrays.asList("DE", "EN");

lang.add("JP"); // throws UnsupportedOperationException

List<String> mutable = new ArrayList<>(lang);

Sorting

Just like collections before, arrays can be sorted. In case of value types, this means in numerical order, for Object and <T>, in the natural order.

Sorting only a specific range of an array is also supported:

If natural order doesn’t suffice, a custom [Comparator](https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html) can be provided:

Streams

I have written multiple times about how much I love Streams. With the help of java.util.Arrays we can easily create Stream<T> and value-type Streams, at least for the supported types:

 type[]   | ValueTypeStream (= V)
----------|------------------------
 int[]    | IntStream
 long[]   | LongStream
 double[] | DoubleStream

Spliterator

Java 8 introduced the java.util.Spliterator interface, a base utility for Streams. It’s used for traversing and partitioning sequences.

Spliterators are also subclassed according to their value-type, and as before, only three numerical types are supported:

 type[]   | ValueTypeSpliterator (= V)
----------|-----------------------------
 int[]    | Spliterator.OfInt
 long[]   | Spliterator.OfLong
 double[] | Spliterator.OfDouble

String representations

For better debugging or logging output, we can create a string representation of arrays. In addition to the listed types above, boolean is supported. Object[] might contain other arrays, so a method for a deep string representation is also available:

We should never use the string representation generated by a toString() method for anything other than debugging or simple display purposes. The actual implementation might change over time, or different JDKs might handle it not quite exactly the same.

Equality

Like string representation, boolean is supported, and we can check Object[] for deep equality:

Hashcodes

Calculating a hashcode also supports boolean and deep calculation:

Binary search

Finds the index a specific element in an array using the binary search algorithm:

Copying

We can copy any array to a specific length, either truncating it, or padding the new space with the appropriate uninitialized value, or null. The value type boolean is supported:

Filling and setting

Fill an array, or specified range, with a provided value. The value type boolean is supported:

If we need more control over the value, a generator function can be provided:

 type[]   | ValueGenerator (= V)
----------|---------------------
 int[]    | IntUnaryOperator
 long[]   | IntToLongFunction
 double[] | IntToDoubleFunction

Parallel prefixing

The parallelPrefixing methods cumulate each element of an array in parallel. For larger arrays, this kind of operation is usually more efficient than sequential processing:

 type[]   | ValueOperator (= V)
----------|---------------------
 int[]    | IntBinaryOperator
 long[]   | LongBinaryOperator
 double[] | DoubleBinaryOperator

If you haven’t used this method before, it can be unclear what exactly it does, at least that has been my experience Here’s an example to explain it:

// Sum up every element pair
int[] array = { 1, 2, 3, 4 };
Arrays.parallelPrefix(array, (lhs, rhs) -> lhs + rhs);

// Simplified view of what happened:
array[0] = 0 + array[0];
array[1] = array[0] + array[1];
array[2] = array[1] + array[2];
array[3] = array[2] + array[3];

Conclusion

Many common tasks that we might either code ourselves, or use a third-party-library, can be safely done directly with the JDK. This way we depend on fewer externalities, don’t introduce bugs by writing additional code and handle common tasks in a concise, consistent, and reproducible way.

The next part will present utility classes for handling I/O.

Resources