Suppose you have a list of users, which realistically would come from a database repository.
User alice = new User(1, "alice", "9akDj18xA");
User bob = new User(2, "bob", "mLih41nZxx");
User carol = new User(3, "carol", "tFxn2Ad70");
List<User> users = List.of(alice, bob, carol);
Each user has an id, a name, and a key that can be used to access third party services.
class User {
int id;
String name;
String consentKey;
public User(int id, String name, String consentKey) {
this.id = id;
this.name = name;
this.consentKey = consentKey;
}
}
Sometimes the third party service returns data - a medical record - that looks like this.
class MedicalRecord {
int userId;
String information;
public MedicalRecord(int userId, String information) {
this.userId = userId;
this.information = information;
}
}
But other times, when the key is invalid, it returns null, so we filter those out.
// Calls a third party service that returns data for some users only
List<MedicalRecord> medicalRecords =
users.stream()
.map(user -> Optional.ofNullable(fetchMedicalRecord(user)))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
The task is to generate a report that combines user data with the medical record, but only if the medical record is present. Here is the code that I wrote to do this.
return users.stream()
.filter(
user -> {
Optional<MedicalRecord> medicalRecord =
medicalRecords.stream()
.filter(record -> record.userId == user.id)
.findFirst();
return medicalRecord.isPresent();
})
.map(
user -> {
MedicalRecord medicalRecord =
medicalRecords.stream()
.filter(record -> record.userId == user.id)
.findFirst()
.get();
// Both user and medical record are needed here
return generateReport(user, medicalRecord);
})
.collect(Collectors.toList());
The problem - finding a medical record from the list is done twice: once in filter
, and then in map
. You could shorten the filter to use something like anyMatch
but I am not trying to reduce the number of lines here. What I am looking for is a way to pass both the medical record (if present) and the user to generateReport(user, medicalRecord)
in one go. In theory it might be something that allows map to take two values e.g. map((user, medicalRecord) -> generateReport(user, medicalRecord))
.
Please note this is a simplified example I just made up to illustrate a problem so ignore minor issues outside of the filter/map scope.
If you want a full working example, here are the two functions required to run all this.
private static MedicalRecord fetchMedicalRecord(User user) {
if (!user.consentKey.equals("tFxn2Ad70")) {
String information = String.format("%s's medical record", user.name);
return new MedicalRecord(user.id, information);
} else {
return null;
}
}
private String generateReport(User user, MedicalRecord medicalRecord) {
return String.format("%s's report - %s", user.name, medicalRecord.information);
}