The ultimate java Tuple

The idea came to me via this SO question. The is a question about generics. Probably it’s too much foreshadowing in a technical blog, but this post is about generics too.

Tuples

First thing first: what’s a tuple? It’s a mathematical buzzword for ‘one-thing-and-another’. We call it a pair, right? Mathematicians like to have their definitions generalized. No wonder, the Tuple is a little more than a simple pair.

When you bind together several different things but you’re too lazy to give them a proper name, then it’s a Tuple. Like, two numbers can be a Tuple of two. A number, some text and a URL is a Tuple of three. And so on. Tuples are useful for grouping return values because most of the languages allow only one return value.

Functional languages are heavily influenced by mathematicians. So if you take a look at functional languages, you’ll find Tuples. There are Tuples in Scala, Haskell, SML.

Perl has no tuples but it has a neat assign-to-array syntax. So does Ruby.

As a side note, Javascript is a functional language but it has not tuples. It has a neat inline object syntax instead so you can easily create ad-hoc group of things.

Although Java is adapting functional constructs, there are no tuples in the standard library. Apache commons has Pairs and Triples in their Tuples package. I think it’s worth to check it out.

Rolling our own

As you check the Tuples in several languages, they use different syntax for that. The following two are applicable in Java

  1. You can use objects for Tuples as Scala does.
  2. You can also return an array, just like Perl or Ruby.

Can you imagine to write a code like this?


Object[] fooReturnsTupleOfTwo() {...}

Object[] tupleOfTwo = fooReturnsTupleOfTwo();

// Confusing: tuples start with 1, not zero
Integer firstValue = (Integer) tupleOfTwo[0];
String secondValue = (String) tupleOfTwo[1];

// Oops, exception
String secondValueAgain = (String) tupleOfTwo[2];

This is bad in several levels:

  1. Casting in java is sooo 2004.
  2. You have to remember how much stuff you got in your Tuple.
  3. You are not following Tuple conventions. Other devs will have some headache.

Otherwise it’s fine. No sarcasm, really fine. You can group different things together in a concise way. They are stored and accessed very efficiently.

Unfortunately, the syntax is a bit hairy.

Let’s fix this!

We can define interfaces for Tuples, and implement them with an object array:


public class Pair<T1, T2> {

    private static final Object[] data = new Object[2];

    public Pair() {
        // NOP - creating an empty tuple
    }

    public Pair<T1, T2>(T1 arg1, T2 arg2) {
        data[0] = arg1;
        data[1] = arg2;
    }

    public T1 get1() {
        return (T1) data[0];
    }

    public void set1(T1 value) {
        data[0] = value;
    }
    // ...
}

This tuple provides a Scala-like syntax for tuples. Easy to use, yet quite general. It’s also easy to generalize.

Metaprogramming

Metaprogramming is another fancy word. It means that we’re going to use reflection. The code below highlights that we can retrieve the array index from the method name:

class TupleInvocationHandler implements InvocationHandler {

    private static final Pattern TUPLE_METHOD_PATTERN = Pattern.compile("(get|set)(\\d+)$");
// initialized in the constructors
    private final Object[] values;

    protected void set(Object val, int index) {
        values[index] = val;
    }

    @SuppressWarnings("unchecked")
    protected <T> T get(int index) {
        return (T) values[index];
    }

    @Override
    public Object invoke(Object target, Method method, Object[] args) throws Throwable {

        String methodName = method.getName();
        Matcher m = TUPLE_METHOD_PATTERN.matcher(methodName);
        if (!m.find()) {
            // a fallback when a non-tuple method is called
            return method.invoke(target, args);
        }

        boolean isGet = "get".equals(m.group(1));
        int index = Integer.parseInt(m.group(2)) - 1;

        if (isGet) {
            return get(index);
        }
        set(args[0], index);
        return null; // returning null for void setter
    }

}

Please note that this code is simplified for the sake of brevity. You can check the real version on github.

How to use it?

I wrote a neat API around this regex magic. There are two ways to create a tuple:

// 1. create an empty one
Tuple3<Integer, String, Long> tup3 = Tup.tup3();

// 2. create an initialized one:
Tuple3<Integer, String, Long> tup3 = Tup.tup3(42, "salala", 12147483647L);

Accessing them is the same:

Integer num = tup3.get(1);
tup3.set2("Fourty-six and two.");

When should you use it?

You can use it if you need a quick Tuple implementation up to four items and you’re not really concerned with performance. The performance is the price you pay for using reflection under the hood.

If you want to use a quick Tuple, then you can use apache commons-s Pair and Triple. Those are implemented as objects with two / three fields. Thus they are fast.

If you want to be really quick and general, you can always fall back to the plain old Object array. But careful, dragons live here.

How can you help?

You can always fork my repo on github. It’s not that difficult to add bigger tuples. You’ll see the pattern:

  1. Add a new interface, TupleN that extends TupleN-1
  2. Add some tests
  3. Add new factory methods to
  4. Send fork request

If you like the syntax but don’t like the performance hit, you can fork this project and write the getN and setN methods manually. This reflection-based approach will be there for you to double-check your implementation.

Did you already do it?

Is there a Tuple implementation, probably in guava that I overlooked? Did you write this too, but in a more clever fashion? Please, let me know.

Advertisements

About tamasrev

A software developer, specialized in Java, addressing himself as generalist. A proud daddy.
This entry was posted in java, programming and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s