Find most recent date in a list of objects on LocalDate property using Java 8 stream

11,903

Solution 1

Putting this in an answer for visibility. Based on @nullpointer's answer and @Holger's suggestion I was able to come up with the two following solutions.

Solution 1

Optional<MyClass> mostRecentDate = list.stream()
    .filter(myObject -> myObject.getId() == id &&
     myObject.getCancelDate() == null && myObject.getEarliestDate() != null)
    .max(Comparator.comparing( s -> s.getEarliestDate()
    .toEpochDay())).map(Function.identity());

Solution 2

LocalDate mostRecentDate = list.stream()
    .filter(myObject -> myObject.getId() == id && myObject.getCancelDate() == null)
    .map(MyObject::getEarliestDate)
    .filter(Objects::nonNull)
    .max(LocalDate::compareTo)
    .orElse(null);

Both solutions work but the second solution is cleaner in my opinion and less ambiguous. It removes the .map(Function.identity()) code that doesn't actually do anything as @Holgen pointed out while making use of Java 8's new Method Reference :: . It also filters the list of objects and maps the dates to a new list that then uses .max() with the compareTo() method instead of a custom function. I view the custom function and the useless code as messy and to anyone reading the code, might make it less understandable.

Note in the second solution I've also removed the Optional class. Using .orElse() satisfies returning the actual LocalDate class instead of an option from the stream.

Thanks for the help everyone and I hope this answer helps others.

Solution 2

You're mostly looking out for:

Optional<YourObject> recentObject = list.stream()
        .filter(object -> object.getId() == id && object.getCancelDate() == null && object.getEarliestDate() != null)
        .max(Comparator.comparing(YourObject::getEarliestDate)); // max of the date for recency

From LocalDate.compareTo

Compares this date to another date. The comparison is primarily based on the date, from earliest to latest. It is "consistent with equals", as defined by Comparable.

Share:
11,903
acousticarmor
Author by

acousticarmor

Updated on June 09, 2022

Comments

  • acousticarmor
    acousticarmor almost 2 years

    I have a list of objects which hold multiple properties, one of which is a LocalDate. I'd like to find the object with the most recent date out of this list.

    I'm fairly green with Java 8 and using streams. Like most programming, it seems as though there's more than one way to skin this cat. Here's what I have so far.

    list.stream().filter( object -> object.getId() == id
    && object.getCancelDate() == null 
    && object.getEarliestDate() != null)
    .min( Comparator.comparing( LocalDate::toEpochDay )
    .get();
    

    This gives me "Non-static method cannot be referenced from a static context" for the Comparator function.

    I've looked at possibly creating a map of just the dates from the filtered objects as well and have so far come up with something like this.

    list.stream().filter( object -> object.getId() == id
    && object.getCancelDate() == null 
    && object.getEarliestDate() != null)
    .map( data -> data.getEarliestDate() )
    .collect( Collectors.toList() )
    

    and I'm not really sure where to go from there or if that will even work.

    I know there's an easy way to do this but my brain just isn't connecting the dots.

    Update

    Thanks for the response. I updated my code Optional<YourObject> recentObject = list.stream().filter(object -> object.getId() == id && object.getCancelDate() == null && object.getEarliestDate() != null) .max(Comparator.comparing(s -> s.getEarliestDate().toEpochDay()));

    I now get a compiler error Incompatible types.

    Required:Optional<MyClass>
    Found:Optional<capture<? extends MyClass>>
    

    The method does extend MyClass, so in the type declaration for Optional, do I need to do something like MyClass.class?

    Update 2 Thanks to @Hogen for helping fix the compiler error by adding on the .map() at the end. Here's what it looked like after the change.

    Optional<MyClass> date = 
    list.stream().filter(object -> object.getId() == id &&
    object.getCancelDate() == null &&
    object.getEarliestDate() != null)
    .max(Comparator.comparing( s -> s.getEarliestDate()
    .toEpochDay())).map(Function.identity());
    

    However, I was able to come up with a solution after some help that moves the map to a different spot so that I wouldn't run into the issue of using an extended class.

    Optional<LocalDate> mostRecentDate = list.stream()
    .filter(data -> data.getId() == id && data.getCancelDate() == null)
    .map(MyObject::getEarliestDate)
    .filter(Objects::nonNull)
    .max(LocalDate::compareTo);
    
  • Basil Bourque
    Basil Bourque over 5 years
    Perhaps YourObject should be YourClass.
  • Ousmane D.
    Ousmane D. over 5 years
    good spot in noticing that " I'd like to find the object with the most recent date out of this list." should be done via max instead of min. note that the comparator should be .max(Comparator.comparing(s -> getEarliestDate().toEpochDay()))
  • Naman
    Naman over 5 years
    @Aomine why not compare LocalDate earliestDate itself?
  • Ousmane D.
    Ousmane D. over 5 years
    @nullpointer I've just noticed OP is doing .min( Comparator.comparing( LocalDate::toEpochDay ) in their code example.
  • Naman
    Naman over 5 years
    @BasilBourque true, just didn't had that class definition anywhere.
  • Naman
    Naman over 5 years
    @Aomine but the point is that the comparator wouldn't work in that way...maybe comparingLong could be the exact syntax, but not probably requried for OPs use case.
  • Ousmane D.
    Ousmane D. over 5 years
    @nullpointer comparing would handle longs as well probably not as efficient as comparingLong but yeah I've not used the Date API much in java so i'll leave it to you guys :) .
  • acousticarmor
    acousticarmor over 5 years
    Thank you for the responses. I now get a compiler error Incompatible types. Required:Optional<MyClass> Found:Optional<capture<? extends MyClass>> The method does extend MyClass, so in the type declaration for Optional, do I need to do something like MyClass.class?
  • Holger
    Holger over 5 years
    @acousticarmor so your list is like List<? extends YourObject> list? Then, this type is passed down through all these operations, unfortunately. You can chain a .map(Function.identity()) after the max. This does not do anything, but make the compiler happy (the identity function will just pass the received YourObject reference back, but solves the issue as a function accepting a YourObject as input will also accept subtypes of it (“? extends YourObject”) as input.
  • acousticarmor
    acousticarmor over 5 years
    Ah yes @Holger that did fix the issue. However, and I'll update the comment, I did find another way of doing it that doesn't require the use of that additional map.