# Using a Pair/Tuple type in Java

At times I find myself wanting to use a type such as **tuple** in Python (or Scala) or the `std::pair` from C++ to represent a listing of homogenous or heterogenous values e.g. coordinates `(x,y)`, a value along with its frequency `('b',2)` or some other type of state involving multiple values. There's 4 different ways I've found to accomplish this in Java without needing external libraries (mostly).

1. Custom class
    
2. `Map.Entry`
    
3. Records (JDK 14+)
    
4. `javafx.util.Pair` class (JDK 11 and below)
    

Note: *the same simple example will be used throughout - returning the average score along with letter grade from a student's test scores*. *Also text blocks for printing output, which are included from JDK 15+*

## Custom Class

Define a class containing the amount of items (2 in this case for a pair) and their types.

```java
// Definition
class Tuple<K,V> {
    private final K first;
    private final V second;

    public Tuple(K first, V second){
        this.first = first;
        this.second = second;
    }

    public K first(){return first;}
    public V second(){return second;}
    // other methods, etc ...
}

// Usage
class Main {
    public static void main(String[] args) {
        var studentId = "123-abc";
        var studentScores = fetchScores(studentId);
        var gradeTuple = calcGrade(studentScores);
        var msg = "Grade for student %s: %s(%.2f)".formatted(studentId, gradeTuple.first(), gradeTuple.second());
        System.out.println(msg);
        // e.g. Grade for student 123-abc: A(95.5)
    }

    public Pair<String, Double> calcGrade(List<Double> studentScores) {
        double avg = studentScores.stream()
            .mapToDouble(Double::doubleValue)
            .average().getAsDouble();
        String letterGrade = getGradeFor(score);
        return new Tuple<String, Double>(letterGrade, avg);
    }
}
```

**Pros 👍**

* Relatively quick to define a custom class, and you can customize it exactly to your needs.
    

**Cons 👎**

* Compared to a record, there will be some extra effort needed to implement methods such as `equals()`, `hashcode()`, etc (if class instances need to be compared or sorted)
    

## Map.Entry

Utilize the Map.Entry interface (a nested interface of Map). It represents a single entry from a map i.e. a (key, value) "pairing". In the example below I use the static Map.entry(key,value) method to return an implementation.

```java
class Main {
    public static void main(String[] args) {
        var studentId = "123-abc";
        var studentScores = fetchScores(studentId);
        var gradeEntry = calcGrade(studentScores);
        var msg = "Grade for student %s: %s(%.2f)".formatted(studentId, gradeEntry.getKey(), gradeEntry.getValue());
        // e.g. Grade for student 123-abc: A(95.5)
        System.out.println(msg);
    }

    public Map.Entry<String, Double> calcGrade(List<Double> studentScores) {
        double avg = studentScores.stream()
            .mapToDouble(Double::doubleValue)
            .average().getAsDouble();
        String letterGrade = getGradeFor(score);
        return Map.entry(letterGrade, avg);
    }
}
```

**Pros 👍**

* `Map.Entry` is included in every JDK and there's no need to create additional classes
    

**Cons 👎**

* The semantics might be a little "odd" if what you are trying to model does not have a key/value relationship
    

## Records

Use the record class introduced in JDK 14. It allows for the quick creation of immutable classes that include `toString()`, `hashCode()`, and `equals()` "out of the box". They can be declared locally, or as class variables.

```java
class Main {
    record Tuple<K,V>(K first, V second){}

    public static void main(String[] args) {
        var studentId = "123-abc";
        var studentScores = fetchScores(studentId);
        var gradeTuple = calcGrade(studentScores);
        var msg = "Grade for student %s: %s(%.2f)".formatted(studentId, gradeTuple.first(), gradeTuple.second());
        // e.g. Grade for student 123-abc: A(95.5)
        System.out.println(msg);
    }

    public Map.Entry<String, Double> calcGrade(List<Double> studentScores) {
        double avg = studentScores.stream()
            .mapToDouble(Double::doubleValue)
            .average().getAsDouble();
        String letterGrade = getGradeFor(score);
        return new Tuple<String, Double>(letterGrade, avg);
    }
}
```

**Pros 👍**

* Much less code needs to be written compared to defining a custom class
    
* Records are immutable by default (helps avoid making unintended changes)
    

**Cons 👎**

* Records aren't available in Java versions older than 14
    

## javafx.util.Pair

Use the `Pair` class from the `javafx` package, which was included in the JDK until version 11.

```java
class Main {
    public static void main(String[] args) {
        var studentId = "123-abc";
        var studentScores = fetchScores(studentId);
        var gradePair = calcGrade(studentScores);
        var msg = "Grade for student %s: %s(%.2f)".formatted(studentId, gradeTuple.first(), gradeTuple.second());
        // e.g. Grade for student 123-abc: A(95.5)
        System.out.println(msg);
    }

    public Pair<String, Double> calcGrade(List<Double> studentScores) {
        double avg = studentScores.stream()
            .mapToDouble(Double::doubleValue)
            .average().getAsDouble();
        String letterGrade = getGradeFor(score);
        return new Pair<String, Double>(letterGrade, avg);
    }
}
```

**Pros 👍**

* Functionality provided out of the box
    
* Built-in `equals()`, `toString()`, etc
    

**Cons 👎**

* Pair class is not included in JDK versions after 11
