Search code examples
javajava-stream

Issues with groupingBy method in java streams


I have to create this method getRentals, which given a book id, it should return a SortedMap<String,String>. The map's key is the reader id and the value is a string with this format "DD-MM-YYYY DD-MM-YYYY", these two dates represent the starting and ending date of the rental of the book. I wrote this code but I'm having some issues, I probably haven't understood well how groupingBy works...

public SortedMap<String, String> getRentals(String bookID) throws LibException {
    Book b = this.idsColl.get(bookID);

    SortedMap<String, String> res = b.getRentalsList().stream().collect(Collectors.groupingBy(Rental::getReaderId,
            r->{
                String start = r.getStartDate();
                String end = r.getEndDate();
                return start+" "+end;
            }));
    return null;
}

The idsColl is a map where I associate each unique book id to a book object. These are the attributes of Rental:

private String readerId;
private String bookId;
private String startDate;
private String endDate;

These are the attributes for the book class:

private String copy_id;
private String title;
private boolean rented=false;
private LinkedList<Rental> rentals = new LinkedList<>();

Could you please help me understand what am I doing wrong?

I was expecting to get the map with all the readers who rented the book associated with the string containing the starting and ending dates of the rental. Eclipse gave me this message: The method groupingBy(Function<? super T,? extends K>, Collector<? super T,A,D>) in the type Collectors is not applicable for the arguments (Rental::getReaderId, (<no type> r) -> {}). Also Eclipse seems blaming at the lamba expression.


Solution

  • groupingBy will always collect several elements and combine them into the map values. I think you're better off with toMap. Unfortunately, that leaves you with the overload that also requires a merge function: toMap(keyMapper, valueMapper, mergeFunction, mapFactory). In other words - you don't get the default merge function for free that throws an exception if the key function doesn't result in unique keys.

    public SortedMap<String, String> getRentals(String bookID) throws LibException {
        Book b = this.idsColl.get(bookID);
    
        return b.getRentalsList()
                .stream()
                .collect(Collectors.toMap(
                    Rental::getReaderId,
                    r -> r.getStartDate() + " " + r.getEndDate(),
                    (u, v) -> {
                        throw new IllegalStateException("found non-unique keys");
                    },
                    TreeMap::new
                ));
    }
    

    I got annoyed that I have to write the merge function if I want to provide a supplier, so I wrote stream-utils that has a collector that provides this merge function internally:

    public SortedMap<String, String> getRentals(String bookID) throws LibException {
        Book b = this.idsColl.get(bookID);
    
        return b.getRentalsList()
                .stream()
                .collect(AdditionalCollectors.toMapWithSupplier(
                    Rental::getReaderId,
                    r -> r.getStartDate() + " " + r.getEndDate(),
                    TreeMap::new
                ));
    }