don't worry, it's probably fine

Easy multi-dimensional maps using records

07 Nov 2020

nablopomo java

Now that Records have been as a non-preview feature in Java 16, I’m exploring different ways they can be taken advantage of when March 2021 rolls around.

Records, as containers for multiple types, synthesise equals and hashcode methods under the bonnet. As such, they can be used as multiple dimensional keys in maps, to create the illusions of 2, 3, 4-dimensional tables.

In Practice

Let’s say that you want to store and query all the candidates for an election in a single java.util.Map. There are multiple areas where elections take place, and multiple parties standing within each area.

You could answer the question “Who is the X party candidate for area Y?” with a single lookup using a map with Records as keys.

record AreaAndParty(Area area, Party party) {}
record Candidate(String name, String address) {}

class Example {

    Map<AreaAndParty, Candidate> lookup = new HashMap<>();

    public Candidate lookup(Area area, Party party) {
        return lookup.get(new AreaAndParty(area, party));
    }
}

You could also answer the question “How many areas have candidates from party X?”

record AreaAndParty(Area area, Party party) {}
record Candidate(String name, String address) {}

class Example {

    Map<AreaAndParty, Candidate> lookup = new HashMap<>();

    public long candidatesForParty(Party party) {
        return lookup.keySet()
            .stream()
            .filter(areaAndParty -> areaAndParty.party().equals(party))
            .count();
    }
}

A further generalisation of this approach can give you something much like the Table type available in Google’s Guava library.

record <T, U> Key(T row, U column) {}

class Table<ROW, COLUMN, ENTRY> {

    Map<Key<ROW, COLUMN>, ENTRY> lookup = new HashMap<>();

    public List<ENTRY> selectWhereRow(Predicate<ROW> filter) {
        return lookup.entrySet()
            .stream()
            .filter(entry -> filter.apply(entry.getKey().row()))
            .map(entry -> entry.getValue())
            .collect(Collectors.toList());
    }
}

In conclusion, using records as easy multi-dimensional keys in combination with streaming primitives can result in quite powerful query structures for collections, without the need for lots of object boilerplate.


November is National Blog Posting Month, or NaBloPoMo. I’ll be endeavouring to write one blog post per day in the month of November 2020 - some short and sweet, others long and boring.