1

I have this piece of code that filters from a list of objects based on a set of String identifiers passed in and returns a map of string-id and objects. Something similar to follows:

class Foo {
    String id;
    String getId() {return id};
};

// Get map of id --> Foo objects whose string are in fooStr
Map<String,Foo> filterMethod (Set<String> fooStr) {
    List<Foo> fDefs; // list of Foo objects
    Map<String,Foo> fObjMap = new HashMap<String, Foo>(); // map of String to Foo objects


    for (Foo f : fDefs) {
        if (fooStr.contains(f.getId()))
            fObjMap.put(f.getId(),f);
    }
    return (fObjMap);
}

Is there a better Java8 way of doing this using filter or map? I could not figure it out and tried searching on stackoverflow but could not find any hints, so am posting as a question.

Any help is much appreciated. ~Ash

ash
  • 107
  • 1
  • 14

4 Answers4

2

When including items conditionally in the final output use filter and when going from stream to a map use Collectors.toMap. Here's what you end up with:

Map<String,Foo> filterMethod (final Set<String> fooStr) {
   List<Foo> fDefs; // list of Foo objects      
   return fDefs.stream()
        .filter(foo -> fooStr.contains(foo.getId()))
        .collect(Collectors.toMap(Foo::getId, Function.identity()));
}
driangle
  • 11,601
  • 5
  • 47
  • 54
  • Please look at comment to first reply. It suffers from same compilation error. – ash Jul 07 '18 at 00:20
  • The error message was "error: local variables referenced from a lambda expression must be final or effectively final". This means you have to make the variable "fooStr" final, I updated the answer. – driangle Jul 07 '18 at 00:22
  • 1
    @ggreiner there is nothing to do with the extra `final`. It's already `effectively final` – Hearen Jul 07 '18 at 00:24
  • @ash **variables** used in lambda should be `final` or `effectively final`, please check this: https://stackoverflow.com/questions/20938095/difference-between-final-and-effectively-final – Hearen Jul 07 '18 at 00:31
2

Though ggreiner has already provided a working solution, when there are duplicates you'd better handle it including a mergeFunction.

Directly using Collectors.toMap(keyMapper, valueMapper), one or another day you will encounter this following issue.

If the mapped keys contains duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.

Based on the OP's solution, I think it would be better using

import static java.util.stream.Collectors.*; // save some typing and make it cleaner;

fDefs.stream()
     .filter(foo -> fooStr.contains(foo.getId()))
     .collect(toMap(Foo::getId, foo -> foo, (oldFoo, newFoo) -> newFoo));
Hearen
  • 7,420
  • 4
  • 53
  • 63
2

Just use the filter operator with the same predicate as above and then the toMap collector to build the map. Also notice that your iterative solution precludes any possibility of key conflict, hence, I have omitted that, too.

Map<String, Foo> idToFooMap = fDefs.stream()
    .filter(f -> fooStr.contains(f.getId()))
    .collect(Collectors.toMap(Foo::getId, f -> f));
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
0

Maybe something like this?

Map<String,Foo> filterMethod (Set<String> fooStr) {
    List<Foo> fDefs; // get this list from somewhere
    Map<String, Foo> fObjMap = new HashMap<> (); 

    fDefs.stream()
        .filter(foo -> fooStr.contains(foo.getId()))
        .forEach(foo -> fObjMap.put(foo.getId(), foo))

    return fObjMap;
}
  • I get compilation error at the line: "error: local variables referenced from a lambda expression must be final or effectively final" at the fooStr.contains(foo.getId())) – ash Jul 06 '18 at 23:54
  • 1
    The compilation error can be resolved by doing something like fDef.stream().map(foo -> foo.GetId()).filter(fooStr::contains)) But not sure how to get the foo object in forEach?? – ash Jul 07 '18 at 00:19