Returning multiple values from a method in Java
Problem
Sometimes it is necessary to return multiple values from a method. When you search the Internet for possible solutions, then you find a couple of suggestions:- Return an
Object[]
orList<Object>
. - Modify an
Object[]
orList<Object>
parameter. - Return a
Tuple<>
with the values. - Return a proper Object with the values as attributes.
- etc.
3. is at least typesafe but, if for example, you want to return multiple strings, then you have to know which tuple attribute contains what value.
4. is probably the cleanest solution but it means that you might end up with a lot of small "Result" classes.
Approach
Other curly brace languages like C++ or C# have built-in call-by-reference mechanism for this situation. C++ has reference parameter (&,
&&) or pointer parameter (
&,
*). C# has the
outand
refparameter modifiers.
Java on the other hand, has only call-by-value and so it is not possible to modify a given parameter directly. But if you call a method with an modifiable object, then you can modify the given object and store additional information. if we generalize this approach then we need a class which allows us to:
- Set a value in the called method.
- Get the value in the caller method.
outparameters are only assigned in the called method and are not used to deliver a value to the called method.
So let's try with a very simple generic
Out<T>:
// Out.java
public class Out<T> {
private T _value = null;
public void set(T value)
{
_value = value;
}
public T get()
{
return _value;
}
}
Examples
The following examples show howOut<T>can be used.
A simple example is splitting a string at a specified separator:
// SplitString.java
public class SplitString {
public static void main(String[] arguments)
{
String keyValue = "language=java";
Out<String> key = new Out<>();
Out<String> value = new Out<>();
assertTrue(splitString(keyValue, '=', key, value));
assertEquals("language", key.get());
assertEquals("java", value.get());
}
public static boolean splitString(String s, char separator, Out<String> before, Out<String> after)
{
int index = s.indexOf(separator);
if (index >= 0) {
before.set(s.substring(0, index));
after.set(s.substring(index + 1));
}
return index >= 0;
}
}
Another example simulates a technique which is used quite often in C++. You call a
function which returns an error indicator and in case of an error fills an
error_code. In the Java case we fill an exception output parameter:
// OpenStream.java
public class OpenStream {
public static void main(String[] arguments) throws IOException
{
Out<IOException> streamException = new Out<>();
try (InputStream stream = tryOpenStream("somefile.txt", streamException)) {
if (stream == null)
System.out.printf("Couldn't open stream because %s%n", streamException.get().getMessage());
}
}
public static InputStream tryOpenStream(String fileName, Out<IOException> streamException)
{
try {
return new FileInputStream(fileName);
} catch (IOException exception) {
streamException.set(exception);
return null;
}
}
}
As a last example I'll show how the C# TryParsefunction could be implemented in Java:
// Parse.java
public class Parse {
public static void main(String[] arguments)
{
String number = "12345";
Out<Integer> integer = new Out<>();
assertTrue(tryParse(number, integer));
assertEquals(12345, integer.get().intValue());
}
public static boolean tryParse(String number, Out<Integer> integer)
{
try {
integer.set(Integer.parseInt(number));
return true;
} catch (NumberFormatException exception) {
return false;
}
}
}
Implementation
This is theOut<T>version which you can find in my JavaAidKit library. This version however is more strict and doesn't allow
nullvalues:
Out<T>::set()
will throw aNullPointerException
when trying to setnull
.Out<T>::get()
will throw aNoSuchElementException
when a value hasn't been set.
package com.pera_software.aidkit.lang;
import java.util.*;
/**
* A class which can be used to return a value from a method via a parameter.
*
* {@snippet :
* public static boolean splitString(String s, char separator, Out<String> before, Out<String> after)
* {
* int index = s.indexOf(separator);
* if (index >= 0) {
* before.set(s.substring(0, index));
* after.set(s.substring(index + 1));
* }
* return index >= 0;
* }
*
* }
*
* @author P. Most
* @see com.pera_software.aidkit.lang.InOut
*/
public class Out<T> {
private T _value = null;
/**
* No initializing constructor because {@code out} parameters are only supposed to be set in the
* called method.
*/
public Out()
{
}
/**
* Sets the value if not null, otherwise throws a {@code NullPointerException}.
* @param value the non-null value to set
* @throws NullPointerException if value is null
*/
public void set(T value)
{
if (value != null)
_value = value;
else
throw new NullPointerException();
}
/**
* If a value is present, returns the value, otherwise throws {@code NoSuchElementException}.
* @throws NoSuchElementException if there is no value present
* @return the non-null value held
*/
public T get()
{
if (_value != null)
return _value;
else
throw new NoSuchElementException();
}
@Override
public String toString()
{
return _value != null ? _value.toString() : "";
}
public String toDebugString()
{
return String.format("Out<%s>: '%s'",
_value != null ? _value.getClass().getSimpleName() : "null",
_value != null ? _value : "null");
}
}
Related
The JavaAidKit library also contains anInOut<T>type, which extends
Out<T>and contains an additional constructor which allows to set an initial value. Because
InOut<T>extends
Out<T>you can use it whenever an
Out<T>is expected.
Tips
- As a callee make sure that the
Out<T>
parameter gets a value and/or give the caller some indication that theOut<T>
parameter wasn't set. (The C# compiler will complain if you don't assign to anout
parameter, but Java and C++ don't!) - As a caller consider to use
InOut<T>
and initialize it with a default value. This will preventNoSuchElementException
if the callee didn't set a value.
Notes
After writingOut<T>and
InOut<T>I've found this article Simulating C# ref parameter in Java.
Comments
Comments aren't available on this site, but there is a reddit thread.Written and © by P. Most ()