A Java collection of value pairs? (tuples?)

Viewed 587507

I like how Java has a Map where you can define the types of each entry in the map, for example <String, Integer>.

What I'm looking for is a type of collection where each element in the collection is a pair of values. Each value in the pair can have its own type (like the String and Integer example above), which is defined at declaration time.

The collection will maintain its given order and will not treat one of the values as a unique key (as in a map).

Essentially I want to be able to define an ARRAY of type <String,Integer> or any other 2 types.

I realize that I can make a class with nothing but the 2 variables in it, but that seems overly verbose.

I also realize that I could use a 2D array, but because of the different types I need to use, I'd have to make them arrays of OBJECT, and then I'd have to cast all the time.

I only need to store pairs in the collection, so I only need two values per entry. Does something like this exist without going the class route? Thanks!

21 Answers

The Pair class is one of those "gimme" generics examples that is easy enough to write on your own. For example, off the top of my head:

public class Pair<L,R> {

  private final L left;
  private final R right;

  public Pair(L left, R right) {
    assert left != null;
    assert right != null;

    this.left = left;
    this.right = right;
  }

  public L getLeft() { return left; }
  public R getRight() { return right; }

  @Override
  public int hashCode() { return left.hashCode() ^ right.hashCode(); }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Pair)) return false;
    Pair pairo = (Pair) o;
    return this.left.equals(pairo.getLeft()) &&
           this.right.equals(pairo.getRight());
  }

}

And yes, this exists in multiple places on the Net, with varying degrees of completeness and feature. (My example above is intended to be immutable.)

You could write a generic Pair<A, B> class and use this in an array or list. Yes, you have to write a class, but you can reuse the same class for all types, so you only have to do it once.

The preferred solution as you've described it is a List of Pairs (i.e. List).

To accomplish this you would create a Pair class for use in your collection. This is a useful utility class to add to your code base.

The closest class in the Sun JDK providing functionality similar to a typical Pair class is AbstractMap.SimpleEntry. You could use this class rather than creating your own Pair class, though you would have to live with some awkward restrictions and I think most people would frown on this as not really the intended role of SimpleEntry. For example SimpleEntry has no "setKey()" method and no default constructor, so you may find it too limiting.

Bear in mind that Collections are designed to contain elements of a single type. Related utility interfaces such as Map are not actually Collections (i.e. Map does not implement the Collection interface). A Pair would not implement the Collection interface either but is obviously a useful class in building larger data structures.

Java 14+ edition

You can create a record which implements equals, hashCode, and toString out of the box. Interfaces like Comparable could also be implemented, if needed.

record Pair<A, B>(A first, B second) {}

Records are immutable.

I was going to ask if you would not want to just use a List<Pair<T, U>>? but then, of course, the JDK doesn't have a Pair<> class. But a quick Google found one on both Wikipedia, and forums.sun.com. Cheers

Spring has a Pair<S,T> type in the Data Utils package org.springframework.data.util

Pair<String,Integer> pair = Pair.of("Test", 123);
System.out.println(pair.getFirst());
System.out.println(pair.getSecond());

First Thing on my mind when talking about key/value pairs is the Properties Class where you can save and load items to a stream/file.

In project Reactor (io.projectreactor:reactor-core) there is advanced support for n-Tuples:

Tuple2<String, Integer> t = Tuples.of("string", 1)

There you can get t.getT1(), t.getT2(), ... Especially with Stream or Flux you can even map the tuple elements:

Stream<Tuple2<String, Integer>> s;
s.map(t -> t.mapT2(i -> i + 2));

You can reuse existing Pair or any such class from "God knows how many libraries already provide such classes". And If you do not want anything from Hans Brende's answer on this question then I don't see any reason for not using 2D Array or List of Object Arrays/ArrayLists used as Pairs/Tuples. The reason for not using Array, you mentioned:

I also realize that I could use a 2D array, but because of the different types I need to use, I'd have to make them arrays of OBJECT, and then I'd have to cast all the time.

Even if you use Pair class from the Accepted Answer, you'll still have to cast the key and value objects. Since you want to store objects of all the types in there. In other words List<Pair> pairs is no different from List<Pair<? extends Object>> pairs which in turn is no different from Object[][2] or List<Object[]> or List<List<Object>>. Because if you write the following code:

List<Pair> pairs = new ArrayList<>();

// logic for populating pairs into list goes here

// then after sometime you need an element
Pair p = pairs.get(whateverIndex);

Object key = p.getKey(); // We don't know type of key, right?
Object value = p.getValue(); // We don't know the exact type here as well

// All sorts of type guessing statemntes go here
GuessedTypeOfKey finallyMyKey = (GuessedTypeOfKey) key;
GuessedTypeOfValue finallyMyValue = (GuessedTypeOfValue) value;

You still have to do the type casting. So I don't find any other reason to not use 2d Object array or List of Object Arrays/ArrayLists used as Pairs/Tuples . Following is code using List and arrays

List<Object[]> simplePairs = new ArrayList<>();

// Add values to pairs
simplePairs.add(new Object[]{1,"One"});
simplePairs.add(new Object[]{"Another Key of Different Type","Value"});
simplePairs.add(new Object[]{"Another Key of Different Type",new AnotherType("Another Value Type")});

// get values
Object[] pair = simplePairs.get(whateverIndex);
Object key = pair[0];
Object value = pair[1];
Related