问题
class EntityCompositeId {
private Long firstId;
private Long secondId;
// getter & setter...
}
class EntityComposite {
private EntityCompositeId id;
private String first;
private String second;
// getter & setter...
}
List<EntityComposite> listEntityComposite = ....
Supose this content
1, 1, "firstA", "secondBirdOne"
1, 2, "firstA", "secondBirdTwo"
1, 3, "firstA", "secondBirdThree"
2, 1, "firstB", "secondCatOne"
2, 2, "firstB", "secondCatTwo"
2, 3, "firstB", "secondCatThree"
3, 1, "firstC", "secondDogOne"
3, 2, "firstC", "secondDogTwo"
3, 3, "firstC", "secondDogThree"
Map<Long, List<String>> listOfLists = new HashMap<>();
Now using stream I want to fill like:
1 -> {"secondBirdOne", "secondBirdTwo", "secondBirdThree"}
2 -> {"secondCatOne", "secondCatTwo", "secondCatThree"}
3 -> {"secondDogOne", "secondDogTwo", "secondDogThree"}
My UNFINISHED (that's the question) code is:
listEntityComposite.stream()forEach(entityComposite {
// How create a list according entityComposite.getId.getFirstId()?
listOfLists.put(entityComposite.getId.getFirstId(), .... )
});
回答1:
There are several different approaches in which you can accomplish the task at hand.
forEach + computeIfAbsent
Map<Long, List<String>> map = new HashMap<>();
listEntityComposite.forEach(e -> map.computeIfAbsent(e.getId().getFirstId(),
k -> new ArrayList<>()).add(e.getSecond()));
- enumerates over the elements in
listEntityComposite
viaforEach
- for each element utilises
computeIfAbsent
to compute the key (i.e.firstId
) and value (i.e.List<String>
)
groupingBy + mapping
another approach would be to apply a groupingBy
with a mapping
downstream collector:
Map<Long, List<String>> resultSet = listEntityComposite.stream()
.collect(groupingBy(e -> e.getId().getFirstId(),
mapping(EntityComposite::getSecond, toList())));
- groups the source elements by the classification function
e.getId().getFirstId()
and then applies amapping
downstream collector to further refine our query.
forEach + merge
listEntityComposite.forEach(e -> map.merge(e.getId().getFirstId(),
new ArrayList<>(singletonList(e.getSecond())),
(l, r) -> {l.addAll(r); return l;}));
enumerates over the elements in
listEntityComposite
viaforEach
for each element utilises
merge
to compute the key (i.e.firstId
) and value (i.e.List<String>
)
toMap
listEntityComposite.stream()
.collect(toMap(e -> e.getId().getFirstId(),
v -> new ArrayList<>(singletonList(v.getSecond())),
(l, r) -> {l.addAll(r); return l;}));
- applies a
keyMapper
functione -> e.getId().getFirstId()
to extract the map keys. - applies a
valueMapper
functionv -> new ArrayList<>(singletonList(v.getSecond()))
to extract the map values. - applies a
merge
function(l, r) -> {l.addAll(r); return l;}
to resolve key collisions.
To conclude, the forEach
+ computeIfAbsent
approach and the groupingBy
+ mapping
approach are the two you should favour in this specific case as they're the more idiomatic.
回答2:
collect
is a more suitable terminal operation for generating an output Map
than forEach
.
You can use collect()
with Collectors.groupingBy
:
Map<Long, List<String>> listOfLists =
listEntityComposite.stream()
.collect(Collectors.groupingBy(e -> e.getId().getFirstId(),
Collectors.mapping(EntityComposite::getSecond,
Collectors.toList());
Collectors.groupingBy
with a single argument (just e -> e.getId().getFirstId()
) would generate a Map<Long,List<EntityComposite>>
.
Chaining to it Collectors.mapping()
maps each EntityComposite
instance to the corresponding getSecond()
String
, as required.
回答3:
I would suggest looking into Guava's MultiMap which makes your use-case easier to deal with (providing lots of optimizations and extra functionality you may want to obtain later down the road)
Edit: An example of why Multimap makes more sense than, say, using the computeIfAbsent approach: 1. each key has a list of a certain size, what if you'll want to get the "total" size in the future? you would have to create some logic to achieve this with good performance (or use a method that takes O(keys)) 2. at the moment you're only putting things into the map, but what happens if you want to remove things from the map in the future? You will need to write some boilerplate code (that is easy to get wrong) to make sure that removing values doesn't cause a memory leak
There are other things to gain from using a multimap but these are just two easy to explain benefits.
Edit 2: An example using your input:
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
public class Example {
public static class EntityCompositeId {
@Override
public String toString() {
return "EntityCompositeId [firstId=" + firstId + ", secondId=" + secondId + "]";
}
public EntityCompositeId(Long firstId, Long secondId) {
super();
this.firstId = firstId;
this.secondId = secondId;
}
private Long firstId;
public Long getFirstId() {
return firstId;
}
private Long secondId;
}
public static class EntityComposite {
@Override
public String toString() {
return "EntityComposite [id=" + id + ", first=" + first + ", second=" + second + "]";
}
public EntityComposite(EntityCompositeId id, String first, String second) {
super();
this.id = id;
this.first = first;
this.second = second;
}
private EntityCompositeId id;
public EntityCompositeId getId() {
return id;
}
private String first;
private String second;
}
public static void main(String[] args) {
List<EntityComposite> listEntityComposite = Arrays.asList(
new EntityComposite(new EntityCompositeId(1l, 1l), "firstA", "secondBirdOne"),
new EntityComposite(new EntityCompositeId(1l, 2l), "firstA", "secondBirdTwo"),
new EntityComposite(new EntityCompositeId(1l, 3l), "firstA", "secondBirdThree"),
new EntityComposite(new EntityCompositeId(2l, 1l), "firstB", "secondCatOne"),
new EntityComposite(new EntityCompositeId(2l, 2l), "firstB", "secondCatTwo"),
new EntityComposite(new EntityCompositeId(2l, 3l), "firstB", "secondCatThree"),
new EntityComposite(new EntityCompositeId(3l, 1l), "firstC", "secondDogOne"),
new EntityComposite(new EntityCompositeId(3l, 2l), "firstC", "secondDogTwo"),
new EntityComposite(new EntityCompositeId(3l, 3l), "firstC", "secondDogThree"));
ListMultimap<Long, EntityComposite> map = ArrayListMultimap.create();
listEntityComposite.forEach(entityComposite -> map.put(entityComposite.getId().getFirstId(), entityComposite));
map.keySet().forEach(key -> System.out.println(map.get(key)));
}
}
Yields the following output:
[EntityComposite [id=EntityCompositeId [firstId=1, secondId=1], first=firstA, second=secondBirdOne], EntityComposite [id=EntityCompositeId [firstId=1, secondId=2], first=firstA, second=secondBirdTwo], EntityComposite [id=EntityCompositeId [firstId=1, secondId=3], first=firstA, second=secondBirdThree]]
[EntityComposite [id=EntityCompositeId [firstId=2, secondId=1], first=firstB, second=secondCatOne], EntityComposite [id=EntityCompositeId [firstId=2, secondId=2], first=firstB, second=secondCatTwo], EntityComposite [id=EntityCompositeId [firstId=2, secondId=3], first=firstB, second=secondCatThree]]
[EntityComposite [id=EntityCompositeId [firstId=3, secondId=1], first=firstC, second=secondDogOne], EntityComposite [id=EntityCompositeId [firstId=3, secondId=2], first=firstC, second=secondDogTwo], EntityComposite [id=EntityCompositeId [firstId=3, secondId=3], first=firstC, second=secondDogThree]]
来源:https://stackoverflow.com/questions/53786325/stream-creating-list-of-list-nested-list-using-foreach-java-8