One oft-noted feature of the Java language is it’s lack of a tuple data type. This leads to an abundance of classes that provide the convenience of grouping more than one value into a single object. These classes usually look more or less like this:
public class ObjectPair<A, B> {
private A primary;
private B secondary;
public ObjectPair(A primary, B secondary) {
this.primary = primary;
this.secondary = secondary;
}
public A getPrimary() {
return primary;
}
public B getSecondary() {
return secondary;
}
@Override
public String toString() {
return "(" + primary.toString() + ", " + secondary.toString() + ")";
}
}
Sometimes you’ll see more specific classes like ObjectWithString, but the idea is the same. This approach is fine for many common tasks, but if you find yourself needing multiple cardinalities of tuple, this approach get’s tedious quickly, as you find yourself writing ObjectTriple, ObjectFourTuple, ObjectFiveTuple, and so on. Thinking that it would be smart to have one class that could create a tuple of any length, you might end up being tempted to write a class like this:
public class Tuple<A> {
public static Tuple<A> getTuple(A... args) {
return Tuple(args);
}
private A[] items;
private Tuple(A[] items) {
this.items = items;
}
public A getItem(int index) {
return items[index];
}
}
But this is even worse than what we had before! Not only are we limited to one type of object (which adds the major headache of unsafe casting to a common use of tuples) but we no longer have any idea of how many items our tuple holds. This approach seems to be a real step backwards, as it’s really just a wrapper around an array, which gets rid of any advantage of having a tuple type available.
To make the single-class approach work, we’ll need some way of storing more information in the type of our tuple objects, by taking an idea from functional programming. We define a type that contains information about how long our tuple is, and what type exists at each location. This recursive type is simply defined, but allows us to create tuples of any length:
public abstract class Tuple<T, U extends Tuple> {
public abstract T getItem();
public abstract U getTuple();
public static final <T> Tuple<T, EmptyTuple> getTuple(T item) {
return new SingleItem(item);
}
public static <T, U extends Tuple> Tuple<T, U> getTuple(T item, U tuple) {
return new SingleItemAndTuple<T, U>(item, tuple);
}
public static abstract class EmptyTuple extends Tuple {}
private static class SingleItem extends Tuple<T, EmptyTuple> {
private T item;
public SingleItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
public EmptyTuple getTuple() {
return null;
}
}
private static class SingleItemAndTuple<T, U extends Tuple> extends Tuple<T, U> {
public T item;
public U tuple;
public SingleItemAndTuple(T item, U tuple) {
this.item = item;
this.tuple = tuple;
}
public T getItem() {
return item;
}
public U getTuple() {
return tuple;
}
}
}
Now we have all of the tuple type-safety we could want, at the expense of clumsy notation:
class TupleTest {
public static void main(String[] args) {
Tuple<String, Tuple<Integer, Tuple.EmptyTuple>> tuple = Tuple.getTuple("Test", Tuple.getTuple(Integer.valueOf(1)));
System.out.println(tuple.getItem());
System.out.println(tuple.getTuple().getItem());
}
}