I previously wrote following:
events.stream()
.map(d -> cli.getItem(d.getValue()))
.map(event -> Report.builder()
.id(event.getId())
.value(event.getValue())
.build())
.filter(r -> !excludesSet.contains(r.value))
.forEach(r -> {
System.out.println(String.format(r.value);
});
getItem returns Item here, I just updated getItem to getItems, which returns List<Item>, and want to keep some logic for every item. Which means I need to create a foreach and put original map, filter and forEach method in it under .map(d -> cli.getItem(d.getValue())), how could I do this?
Thanks!
This is a use case for flatMap:
events.stream()
.flatMap(d -> cli.getItems(d.getValue()).stream())
// everything below stays the same
flatMap allows you to map each item in a stream to multiple items.
Related
This question already has answers here:
Java 8 collecting the list that is already present in object
(2 answers)
Rewrite double nested for loop as a Java 8 stream
(2 answers)
Closed 2 years ago.
So I have lists stored inside of another list.
And inside of that lists there a different objects.
I want to get a specific object based on an attribute.
Human robert = building.getRoomList()
.stream()
.filter(room -> room.getEmployeeList()
.stream()
.filter(human -> human.getName().equals("Robert")))
.findFirst().get();
Why doesn't it work?
Try this:
Human robert = building.getRoomList().stream()
.map(Room::getRoomList)
.flatMap(Collection::stream)
.filter(human -> human.getName().equals("Robert"))
.findFirst()
.orElseThrow(() -> new RuntimeException("Robert not found!"));
Why doesn't it work?
Stream::filter requires a Predicate<T> and returns the very same Stream<T>. As long as you nest filter in filter, this would never work because the outer one requires Predicate<T> while the inner one returns Stream<T> which is clearly not compatible.
You might want to use flatMap:
Human robert = building.getRoomList()
.stream() // Stream<Room>
.flatMap(room -> room // Stream<Human> flatmapped from:
.getEmployeeList() // .. for each Room get List<Human>
.stream() // .. for each Room have Stream<Human>
.filter(human -> human.getName() // .. for each Room keep the one named
.equals("Robert"))) // .. Robert
.findFirst() // find the first Robert, the Human
.orElse(null); // or result in null
Remember the predicate human -> human.getName().equals("Robert")) is not null-safe neither when human is null nor when its name is null. For the null-safety, flat-map like this:
.flatMap(room -> room.getEmployeeList()
.stream()
.filter(Objects::nonNull)
.filter(human -> "Robert".equals(human.getName())))
You can do it using Stream.flatMap:
Optional<Human> robert = building.getRoomList().stream()
.flatMap(room -> room.getEmployeeList().stream())
.filter(human -> human.getName().equals("Robert"))
.findFirst();
robert.ifPresent(r -> /* do something with Robert */);
Stream.flatMap maps each room to a stream of humans and the returned stream consists of all the humans of all the rooms, flattened (i.e. not as sublists or substreams).
You are doing .findFirst() on RoomList only but you need to do .findFirst() on inner list means EmployeeList of every Room also and transform Room into Human using .map(), like
building.getRoomList()
.stream()
.map(room -> room.getEmployeeList()
.stream()
.filter(human -> human.getName().equals("Robert"))
.findFirst().get())
.findFirst().get();
Which can be simplified using flatMap to flatten then EmployeeList then get findFirst
building.getRoomList()
.flatMap(e -> room.getEmployeeList().stream())
.filter(human -> human.getName().equals("Robert"))
.findFirst().get();
Note: It's better to use .orElse() rather calling .get()
Here's what I have so far:
Map<Care, List<Correlative>> mapOf = quickSearchList
.stream()
.map(QuickSearch::getFacility)
.collect(Collectors.flatMapping(facility -> facility.getFacilityCares().stream(),
Collectors.groupingBy(FacilityCare::getCare,
Collectors.mapping(c -> {
final Facility facility = new Facility();
facility.setId(c.getFacilityId());
return Correlative.createFromFacility(facility);
}, Collectors.toList()))));
I have a list of Quick Searches to begin with. Each item in the quick search has a single facility as in:
public class QuickSearch {
Facility facility;
}
In every Facility, there's a List of FacilityCare as in:
public class Facility {
List<FacilityCare> facilityCares;
}
And finally, FacilityCare has Care property as in:
public class FacilityCare {
Care care;
}
Now, the idea is to convert a List of QuickSearch to a Map of <Care, List<Correlative>>.
The code within the mapping() function is bogus, in the example above. FacilityCare only has facilityID and not Facility entity. I want the facility object that went as param in flatMapping to be my param again in mapping() function as in:
Collectors.mapping(c -> Correlative.createFromFacility(facility))
where "facility" is the same object as the one in flatMapping.
Is there any way to achieve this? Please let me know if things need to be explained further.
Edit:
Here's a solution doesn't fully utilize Collectors.
final Map<Care, List<Correlative>> mapToHydrate = new HashMap<>();
quickSearchList
.stream()
.map(QuickSearch::getFacility)
.forEach(facility -> {
facility.getFacilityCares()
.stream()
.map(FacilityCare::getCare)
.distinct()
.forEach(care -> {
mapToHydrate.computeIfAbsent(care, care -> new ArrayList<>());
mapToHydrate.computeIfPresent(care, (c, list) -> {
list.add(Correlative.createFromFacility(facility));
return list;
});
});
});
Sometimes, streams are not the best solution. This seems to be the case, because you are losing each facility instance when going down the pipeline.
Instead, you could do it as follows:
Map<Care, List<Correlative>> mapToHydrate = new LinkedHashMap<>();
quickSearchList.forEach(q -> {
Facility facility = q.getFacility();
facility.getFacilityCares().forEach(fCare ->
mapToHydrate.computeIfAbsent(fCare.getCare(), k -> new ArrayList<>())
.add(Correlative.createFromFacility(facility)));
});
This uses the return value of Map.computeIfAbsent (which is either the newly created list of correlatives or the already present one).
It is not clear from your question why you need distinct cares before adding them to the map.
EDIT: Starting from Java 16, you might want to use Stream.mapMulti:
Map<Care, List<Correlative>> mapToHydrate = quickSearchList.stream()
.map(QuickSearch::getFacility)
.mapMulti((facility, consumer) -> facility.getFacilityCares()
.forEach(fCare -> consumer.accept(Map.entry(fCare.getCare(), facility))))
.collect(Collectors.groupingBy(
e -> e.getKey(),
Collectors.mapping(
e -> Correlative.createFromFacility(e.getValue()),
Collectors.toList())));
This is what I came up with based on the information provided. The Facility and Care are stored in a temp array to be processed later in the desired map.
Map<Care, List<Correlative>> mapOf = quickSearchList.stream()
.map(QuickSearch::getFacility)
.flatMap(facility -> facility
.getFacilityCares().stream()
.map(facCare->new Object[]{facility, facCare.getCare()}))
.collect(Collectors.groupingBy(obj->(Care)obj[1], Collectors
.mapping(obj -> Correlative.createFromFacility(
(Facility)obj[0]),
Collectors.toList())));
I prepared some simple test data and this seems to work assuming I understand the ultimate goal. For each type of care offered, it puts all the facilities that offer that care in an associated list of facilities.
Inspired by #fps answer, I was able to come up with a solution that will work for the time being (pre-Java16).
Map<Care, List<Correlative>> mapOf = quickSearchList
.stream()
.map(QuickSearch::getFacility)
.map(expandIterable())
.collect(
Collectors.flatMapping(map -> map.entrySet().stream(),
Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(entry -> Correlative.createFromFacility(entry.getValue()),
Collectors.toList()
)
)
));
}
public Function<Facility, Map<Care, Facility>> expandIterable() {
return facility -> facility.getFacilityCares()
.stream()
.map(FacilityCare::getCare)
.distinct()
.collect(Collectors.toMap(c -> c, c -> facility));
}
Basically, I added a method call that returns a Function that takes in Facility as argument and returns a Map of Care as key with Facility as value. That map is used in the collection of the previous stream.
Here is the example of code that I struggle with:
List<CategoryHolder> categories = ...
List<String> categoryNames = categoryIds.stream()
.map(id -> categories.stream()
.filter(category -> category.getId().equals(id))
.findFirst().get().getName())
.collect(Collectors.toList());
So I have a list of CategoryHolder objects consisting of category.id and category.name. I also have a list of category ids. I want to iterate through ids and for each id I want to iterate through the CategoryHolder list and when id from categoryIds list is matched with a CategoryHolder.id I want to return the category.name. So basically I want to map every value from categoryIds to its category.name.
So the problem is when no values are matched, filter doesn't pass any elements through and there is nothing to collect, so I would like to break from the current inner stream, and continue to the next id from categoryIds list. Is there a way to achieve this in stream API?
You can do like:
categories.stream()
.filter(categoryHolder -> categoryIds.stream()
.anyMatch(id->categoryHolder.getId().equals(id)))
.map(CategoryHolder::getName)
.collect(Collectors.toList());
or for better performance you can do:
Map<String,CategoryHolder> map = categories.stream()
.collect(Collectors.toMap(CategoryHolder::getId, Function.identity()));
List<String> names = categoryIds.stream()
.map(id -> map.get(id).getName())
.collect(Collectors.toList());
The problem is with your call to filter where you are doing .findFirst().get().getName()
This would fail in case empty Optional is returned by findFirst().
You can instead rewrite it as follows:
List<String> categoryNames =
categoryIds
.stream()
.map(id -> categories
.stream()
.filter(catgory -> catgory.getId().equals(id))
.findFirst())
.collect(
ArrayList::new,
(list, optional) -> {
optional.ifPresent(categoryHolder -> list.add(categoryHolder.name));
},
(list, list2) -> {}
);
I am iterating over a series of list items. Items is having a property id and internal id. for each item I am creating a some operation which returns another object say BackendItem along with unique id. My goal is create a map of item id and BackendItem id.
items.steam()
.filter(item -> item.type ==1)
.map(item -> backendService.createBackendItem(item))
.map (backendItem -> backendItem.id)
here i want to create a map of (item.id,backendItem.id)
Which operator should i use ?
Don't use map(...):
items.stream()
.filter(item -> item.type == 1)
.collect(Collectors.toMap(item -> item.id,
item -> backendSerivce.createBackendItem(item).id));
Its possible to filter and continue emiting itens like below ?
My code that calls subscriber 2 times:
Observable<Map.Entry<String, ArrayList<MockOverview>>> requestEntries =
this.requestView.request(request)
.map(HashMap::entrySet)
.flatMapIterable(entries -> entries);
requestEntries.filter(entry -> entry.getKey().equals("featured"))
.map((Func1<Map.Entry<String, ArrayList<MockOverview>>, List<MockOverview>>) Map.Entry::getValue)
.subscribe(mockOverviews -> {
Log.i("subscrive", "featured");
});
requestEntries.filter(entry -> entry.getKey().equals("done"))
.map((Func1<Map.Entry<String, ArrayList<MockOverview>>, List<MockOverview>>) Map.Entry::getValue)
.subscribe(mockOverviews -> {
Log.i("subscrive", "featured");
});
What i want:
requestEntries.filter(entry -> entry.getKey().equals("featured"))
.map((Func1<Map.Entry<String, ArrayList<MockOverview>>, List<MockOverview>>) Map.Entry::getValue)
.subscribe(mockOverviews -> {
})
.filter(entry -> entry.getKey().equals("done"))
.map((Func1<Map.Entry<String, ArrayList<MockOverview>>, List<MockOverview>>) Map.Entry::getValue)
.subscribe(mockOverviews -> {
});
By the looks of things your second version is not equal to your first: the former looks at the requestEntries stream twice, filters on featured and done keys respectively and does its own things with it. Your second version however first filters on featured first then does some transformations and side-effects and then filter out the done. However, that Observable<entryset> is not at all in scope in that second filter lambda.
What you need to do here is use publish(<lambda>) on requestEntries and in the lambda do the stuff from your first version, use onNext instead of subscribe, merge the streams and return that combined stream. Then outside of the publish you subscribe once (and do nothing in there) or go on and use the result of your stream somewhere else.
requestEntries.publish(re -> {
Observable<...> x = re.filter(...<featured>...).map(...).doOnNext(...Log.i(...));
Observable<...> y = re.filter(...<done>...).map(...).doOnNext(...Log.i(...));
return x.mergeWith(y);
})
You can use doOnNext in the place of the first subscribe()
requestEntry.filter(v -> ...)
.map(v -> ...)
.doOnNext(v -> ...)
.filter(v -> ...)
.map(v -> ...)
.subscribe(...)
or use publish(Func1):
requestEntry.filter(v -> ...)
.map(v -> ...)
.publish(o -> {
o.subscribe(...);
return o;
})
.filter(v -> ...)
.map(v -> ...)
.subscribe(...)