ExceptionRaiser
ExceptionRaiser

Reputation: 73

Using Java 8 lambda expressions, filter a List of objects by property of an inner list

I'm new to Java lambda expressions and have been struggling with the following problem:

I'm trying to filter the following List of Activity objects down to the single Activity with the most recent start time:

{
     Name: "activity1"
     Id: "abc-123"
     Fields: [
          { key: "startTime", value: "2018-08-24T12:00:01" },
          { key: "foo", value: "bar" },
          { key: "baz", value: "zoop" }
     ]
},
{
     Name: "activity2"
     Id: "def-456"
     Fields: [
          { key: "bat", value: "zap" },
          { key: "startTime", value: "2018-08-23T12:00:01" },
          { key: "cat", value: "dog" }
     ]
},
{
     Name: "activity3"
     Id: "ghi-789"
     Fields: [
          { key: "flim", value: "flam" },
          { key: "boop", value: "bop" },
          { key: "startTime", value: "2018-08-22T12:00:01" }
     ]
}

Basically, each object has an inner List of fields that appear in no particular order, and I want to filter the parent collection down to one item based on the "startTime" field with the max date.

For the example data, the goal would be to return only the object with the name "activity1". Note that the Field objects appear in no particular order in my data. Also, if the startTime for an activity matches that of another activity, the code should take whichever item comes first in the collection.

Assuming that the activities and fields are in a List, and there are getters/setters on my Activity class, this is what I have started with, but I know it's not correct:

activities().stream().filter(activity ->  
    activity.getFields().stream()
            .filter(field -> field.getKey().equals("startTime"))
            .max((field1, field2) -> {
                Date date1 = Date.from(Instant.parse(field1.getValue()));
                Date date2 = Date.from(Instant.parse(field2.getValue()));
                return date1.compareTo(date2);
            }).isPresent()
).findAny().get();

Any help or an example of how I should approach this would be really appreciated. Thanks!


Edit-1: The actual Activity and Field classes I'm working with are being retrieved from a 3rd party library, and are not of my own design. Here's an idea of what they look like:

public class Activity {
     private String id;
     private String name;
     private List<Field> fields;

     public String getId() {
         return id;
     }

     public void setId(String id) {
         this.id = id;
     }

     public String getName() {
         return name;
     }

     public void setName(String name) {
         this.name = name;
     }

     public List<Field> getFields() {
         return fields;
     }

     public void setFields(List<Field> fields) {
         this.fields = fields;
     }
 }

public class Field {
     private String key;
     private String value;

     public String getKey() {
         return key;
     }

     public void setKey(String key) {
         this.key = key;
     }

     public String getValue() {
         return value;
     }

     public void setValue(String value) {
         this.value = value;
     }
 }

Upvotes: 1

Views: 3132

Answers (1)

jacobm
jacobm

Reputation: 14035

You probably want to define a function that extracts the date, and then use Collectors.maxBy. For instance:

// Somewhere in your class:
Instant startDate(Activity activity) {
  return activity.getFields()
    .stream()
    .filter(field -> field.getKey().equals("startTime"))
    .map(field -> Instant.parse(field.getValue()))
    .findAny()
    .get();
}

// .. and then to use it:
Optional<Activity> maxStartDateActivity = activities()
    .stream()
    .max(Comparator.comparing(MyClass::startDate));

Side note: you probably shouldn't be representing your data the way you are. It's worth the trouble to define an interface for your Activity values that lets you do things like get the parsed start time as a single call that can then be used in a stream pipeline. Doing so will help decouple how activities are represented from what you're trying to do with them, which will make your overall program easier to read, easier to add more features to, and easier to change in the future.

Upvotes: 5

Related Questions