Curly Braces #3: Let’s have fun with Java arrays

April 7, 2022 | 8 minute read
Text Size 100%:

Elegant array development might encompass reflection, generics, and lambdas.

Download a PDF of this article

I was recently chatting with a colleague who develops in C. The conversation came to arrays and how very different they work in Java compared to C—which he found surprising given that Java is considered a C-like language.

This exploration of Java arrays starts simple but quickly gets interesting, especially if you studied or use C.

Declaring an array

If you follow a tutorial on Java, you’ll see there are two ways to declare an array. The first is straightforward.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
int[] array; // a Java array declaration

You can see how it differs from C, where the following is the proper syntax:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
int array[]; // a C array declaration

Focusing now on Java, after declaring an array you need to allocate it.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
array = new int[10]; // Java array allocation

Can you declare and initialize an array in one step? Well, you cannot take a shortcut and do the following:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
int[10] array; // NOPE, ERROR!

However, you can declare and initialize an array in one step if you already know the values.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
int[] array = { 0, 1, 1, 2, 3, 5, 8 };

What if you don’t know the values? Here’s the code you’ll encounter more often to declare, allocate, and use an int array.

int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...

Notice I specified an int array, which is an array of Java primitive data types. Let’s see what happens if you try the same process with an array of Java objects instead of primitives.

class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;

If you run the code above, you’ll get an exception as soon as you try to use the first array element. Why? Although the array is allocated, the array buckets each contain null object references. If you type this code into your IDE, it will even autocomplete the .val for you, so the error can be confusing. To resolve the error, do the following:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;

That is not elegant. Indeed, it has often frustrated me that I cannot more easily allocate the array, and the objects within the array, by writing less code, maybe even all in one line.

Thus, I did some experimenting.

Finding Java array nirvana

The goal here is coding elegance, not to be a purist. Smells like “clean code” spirit! And in that spirit, I set out to create some reusable code to clean up the array allocation pattern. Here’s a first attempt.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
public class MyArray {
    
    public static Object[] toArray(Class cls, int size) 
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }
        
        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}

The one line of code marked as “see this” is elegant and looks just the way I wanted, thanks to the implementation of toArray. This approach uses reflection to find the default constructor for the provided class, and then it calls that constructor to instantiate an object of this class. The process calls the constructor once for each element of the array. Brilliant!

Too bad it doesn’t work.

The code compiles fine but results in a ClassCastException when you run it. To use this code, you need to create an array of Object elements and then cast each array element to class SomeClass, as follows:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...

That isn’t elegant! After more experimentation, I evolved several solutions that use reflection, generics, and lambdas.

Solution 1: Use reflection

The answer to the issues above is to use the java.lang.reflect.Array class to instantiate an array of the class you specify, instead of using the base java.lang.Object class. This is essentially a single-line code change that gets closer to the goal.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}

You can use this approach to get an array of the class you desire, and then operate on it as follows:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);

Although it’s not a necessary change, the second line was modified to use reflection’s Array class to set each array element’s content. This is great! But there’s one more detail that does not feel quite right: That cast to SomeClass[] isn’t elegant. Fortunately, there is a solution with generics.

Solution 2: Use generics

The Collections framework uses generics to be type-specific and eliminate casts in many of their operations. You can use generics here as well. Take java.util.List, for example.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...

The third line in the above snippet will result in an error, unless you update the first line as follows:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
List<SomeClass> = new ArrayList();

You can achieve the same result using generics in the MyArray class. Here’s the new version.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();

This looks good. By using generics and including the target type in the declaration, the type can be inferred in other operations. In fact, this code can be reduced to a single line, as follows, if you choose:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();

Mission accomplished, right? Well, not quite. This is fine if you don’t care which class constructor you’re calling, but if you want to call a specific constructor, this solution falls short. You can continue to use reflection to solve this problem, but the code may get complex. Fortunately, lambdas offer another solution.

Solution 3: Use lambdas

I’ll admit, I was slow to adopt lambdas, but I’ve grown to appreciate their value. In particular, I’ve come to appreciate the java.util.stream.Stream interface, which processes collections of objects. Stream helped me achieve Java array nirvana.

Here’s my first attempt to use lambdas.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
SomeClass[] array = 
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);

I broke this code across three lines for readability. You can see it checks all the boxes: It is simple and elegant, creates a populated array of instantiated objects, and allows you to call a specific constructor.

Notice the parameter to the toArray method: SomeClass[]::new. This is a generator function used to allocate an array of the specified type.

However, as it stands, this code has a minor issue: It creates an array of infinite size. That is suboptimal. Fortunately, this can be resolved by calling the limit method.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
SomeClass[] array = 
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);

The array is now limited to 32 elements. You can even set specific object values for each array element, as in the following:

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);

This code demonstrates the power of lambdas, but the code is not neat and compact. Calling a different constructor to set the value is much cleaner, in my opinion.

Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);

I like the lambda-based solution, which is ideal when you need to call a specific constructor or operate on each array element. When I need something more basic, I tend to use the solution based on generics since it’s simpler. However, you can see that lambdas provide an elegant and flexible solution.

Conclusion

This journey explored a lot about Java: declaring and allocating arrays of primitives, allocating arrays of Object elements, using reflection, using generics, and using lambdas.

Best of all, I did this all for a noble cause: to write elegant and compact Java code. Mission complete!

Finally, try this quiz recently published in Java Magazine to see how you do, just for fun.

Dig deeper

Eric J. Bruno

Eric J. Bruno is in the advanced research group at Dell focused on Edge and 5G. He has almost 30 years experience in the information technology community as an enterprise architect, developer, and analyst with expertise in large-scale distributed software design, real-time systems, and edge/IoT. Follow him on Twitter at @ericjbruno.


Previous Post

Bruce Eckel on Java records

Bruce Eckel | 6 min read

Next Post


Java 18’s Simple Web Server: A tool for the command line and beyond

Julia Boes | 7 min read