Home > Blockchain >  Java Streams filter two Lists of Maps using ifPresentOrElse()
Java Streams filter two Lists of Maps using ifPresentOrElse()

Time:07-16

I have two lists:

Map<String, String> map1 = new HashMap<>();

map1.put("age", "30");
map1.put("name", "john");

Map<String, String> map2 = new HashMap<>();
map1.put("age", "31");
map1.put("name", "marry");

List<Map<String, String>> list1 = new ArrayList<>();
list1.add(map1);
list1.add(map2);

Map<String, String> map3 = new HashMap<>();
map1.put("age", "40");
map1.put("name", "mike");

Map<String, String> map4 = new HashMap<>();
map1.put("age", "41");
map1.put("name", "terry");

List<Map<String, String>> list2 = new ArrayList<>();
list1.add(map3);
list1.add(map4);

I want to iterate through the list1 and find if age is found. If age is found, it should return the name.

If the target age is not found in the list1, I want to iterate through the list2, and if the result was found return the name.

I'm thinking of something along these lines:

list1.stream()
    .filter(a -> a.get("age").equals("40"))
    .findAny()
    .ifPresentOrElse()

How can I achieve this using streams?

CodePudding user response:

Use the Power of Objects

The usage of maps in your code is an example of abuse of collections. Storing the data in such a way is inconvenient and error-prone.

Instead of trying to substitute a domain object with a Map, you need to define a class (or a record).

It'll give you many advantages that you're depriving yourself by using maps:

  • Ability to use the proper type for every property instead of keeping everything as strings;
  • No need to perform parsing;
  • Self-explanatory method names instead of hard-coded string keys, and your code will not fail because you've misspelled a key;
  • The code is easier to read and maintain.

For the sake of simplicity and conciseness, I'll go with a Java 16 record:

public record Person(String name, int age) {}

And now we have two lists of Person instead of two lists of maps.

To implement the logic when we're starting with examining the first list and only if a result was not found we proceed by iterating through the second list, we can make use of the Java 9 method or() defined by Optional. While invoked on the optional object containing a value, or() returns the same optional, otherwise it'll return an optional produced by a supplier provided as an argument.

For convenience, we can define a method that takes a List<Person> and the target age and returns an optional result.

public static Optional<Person> getPersonByAge(List<Person> people, int age) {
    return people.stream().filter(pers -> pers.age() == age).findFirst();
}

We can make this method reusable by making it generic. So it would expect a List<T> and a Predicate<T> as its arguments.

public static <T> Optional<T> getPersonByAge(List<T> people,
                                             Predicate<T> predicate) {
    
    return people.stream().filter(predicate).findFirst();
}

And that how we can apply it:

List<Person> list1 = List.of(new Person("john", 30), new Person("marry", 31));
List<Person> list2 = List.of(new Person("mike", 40), new Person("terry", 41));
        
Predicate<Person> age40 = pers -> pers.age() == 40;
    
String name = getPersonByAge(list1, age40)
    .or(() -> getPersonByAge(list2, age40))
    .orElseThrow()
    .name();
    
System.out.println(name);

Output:

mike

A link to Online Demo

CodePudding user response:

If you want to use the map so you can:

Optional<String> nameByAge = getNameByAge(list1, "40").or(()->getNameByAge(list2, "40"));

public static Optional<String> getNameByAge(List<Map<String, String>> people, String age) {
        return people.stream().filter(person -> person.get("age").equals(age)).findAny().map(person-> person.get("name"));
    }
  • Related