Tuples (Pair…Sept)
Type-safe N-ary tuples — both as legacy nested classes and as modern Java records.
functional-reactive ships two parallel tuple flavours so you can pick whichever fits your JDK target and style:
- Records under
com.svenruppert.functional.tuple— proper Java 14+ records, component accessorst1()/t2()/… - Classic classes nested in
com.svenruppert.functional.model.DataRecords— JDK-8-compatible classes withgetT1()/getT2()/… getters.
Pick records for new code on JDK 14+. Pick DataRecords when targeting JDK 8/11 or when you need to extend a tuple with domain methods (records cannot be extended).
Tuple sizes
Both packages expose: Single, Pair, Triple, Quad, Quint, Sext, Sept — 1- through 7-tuple.
Records (modern)
import com.svenruppert.functional.tuple.Pair;
import com.svenruppert.functional.tuple.Triple;
Pair<String, Integer> entry = new Pair<>("answer", 42);
entry.t1(); // "answer"
entry.t2(); // 42
Triple<String, Integer, Boolean> row = new Triple<>("id", 7, true);
row.t1(); // "id"
Because they are records, you also get equals, hashCode, toString, and full destructuring with Java 21+ pattern matching for free:
if (entry instanceof Pair<String, Integer>(var key, var value)) {
// use key, value directly
}
DataRecords (classic)
import com.svenruppert.functional.model.DataRecords;
DataRecords.Pair<String, Integer> entry =
new DataRecords.Pair<>("answer", 42);
entry.getT1(); // "answer"
entry.getT2(); // 42
Each class also offers a static next(...) factory if you prefer that style:
DataRecords.Pair.next("answer", 42);
DataRecords.Triple.next("id", 7, true);
equals, hashCode and toString are implemented manually inside the classes, so behaviour matches the record variant.
Serializable variants
For tuples that need to cross a serialization boundary (RMI, GWT, Vaadin push, etc.), the classic flavour also exists in a Serializable form:
import com.svenruppert.functional.model.DataRecordsSerializable;
DataRecordsSerializable.Pair<String, Integer> p =
new DataRecordsSerializable.Pair<>("k", 1);
The type parameters are bounded by extends Serializable, so the compiler enforces that everything inside can actually travel.
When to use which
- JDK 14+, no inheritance needed → records (
functional.tuple.*). - Need to extend a tuple with domain getters (e.g.
class Car extends Quint<…>) → classic (DataRecords.*). - Crossing a serialization boundary →
DataRecordsSerializable.*.
Extending tuples for domain semantics
A common pattern with the classic flavour is to extend a tuple to give it meaningful accessors:
public class Car extends DataRecords.Quint<String, Colour, Integer, Integer, Float> {
public Car(String brand, Colour colour, Integer speed, Integer year, Float price) {
super(brand, colour, speed, year, price);
}
public String brand() { return getT1(); }
public Colour colour() { return getT2(); }
public Integer speed() { return getT3(); }
public Integer year() { return getT4(); }
public Float price() { return getT5(); }
}
Records do not allow this — they are implicitly final. If you need a domain-typed accessor with a record, write a wrapper record instead.