July 20, 2018

Tuple in Java

Tuple is a pair of two or more elements. This kind of abstraction is rarely used in Java and there is real reason for it.

In short, using tuples in Java should be avoided!

Examples of possible tuples

There is a lot of places and examples when tuple could be suitable, but should not. For example:

  1. Point in two-dimensional space is a pair of coordinates, e.g. x and y. And it could be represented as tuple.
  2. Returning two or more values from function could require usage of tuple. This is very common pattern for (error handling in Go)[https://blog.golang.org/error-handling-and-go].
  3. A lot of things which should be passed through function call stack.

Why these reasons could be bad for using tuples?

Tuple complicates

Hides concept and decreases readability

The tuple hides an idea or a concept behind data. One example from Scala:

val point = (5, 2)
(5,2): scala.Tuple2

point._1
// 5
point._2
// 2

// ...

val(x, y) = point

Using such kind of API is hard. Which field represents x and which represents y? In case if tuple is used as function parameter, only parameter name can tell something about data.

Python addresses issue with readability using named tuples. Instead of using:

def get_name():
    return "Richard", "Xavier", "Jones"

name = get_name()

# no idea what these indexes map to!
print(name[0], name[1], name[2])

Python allows to use named tuples:

from collections import namedtuple

def get_name():
    name = namedtuple("name", ["first", "middle", "last"])
    return name("Richard", "Xavier", "Jones")

name = get_name()

# much easier to read
print(name.first, name.middle, name.last)

Which are far more readable, but type of the variable is still and not a person full name. Issue with type can be addressed by using Value Objects instead of simple types. E.g.:

public Tupple<ScheduledTime, EstimatedTime> arrivalTime(PlaneId planeId);

Is more readable than:

public Tupple<DateTime, DateTime> arrivalTime(PlaneId planeId);

Hides bad function design

In case of returning 2 or more values from function. Most of the time design of function should be reconsidered. Probably function performs 2 different and unrelated tasks.

There is a case in Go language for error handling:

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// do something with the open *File f

This is design choice and common pattern in language, which can be considered as accepted. In Scala there is an Either analog to Go:

import scala.io.Source
import java.net.URL
def getContent(url: URL): Either[String, Source] =
  if (url.getHost.contains("google"))
    Left("Requested URL is blocked for the good of the people!")
  else
    Right(Source.fromURL(url))

And usage:

getContent(new URL("http://google.com")) match {
  case Left(msg) => println(msg)
  case Right(source) => source.getLines.foreach(println)
}

In Java there are exceptions for such cases. And both cases (Go and Scala) can be implemented in Java. This is not about better or worse. This is only about acceptable case of returning tuple from function.

By the way if tuple is returned from private method and exposed to API, such approach also can be acceptable. But in public API it may seem strange:

public Tupple<DateTime, DateTime> arrivalTime(PlaneId planeId);

What does method arrivalTime return? But in case:

public ArrivalTime arrivalTime(PlaneId planeId);

With:

var arrivalTime = arrivalTime(plane.id());

// ...

print(arrivalTime.estimated());
print(arrivalTime.scheduled());

Specific and verbose data type should be always preferred to obscure tuple.

Fragile architecture

In case if tuples used across all code, architecture can be very fragile. Adding one more element to pair converts it to another type (triple) in most languages and refactoring is required. It could take a lot of time if tuple is favorite developer tool.

The tuple is compromise between static and dynamic typing. Static version of tuple is class with concrete fields and concrete concept in domain, dynamic version is tuple itself.

Pair in Java

Typed Pair

The simplest and fastest implementation:

class Tuple2<T1, T2> {
    final T1 _1;
    final T2 _2;

    Tuple2(T1 _1, T2 _2) {
        this._1 = _1;
        this._2 = _2;
    }
}

Gives:

var language = new Tuple2<>("Java", 2);

System.out.println(language._1);
// Java
System.out.println(language._2);
// 2

There is no implementation of equals, hashCode, toString, and Serializable. In case if there is need for it, it is easy to implement:

class Tuple2<T1, T2> {
    final T1 _1;
    final T2 _2;

    Tuple2(T1 _1, T2 _2) {
        this._1 = _1;
        this._2 = _2;
    }

    @Override
    public String toString() {
        return "(" + _1 + "," + _2 + ")";
    }

    @Override
    public int hashCode() {
        return _1.hashCode() ^ _2.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        Tuple2<?, ?> tuple2 = (Tuple2<?, ?>) o;

        return Objects.equals(_1, tuple2._1) && Objects.equals(_2, tuple2._2);
    }
}

And usage:

var language = new Tuple2<>("Java", 2);

System.out.println(language);
// (Java,2)
System.out.println(language.equals(new Tuple2<>("Scala", 1)));
// false
System.out.println(language.equals(new Tuple2<>("Java", 2)));
// true

Tuple, Triple and Friends

Triple and friends:

class Tuple3<T1, T2, T3> {
    final T1 _1;
    final T2 _2;
    final T3 _3;

    Tuple2(T1 _1, T2 _2, T3 _3) {
        this._1 = _1;
        this._2 = _2;
        this._3 = _3;
    }
}

There is problem with generalization of arity of tuples in Java and implementations should be repeated or other way to implement should be found. E.g. store fields as array and implement methods based on array logic.

Utility methods

Other usable methods could be added to Tuple API to simplify developer life, e.g. map:

class Tuple2<T1, T2> {
    // ...

    public <A1, A2> Tuple2<A1, A2> map(BiFunction<T1, T2, Tuple2<A1, A2>> mapper) {
        return mapper.apply(_1, _2);
    }

    // ...
}

Or swap:

class Tuple2<T1, T2> {
    // ...

    public Tuple2<T2, T1> swap() {
        return new Tuple2<>(_2, _1);
    }

    // ...
}

Usage:

var language = new Tuple2<>("Java", 2);

System.out.println(language.swap());
// (2, Java)
System.out.println(language.map((x, y) -> new Tuple2<>(x.toUpperCase(), y - 1)));
// (JAVA, 1)

Libraries and known implementations

There is no need to implement tuples in Java, there is a lot of implementations.

Built-in map element

There is built-in possibility to use tuples (Java 9 version code):

var ageOfPerson = Map.entry("John", 16);

System.out.println(ageOfPerson);
// John=20

API provides method to setValue, but it will throw UnsupportedOperationException.

In earlier versions of Java, element can be created directly by instantiating AbstractMap.SimpleEntry<K, V> or AbstractMap.SimpleImmutableEntry<K, V>:

AbstractMap.SimpleEntry<Integer, String> entry = new AbstractMap.SimpleEntry<>(10, "ten");

AbstractMap.SimpleEntry allows to change values compared to its immutable friend - AbstractMap.SimpleImmutableEntry.

Based on arrays

Built-in old fashioned, but stable Java arrays, also could be to used to return multiple values from function:


private Double[] updateBalance(double delta) {
    // ...
    return new Double[] {currentBalance, previousBalance};
}

But in case of heterogeneous tuple, Object type should be used in pair with unboxing:

var catalogRecord = new Object[] {"BMW", "M3", 1.8, 20000};

System.out.println(catalogRecord);
// [Ljava.lang.Object;@47d384ee

int price = (int) catalogRecord[3];
System.out.println(price);
// 20000

Equals:

var catalogRecord1 = new Object[] {"BMW", "M3", 1.8, 20000};
var catalogRecord2 = new Object[] {"BMW", "M3", 1.8, 20000};

System.out.println(catalogRecord1.equals(catalogRecord2));
// false

System.out.println(Arrays.equals(catalogRecord1, catalogRecord2));
// true

Downsides are:

  1. Array is mutable.
  2. toString does not represents value.
  3. Should remember using Arrays.equals instead of equals, but IDE can recommend this, e.g. IntelliJ IDEA.

javatuples

There is an old library for tuples and more complex types - javatuples, the latest release was in October 15th, 2011.

It supports types from 1 to 10 elements (Unit, Pair, Triple, …, Decade). All tuple types are typesafe, immutable, iterable, serializable, comparable (implements Comparable), implementing equals(…), hashCode() and toString().

Example of creating triple:

Triplet<String, Integer, Double> triplet = Triplet.with("BMW", 20000, 1.8);

Or (as in Java 10):

var triplet = Triplet.with("BMW", 20000, 1.8);

And usage:

String brandName = triplet.getValue0();
Integer price = triplet.getValue1();
Double engineVolume = triplet.getValue2();

A fun fact is that tuples in this library also support iteration, like collections and arrays:

for (Object element : decade) {
   ...
}

There is also a lot of additional methods to work with tuples, e.g. convert pair to triple by adding elements (without mutation).

Quite built-in javafx.util (JDK 11)

First of all, starting from JDK 11 JavaFX will be remove from standard library and will be available as standalone download.

It is an immutable pair which is also serializable, have equals and hashCode methods implemented.

var point = new Pair<>(5, 10);

System.out.println(point);
// 5=10

System.out.println(point.getKey());
// 5
System.out.println(point.getValue());
// 10

As has been said, tuples lose for readability versus specific types, point.getKey() is not clear as point.x().

Apache Commons

In case if Apache Commons library has been already integrated into a project. It is very cheap to use its implementation of tuples.

There are several classes in apache.common.lang3 for such case: ImmutablePair<L,R>, ImmutableTriple<L,M,R>, MutablePair<L,R>, MutableTriple<L,M,R>.

Example of using ImmutableTriple<L,M,R>:

var triple = new ImmutableTriple<>(1, 2, 3);

var left = triple.getLeft();
var middle = triple.getMiddle();
var triple = triple.getRight();

Vavr

There is a beautiful library Vavr, which brings functional programming idioms to Java. It has high code coverage with tests and elegant API:

var numbers = Tuple.of(5, 10);

System.out.println(numbers._1);
// 5

System.out.println(numbers._2);
// 10

var mapped = numbers.map(
        a -> a * 20,
        b -> b * 20
);

System.out.println(mapped);
// (100, 200)

Scala tuples

For academic sophistication:

import scala.Tuple2;

public class Main {
    public static void main(String[] args) {
        var scalaTuple = Tuple2.apply("Scala", 1);

        System.out.println(scalaTuple);
        // (Scala,1)
        System.out.println(scalaTuple.swap());
        // (1,Scala)
        System.out.println(scalaTuple._1);
        // Scala
        System.out.println(scalaTuple._2);
        // 1
    }
}

Scala language library must be included into the project.

Performance considerations

In case of Java implementations tuple takes more memory than should, it represents 2 elements, but takes memory for 3 -
for 2 elements and for itself as object. There are not any performance gains for using tuples compared to using tuples in functional programming languages where they can save memory and improve performance.

There is also proposition to introduce values to world of JVM. This issue addresses performance issues and optimizations.

When to use

  1. In private methods.
  2. In local calculations.
  3. In languages with syntax sugar and type aliases.

When not to use

Using tuples in Java should be avoided, brief and readable concrete types are more preferable.

Shoulders of Atlases

  1. https://stackoverflow.com/a/24336841
  2. http://cr.openjdk.java.net/~jrose/values/values-0.html
  3. https://www.javatuples.org/using.html
  4. https://javax0.wordpress.com/2015/04/22/sometimes-you-need-tuples-in-java-or-not/
  5. https://stackoverflow.com/questions/13977236/tuple-unmodifiable-ordered-list-of-heterogeneous-elements-support-in-java
  6. https://stackoverflow.com/questions/156275/what-is-the-equivalent-of-the-c-pairl-r-in-java/156685#156685
  7. https://github.com/google/guava/wiki/IdeaGraveyard
  8. https://danielwestheide.com/blog/2012/12/26/the-neophytes-guide-to-scala-part-6-error-handling-with-try.html
  9. https://softwareengineering.stackexchange.com/questions/166908/a-way-to-return-multiple-return-values-from-a-method-put-method-inside-class-re
  10. https://docs.quantifiedcode.com/python-anti-patterns/readability/not_using_named_tuples_when_returning_more_than_one_value.html
  11. http://www.baeldung.com/java-pairs