Java – Returns multiple primitive objects in Java. Not recommended?

Returns multiple primitive objects in Java. Not recommended?… here is a solution to the problem.

Returns multiple primitive objects in Java. Not recommended?

I just started learning OOP programming in Java. I’ve written some programs in C++, and one of the things I miss the most in Java is the possibility of returning multiple values. It is true that C++ functions only return strictly one variable, but we can return more variables using reference parameters. Conversely, we can’t do such things in Java, at least not for primitive types.

The solution I came up with was to create a class that groups the variables I want to return and returns an instance of that class. For example, I need to look up an object in an array, I want to return a boolean value (found or not) and an index. I know I can set the index to -1 if nothing is found, but I think the other way is clearer.

The problem is that I’ve been told that I know a lot more about Java than I know and that I shouldn’t create classes in order to return multiple values even if they are related. He tells that classes should never be used as C++ constructs, just to group elements. He also said that methods should not return non-original objects, they should receive the object from the outside and only modify it. Which of the following is true?

Solution

I shouldn’t create classes for the purpose of returning multiple values

classes should never be used as C++ structs, just to group elements.

methods shouldn’t return non-primitive objects, they should receive the object from the outside and only modify it

This is definitely not the case for any of the above statements. Data objects are useful, and in fact, it’s good practice to separate pure data from classes that contain a lot of logic.

In Java, the closest thing we have to a structure is a POJO (plain old java object), often called a data class in other languages. These classes are just a set of data. A rule of thumb for POJOs is that it should contain only primitives, simple types (strings, boxed primitives, etc.), simple containers (maps, arrays, lists, etc.), or other POJO classes. Basically a class that can be easily serialized.

It is common to pair two, three, or n objects together. Sometimes the data is important enough to warrant a whole new category, while in other cases it is not. In these cases, programmers often use the Pair or Tuple classes. This is a simple example of a generic tuple with two elements.

public class Tuple2<T,U>{
    private final T first;
    private final U second;

public Tuple2(T first, U second) {
        this.first = first;
        this.second = second;
    }

public T getFirst() { return first; }
    public U getSecond() { return second; }
}

A class that uses tuples as part of a method signature might look like this:

public interface Container<T> {
     ...
     public Tuple2<Boolean, Integer> search(T key);
}

One disadvantage of creating such a data class is that for quality of life, we have to implement toString, hashCode, equals getters, setters, constructors, etc. For each tuple of different sizes, you must create a new class (Tuple2, Tuple3, Tuple4, etc.). For these reasons, developers often avoid creating data classes.

Libraries like Lombok can be very helpful in overcoming these challenges. Our definition of Tuple2, plus all the methods listed above, can be written as:

@Data
public class Tuple2<T,U>{
    private final T first;
    private final U second;
}

This also makes it very easy to create custom response classes. Using custom classes avoids generic autoboxing and greatly improves readability. For example:

@Data
public class SearchResult {
    private final boolean found;
    private final int index;
}
...
public interface Container<T> {
     ...
     public SearchResult search(T key);
}

methods should receive the object from the outside and only modify it

This is bad advice. Designing data around immutability is much better. From Effective Java 2nd Edition, p75

Immutable objects are simple. An immutable object can be in exactly one state, the state in which it was created. If you make sure that all constructors establish class invariants, then it is guaranteed that these invariants will remain true for all time, with no further effort on your part or on the part of the programmer who uses the class. Mutable objects, on the other hand, can have arbitrarily complex state spaces. If the documentation does not provide a precise description of the state transitions performed by mutator methods, it can be difficult or impossible to use a mutable class reliably.

Immutable objects are inherently thread-safe ; they require no synchronization. They cannot be corrupted by multiple threads accessing them concurrently. This is far and away the easiest approach to achieving thread safety. In fact, no thread can ever observe any effect of another thread on an immutable object. Therefore, immutable objects can be shared freely.

Related Problems and Solutions