Saturday, January 14, 2023

Returning multiple values in Java - My preferred approach

Java does not natively support the ability to return multiple values from a method.

It would be nice if we could do something like this: could


public int, int methodThatReturnsMultipleValues() {

    return 1, 2;

}


But we can’t. You’ll likely find this particularly bothersome if you’ve migrated over to Java from another language that supports returning multiple values out of the box (like Golang, for instance).


Thankfully, there are workarounds we can use to emulate this behavior. One of the more common workarounds is to simply return an array that contains the return values, like below:


public int[] methodThatReturnsMultipleValues() {

    return new int[]{1, 2};

}


In order to retrieve both integer values we returned, we read both elements of the array:


int[] returnValues = methodThatReturnsMultipleValues();

int firstValue = returnValues[0];

int secondValue = returnValues[1];


For smaller use cases this works fine, but it gets messier with scale — and that’s why I don’t like to use it. 


It’s easy to introduce errors that won’t be exposed until the application is running, such as issues pertaining to:


Array Indexing


Let’s say you forget to make your code zero-based and attempt to pull from returnValues[1] and returnValues[2] instead. At some point an ArrayIndexOutOfBoundsException is going to get thrown when this code executes.


Additionally, by only using indexes to identify which array element corresponds to which return value, you could inadvertently assign returnValue[1] to firstInteger, and returnValue[0] to secondInteger.


Casting


In a lot of situations where it’s handy to return multiple values, the values are of different types. Since Object is a parent class of all other classes in Java (the topmost class), we can have an Object[] array that returns anything we want:


public Object[] methodReturnsMultipleValuesDifferentTypes() {

    String a = "This is a string";

    int[] b = {1, 2};

    return new Object[] {a, b};

}


We can put any object of any type into that array. Here, I’m putting a string and an integer array into the Object[] array.


But we then need to employ casting when we want to read those values back and assign to their appropriate type(s):


Object[] returnValues = methodReturnsMultipleValuesDifferentTypes();

String firstReturnValue = (String)returnValues[0];

int[] secondReturnValue = (int[])returnValues[1];

And therein lies the possibility for casting mistakes to be made. If you mistakenly cast a value as the wrong type, a ClassCastException will occur during runtime.


My Preferred Approach: 


Because of all the considerations I mention above, I almost never use the array approach.


Instead, I use a generic class to store whatever values I need to return from my method.


I create a generic class named “ResultPair" (or something that clearly conveys what the class is used for), and then place it somewhere accessible to every other class in the project. 


I have a type parameter for every return value, and a constructor that accepts each return value that we want the ResultPair to store:


public class ResultPair <T1, T2> {


    public T1 firstValue;

    public T2 secondValue;


    public ResultPair(T1 firstValue, T2 secondValue) {

        this.firstValue = firstValue;

        this.secondValue = secondValue;

    }


}


Instead of using a convoluted array, now we just pass the values to our ResultPair constructor, and return that from a method:


public ResultPair<String, int[]> methodThatReturnsResultPair() {

    String a = "This is a string";

    int[] b = {1, 2};

    return new ResultPair(a, b);

}



Now accessing the return values is much cleaner:


ResultPair<String, int[]> returnValues = methodThatReturnsResultPair();

String firstReturnValue = returnValues.firstReturnValue;

int[] secondReturnValue = returnValues.secondReturnValue;


There’s no need now to read from an Array, so ArrayIndexOutOfBounds exceptions are no longer a concern. We also don’t need to cast anymore, and if we even attempt to assign one of the return values to the wrong type (assigning the String return value to a Double, for example), we’ll get a heads up with IDE warnings we can resolve before running the application:




The other really nice thing about this approach is that you have a clear indication of how many, and what type of values you’re getting back from a method. In this case when I see ResultPair<String, int[]> in the method signature, I know that I’m getting a String and an Integer back from the method I called — whereas when dealing with an Object[] array I might have to trace through the method to discover what types of values I’ll be given.


When I did some quick benchmark tests, I found no significant speed impairments with this approach — and in fact often times the generic class approach was faster than the array method. 


Everything considered, unless there’s some niche case that calls for using an array, I highly encourage using the generic class approach to return multiple values in Java.