Utility Classes of the JDK: Collections and Arrays

 · 8 min
Thomas Ciszewski on Unsplash

The JDK is evolving with every new release, adding more and improving existing features. Beneath the surface are hidden gems that 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:


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


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:


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:

List<String> threadSafe =

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 of how to use make a List` type-safe:

List untyped = new ArrayList();
// untyped.add(1); <-- this would be valid, but is undesired

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

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:

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:

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

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

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


Just like collections before, arrays can be sorted. In the 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:


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


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.


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


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:


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];


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.

A Functional Approach to Java Cover Image
Interested in using functional concepts and techniques in your Java code?
Check out my book!
Available in English, Polish, Korean, and soon, Chinese.