Definitive Guide to Java Pairs - Working with Tuples in Java

Tuples in Java

Tuples are immutable and ordered sequences of elements. In that regard, they're similar to immutable lists - however, commonly, tuples are used to represent pairs in Software Engineering. It's worth noting that they're not limited to being pairs and can be of n-length. Java has great support for immutable lists (and other collections), but not a great one for pairs.

Pairs can either be a key-value mapping between two elements, or simply a pair of two elements that are returned from a method.

Pairs become exceedingly useful when you want to return multiple values from a method. For instance:

mean, std = getStats()

Technically, we can return key-value mappings between elements, or a sequence of 2 elements in Java with a Map or List implementation, but these are awkward to work with, in the context of tuples and pairs:

Map<Float, Float> meanStdMap = getStatsMap();
List<Float> meanStdList = getStatsList();

Neither a Map, nor a List, simply speaking, are meant for this. You can enforce immutability to get the effect of tuples, by making the getStats_() methods return unmodifiable collections:

public static Map<Float, Float> getStatsMap() {
    return Collections.unmodifiableMap(new HashMap<Float, Float>());
}

public static List<Float> getStatsList() {
    return Collections.unmodifiableList(new ArrayList<>());
}

But this ultimately feels like a workaround for an inherently missing feature of the language! Unfortunately, as of writing, all solutions are a workaround for an inherently missing feature, although some are more clunky than others.

In this guide, we'll take a look at how to create and use Tuples in Java - a feature that's not built-in. We'll explore core packages and classes such as Pair and AbstractMap.SimpleImmutableEntry, third-party libraries that code up a simple custom class.

javafx.util.Pair

Note: As of JDK 11, JavaFX isn't shipped with the default JDK download and has become a standalone package. It has to be downloaded/imported as a dependency separately. This makes the use of Pair as a Tuple more cumbersome.

The core solution to the missing Tuple class is a Pair. It resides in the javafx.util package, and was added to represent name-value pairs, which are common in desktop and mobile software development. Although it was originally meant to be used in JavaFX applications - the class is highly generalizable to other domains!

It's as simple as it gets:

import javafx.util.Pair;

// public class Pair<K,V> implements Serializable {...}
Pair<String, Integer> pair = new Pair<>("Mean Value", 25);

Both the K (key) and V (value) are generic, so you can assign any type to them. Both of them can be accessed via their respective getters:

System.out.printf("Key: %s, Value: %s%n", pair.getKey(), pair.getValue());
// Key: Mean Value, Value: 25
System.out.println(pair);
// Mean Value=25

Java inherently cannot return two values from a method, but can return a Pair, which is a wrapper for the two:

public static Pair<String, Integer> getStats() {
    return new Pair<>("Mean Value", 25);
}

A Pair is immutable, as a Tuple would be, so there are no setter functions.

AbstractMap.SimpleImmutableEntry

Another core class you could use is the AbstractMap.SimpleImmutableEntry class, although, this is even more of a workaround than the previous one and is generally not very widely used. It's meant to help create custom map implementations, but in a pinch, can serve as a tuple.

The main benefit of using a SimpleImmutableEntry instead of a Pair is that it comes shipped in all current versions of the JDK, so you don't have to download an external dependency or downgrade your JDK version.

Note: While a SimpleEntry counterpart does exist, it's mutable, so we'll be using the immutable version.

We can rewrite the previous method as:

public static AbstractMap.SimpleImmutableEntry<String, Integer> getStats() {
    return new AbstractMap.SimpleImmutableEntry<>("Mean Value", 25);
}

Which can then be obtained and parsed as:

AbstractMap.SimpleImmutableEntry<String, Integer> stats = getStats();

System.out.printf("Key: %s, Value: %s%n", stats.getKey(), stats.getValue());
// Key: Mean Value, Value: 25
System.out.println(stats);
// Mean Value=25

Apache Commons

Apache Commons is a widely used library present in many Java projects, mainly used for helper/convenience methods and classes that expand the official capabilities of Java. One of these classes is the Pair class, from the lang3 package.

Using Maven, you can add the dependency as:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${version}</version>
</dependency>

Or, if you're using Gradle:

implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

It's worth noting that Apache's Pair class is based on the Map.Entry<K, V>! It implements the interface, and provides a single map-like entry, in the form of a left and right element:

public abstract class Pair<L, R> implements Map.Entry<L, R> ... Serializable {...}

A Pair is created via its of() method:

public static Pair<String, Integer> getStats() {
    return Pair.of("Mean Value", 25);
}

Accessing the elements can be achieved through the getLeft() and getRight() getters, implying it's not a key-value mapping, but a pair-like tuple of two elements:

Pair<String, Integer> stats = getStats();

