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
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));
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()));
}
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));
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;
}
Related
I have an object Foo that has references to Bar and Baz objects:
public class Foo {
private Bar bar;
private Baz baz;
public Foo(Bar bar, Baz baz) {
this.bar = bar;
this.baz = baz;
}
}
I have a List<Foo> that I'd like to convert into a Map. I'd like the key to be Bar and the value to be a List<Baz>.
I can create a Map where Bar is the key and the value is a List<Foo>:
Map<Bar, List<Foo>> mapBarToFoos = foos.stream().collect(Collectors.groupingBy(Foo::getBar));
I don't know how to take that last step and turn the value List into a List<Baz>. Is there a lambda conversion on the value that I'm not seeing?
I think you want
list.stream().collect(groupingBy(Foo::getBar,
mapping(Foo::getBaz, toList())));
Where getBaz is the "downstream collector" which transforms the grouped Foos, then yet another which creates the list.
You're close, you'll need to supply a "downstream" collector to further refine your criteria. in this case the groupingBy approach along with a mapping downstream collector is the idiomatic approach i.e.
list.stream().collect(groupingBy(Foo::getBar, mapping(Foo::getBaz, toList())));
This essentially works by applying a mapping downstream collector to the results of the classification (Foo::getBar) function.
basically what we've done is map each Foo object to Baz and puts this into a list.
Just wanted to show another variant here, although not as readable as the groupingBy approach:
foos.stream()
.collect(toMap(Foo::getBar, v -> new ArrayList<>(singletonList(v.getBaz())),
(l, r) -> {l.addAll(r); return l;}));
Foo::getBar is the keyMapper function to extract the map keys.
v -> new ArrayList<>(singletonList(v)) is the valueMapper
function to extract the map values.
(l, r) -> {l.addAll(r); return l;} is the merge function used to
combine two lists that happen to have the same getBar value.
To change the classic grouping to a Map<Bar, List<Foo>> you need to use the method which allows to change the values of the map :
the version with a downstream Collectors.groupingBy(classifier, downstream)
which is a mapping operation Collectors.mapping(mapper, downstream)
which requires another operation to set the container Collectors.toList()
//Use the import to reduce the Collectors. redundancy
//import static java.util.stream.Collectors.*;
Map<Bar, List<Baz>> mapBarToFoos =
foos.stream().collect(groupingBy(Foo::getBar, mapping(Foo::getBaz, toList())));
//without you'll get
Map<Bar, List<Baz>> mapBarToFoos =
foos.stream().collect(Collectors.groupingBy(Foo::getBar, Collectors.mapping(Foo::getBaz, Collectors.toList())));
I have a collection of pojos:
public class Foo {
String name;
String date;
int count;
}
I need to iterate over collection, groupBy Foos by name and sum counts, then create new collection with pojos with summed count.
Here is how I do it now:
List<Foo> foosToSum = ...
Map<String, List<Foo>> foosGroupedByName = foosToSum.stream()
.collect(Collectors.groupingBy(Foo::getName));
List<Foo> groupedFoos = foosGroupedByName.keySet().stream().map(name -> {
int totalCount = 0;
String date = "";
for(Foo foo: foosGroupedByName.get(name)) {
totalCount += foo.getCount();
date = foo.getDate() //last is used
}
return new Foo(name, date, totalCount);
}).collect(Collectors.toList());
Is there a more beauty way to do it with streams?
UPDATE Thanks everyone for help. All answers were great.
I decided to create merge function in pojo.
The final solution looks like:
Collection<Foo> groupedFoos = foosToSum.stream()
.collect(Collectors.toMap(Foo::getName, Function.identity(), Foo::merge))
.values();
You can do it either using groupingBy or using toMap collector, as for which to use is debatable so I'll let you decide on the one you prefer.
For better readability, I'd create a merge function in Foo and hide all the merging logic inside there.
This also means better maintainability as the more complex the merging gets, you only have to change one place and that is the merge method, not the stream query.
e.g.
public Foo merge(Foo another){
this.count += another.getCount();
/* further merging if needed...*/
return this;
}
Now you can do:
Collection<Foo> resultSet = foosToSum.stream()
.collect(Collectors.toMap(Foo::getName,
Function.identity(), Foo::merge)).values();
Note, the above merge function mutates the objects in the source collection, if instead, you want to keep it immutable then you can construct new Foo's like this:
public Foo merge(Foo another){
return new Foo(this.getName(), null, this.getCount() + another.getCount());
}
Further, if for some reason you explicitly require a List<Foo> instead of Collection<Foo> then it can be done by using the ArrayList copy constructor.
List<Foo> resultList = new ArrayList<>(resultSet);
Update
As #Federico has mentioned in the comments the last merge function above is expensive as it creates unnecessary objects that could be avoided. So, as he has suggested, a more friendly alternative is to proceed with the first merge function I've shown above and then change your stream query to this:
Collection<Foo> resultSet = foosToSum.stream()
.collect(Collectors.toMap(Foo::getName,
f -> new Foo(f.getName(), null, f.getCount()), Foo::merge))
.values();
Yes, you could use a downstream collector in your groupingBy to immediately sum the counts. Afterwards, stream the map and map to Foos.
foosToSum.stream()
.collect(Collectors.groupingBy(Foo::getName,
Collectors.summingInt(Foo::getCount)))
.entrySet()
.stream()
.map(entry -> new Foo(entry.getKey(), null, entry.getValue()))
.collect(Collectors.toList());
A more efficient solution could avoid grouping into a map only to stream it immediately, but sacrifices some readability (in my opinion):
foosToSum.stream()
.collect(Collectors.groupingBy(Foo::getName,
Collectors.reducing(new Foo(),
(foo1, foo2) -> new Foo(foo1.getName(), null, foo1.getCount() + foo2.getCount()))))
.values();
By reducing Foos instead of ints, we keep the name in mind and can immediately sum into Foo.
I have this Map:
Map<Integer, Set<String>> map = ...
And I have this class Foo:
class Foo {
int id;
String name;
}
I want to convert the map to List<Foo>. Is there a convenient manner in Java 8 to do this?
Currently, my way is:
List<Foo> list = new ArrayList<>((int) map.values().flatMap(e->e.stream()).count()));
for(Integer id : map.keySet()){
for(String name : map.get(id)){
Foo foo = new Foo(id,name);
list.add(foo);
}
}
I feel it's too cumbersome.
You can have the following:
List<Foo> list = map.entrySet()
.stream()
.flatMap(e -> e.getValue().stream().map(name -> new Foo(e.getKey(), name)))
.collect(toList());
For each entry of the map, we create a Stream of the value and map it to the corresponding Foo and then flatten it using flatMap.
The main reason for your version being cumbersome is that you have decided to calculate the capacity of the ArrayList first. Since this calculation requires iterating over the entire map, there is unlikely to be any benefit in this. You definitely should not do such a thing unless you have proved using a proper benchmark that it is needed.
I can't see anything wrong with just using your original version but with the parameterless constructor for ArrayList instead. If you also get rid of the redundant local variable foo, it's actually fewer characters (and clearer to read) than the stream version.
final Map<Integer, Set<String>> map = new HashMap<>();
map
.entrySet()
.stream()
.flatMap(e -> e.getValue().stream().map(s -> new Foo(e.getKey(), s)))
.collect(Collectors.toList());
I've current got a method which looks like:
public Map<Long, List<ReferralDetailsDTO>> getWaiting() {
return referralDao.findAll()
.stream()
.map(ReferralDetailsDTO::new)
.collect(Collectors.groupingBy(ReferralDetailsDTO::getLocationId, Collectors.toList()));
}
}
It returns me a Map of location IDs to ReferralDetailsDTO objects. However, I'd like to swap out the location ID for the LocationDTO object.
I'd have naively imagined something like this might work:
public Map<Long, List<ReferralDetailsDTO>> getWaiting() {
return referralDao.findAll()
.stream()
.map(ReferralDetailsDTO::new)
.collect(Collectors.groupingBy(locationDao.findById(ReferralDetailsDTO::getLocationId), Collectors.toList()));
}
Obviously, I'm here because it doesn't - Java complains the findById method is expecting a Long value, not the method reference. Any suggestions for how I can neatly address this? Thanks in advance.
First of all, change the key type of the Map from Long to your relevant class (is it LocationDTO or some other class?)
Second of all, use a lambda expression instead of method reference for the lookup :
public Map<LocationDTO, List<ReferralDetailsDTO>> getWaiting() {
return referralDao.findAll()
.stream()
.map(ReferralDetailsDTO::new)
.collect(Collectors.groupingBy(r -> locationDao.findById(r.getLocationId()));
}
This question already has answers here:
How to convert List to Map?
(20 answers)
Closed 7 years ago.
I would like to find a way to take the object specific routine below and abstract it into a method that you can pass a class, list, and fieldname to get back a Map.
If I could get a general pointer on the pattern used or , etc that could get me started in the right direction.
Map<String,Role> mapped_roles = new HashMap<String,Role>();
List<Role> p_roles = (List<Role>) c.list();
for (Role el : p_roles) {
mapped_roles.put(el.getName(), el);
}
to this? (Pseudo code)
Map<String,?> MapMe(Class clz, Collection list, String methodName)
Map<String,?> map = new HashMap<String,?>();
for (clz el : list) {
map.put(el.methodName(), el);
}
is it possible?
Using Guava (formerly Google Collections):
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, Functions.toStringFunction());
Or, if you want to supply your own method that makes a String out of the object:
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
public String apply(Role from) {
return from.getName(); // or something else
}});
Here's what I would do. I am not entirely sure if I am handling generics right, but oh well:
public <T> Map<String, T> mapMe(Collection<T> list) {
Map<String, T> map = new HashMap<String, T>();
for (T el : list) {
map.put(el.toString(), el);
}
return map;
}
Just pass a Collection to it, and have your classes implement toString() to return the name. Polymorphism will take care of it.
Java 8 streams and method references make this so easy you don't need a helper method for it.
Map<String, Foo> map = listOfFoos.stream()
.collect(Collectors.toMap(Foo::getName, Function.identity()));
If there may be duplicate keys, you can aggregate the values with the toMap overload that takes a value merge function, or you can use groupingBy to collect into a list:
//taken right from the Collectors javadoc
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
As shown above, none of this is specific to String -- you can create an index on any type.
If you have a lot of objects to process and/or your indexing function is expensive, you can go parallel by using Collection.parallelStream() or stream().parallel() (they do the same thing). In that case you might use toConcurrentMap or groupingByConcurrent, as they allow the stream implementation to just blast elements into a ConcurrentMap instead of making separate maps for each thread and then merging them.
If you don't want to commit to Foo::getName (or any specific method) at the call site, you can use a Function passed in by a caller, stored in a field, etc.. Whoever actually creates the Function can still take advantage of method reference or lambda syntax.
Avoid reflection like the plague.
Unfortunately, Java's syntax for this is verbose. (A recent JDK7 proposal would make it much more consise.)
interface ToString<T> {
String toString(T obj);
}
public static <T> Map<String,T> stringIndexOf(
Iterable<T> things,
ToString<T> toString
) {
Map<String,T> map = new HashMap<String,T>();
for (T thing : things) {
map.put(toString.toString(thing), thing);
}
return map;
}
Currently call as:
Map<String,Thing> map = stringIndexOf(
things,
new ToString<Thing>() { public String toString(Thing thing) {
return thing.getSomething();
}
);
In JDK7, it may be something like:
Map<String,Thing> map = stringIndexOf(
things,
{ thing -> thing.getSomething(); }
);
(Might need a yield in there.)
Using reflection and generics:
public static <T> Map<String, T> MapMe(Class<T> clz, Collection<T> list, String methodName)
throws Exception{
Map<String, T> map = new HashMap<String, T>();
Method method = clz.getMethod(methodName);
for (T el : list){
map.put((String)method.invoke(el), el);
}
return map;
}
In your documentation, make sure you mention that the return type of the method must be a String. Otherwise, it will throw a ClassCastException when it tries to cast the return value.
If you're sure that each object in the List will have a unique index, use Guava with Jorn's suggestion of Maps.uniqueIndex.
If, on the other hand, more than one object may have the same value for the index field (which, while not true for your specific example perhaps, is true in many use cases for this sort of thing), the more general way do this indexing is to use Multimaps.index(Iterable<V> values, Function<? super V,K> keyFunction) to create an ImmutableListMultimap<K,V> that maps each key to one or more matching values.
Here's an example that uses a custom Function that creates an index on a specific property of an object:
List<Foo> foos = ...
ImmutableListMultimap<String, Foo> index = Multimaps.index(foos,
new Function<Foo, String>() {
public String apply(Foo input) {
return input.getBar();
}
});
// iterate over all Foos that have "baz" as their Bar property
for (Foo foo : index.get("baz")) { ... }