Table of contents
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).
Custom class
Map.Entry
Records (JDK 14+)
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.
// 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.
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.
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.
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