System.out.printf("Key: %s, Value: %s%n", stats.getLeft(), stats.getRight());
// Left: Mean Value, Right: 25
System.out.println(stats);
// (Mean Value,25)

If you've worked with languages such as Python, this code returns an output more similar to what you might be used to.

Note: Even though Apache Common's implementation doesn't look like a key-value mapping between elements, it does have an interesting setValue() method, which ultimately sets the R (right element) of the Pair, as if it's a value corresponding to a key.

It's also worth noting that this Pair is by default an ImmutablePair, and even though the setValue() method exists publicly - it causes an UnsupportedOperationException:

Pair<String, Integer> stats = getStats();
System.out.println(stats);
stats.setValue(15);
System.out.println(stats);
(Mean Value,25)
Exception in thread "main" java.lang.UnsupportedOperationException
    at org.apache.commons.lang3.tuple.ImmutablePair.setValue(ImmutablePair.java:202)
    at Main.main(Main.java:31)

However, if you use a MutablePair, the operation would succeed.

javatuples

javatuples is an older library, which has seen its latest update in 2011. It's no longer being maintained, but it does work reasonably well as a lightweight library that lets you work around the lack of tuples in Java.

It can be imported as a dependency through Maven:

<dependency>
  <groupId>org.javatuples</groupId>
  <artifactId>javatuples</artifactId>
  <version>1.2</version>
</dependency>

Or Gradle:

implementation 'org.javatuples:javatuples:1.2'

Note: You may have issues importing the dependency through Maven. In that case, download the JAR file manually.

The library offers more than pair tuples! It offers tuples of length 1 to 10 - Unit, Pair, Triplet, ... Decade. All of these classes are type-safe, immutable, serializable and iterable, so you're covered on all fronts.

Unsurprisingly, they work in much the same way we've used other Tuple variants:

public static Pair<String, Integer> getStats() {
    return Pair.with("Mean Value", 25);
}

The values in a Tuple are ultimately stored in a List, with various wrapper methods that let you access them individually as within a Tuple:

Pair<String, Integer> stats = getStats();

System.out.printf("Element_1: %s, Element_2: %s%n", stats.getValue(0), stats.getValue(1));
// Element_1: Mean Value, Element_2: 25
System.out.println(stats);
// ["Mean Value", 25]

Custom Class

Finally, you might choose to implement your own class to represent tuples. Third-party libraries might be off the table for you, or you simply don't want to hassle yourself with downloading any.

The Pair class works reasonably well, and AbstractMap.SimpleImmutableEntry is just odd to use, as it wasn't meant to be used as a pair-like tuple.

Thankfully, it's not too hard to implement something like this, and you may go with a simple wrapper class or more complex. The easiest solution would be to create:

public class Tuple {
    private Object element1;
    private Object element2;
}

However, this solution isn't very malleable. If you're sure to know the return types, or if you're creating a specialized pair as a holder object - this approach would work. However, to create a more general pair-like Tuple in Java, we'd want to do something along the lines of:

public class Tuple<E1, E2> {
    private final E1 e1;
    private final E2 e2;

    public Tuple(E1 e1, E2 e2){
        this.e1 = e1;
        this.e2 = e2;
    }

    public E1 getE1() {
        return e1;
    }

    public E2 getE2() {
        return e2;
    }

    public String toString() {
        return String.format("(%s, %s)", e1, e2);
    }
}

Two generic final elements (immutable), with a constructor that accepts two elements of any type and getters for them. This class can then be used as:

public static Tuple<String, Integer> getStats() {
    return new Tuple("Mean Value", 25);
}

And we can extract data from the tuple as:

Tuple<String, Integer> stats = getStats();

System.out.printf("E1: %s, E2: %s%n", stats.getE1(), stats.getE2());
// E1: Mean Value, E2: 25
System.out.println(stats);
// (Mean Value, 25)

Conclusion

Tuples are immutable and ordered sequences of elements. They're commonly used to represent pairs - tuples of two elements. Java has great support for immutable lists (and other collections), but not a great one for pairs.

In this guide, we've taken a look at what tuples are, and how pairs are a specific type of tuple. We've taken a look at the core packages in Java that can be used to represent a pair of data points, and explored third-party libraries that offer the same basic functionality.

Finally, we implemented our own generic Tuple class that can be used to represent pairs of any two types.

Last Updated: May 19th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

David LandupAuthor

Entrepreneur, Software and Machine Learning Engineer, with a deep fascination towards the application of Computation and Deep Learning in Life Sciences (Bioinformatics, Drug Discovery, Genomics), Neuroscience (Computational Neuroscience), robotics and BCIs.

Great passion for accessible education and promotion of reason, science, humanism, and progress.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms