Reputation: 6823
I have some cases where using Java 8 Stream makes me repeat the execution of some operation where it could be avoided if done without the Stream, but I think that the problem is not with the stream, but me.
Some example:
private class Item {
String id;
List<String> strings;
}
// This method, filters only the Items that have the strToFind, and
// then maps it to a new string, that has the id and the str found
private void doIt(List<Item> items, String strToFind) {
items.stream().filter(item -> {
return item.strings.stream().anyMatch(str -> this.operation(str, strToFind));
}).map(item -> {
return item.id + "-" + item.strings.stream()
.filter(str -> this.operation(str, strToFind)).findAny().get();
});
}
// This operation can have a lot of overhead, therefore
// it would be really bad to apply it twice
private boolean operation(String str, String strToFind) {
return str.equals(strToFind);
}
As you can see, the function operation
is being called twice for each item, and I don't want that. What I thought first was to map directly and return "null" if not found and then filter nulls, but if I do that, I will lose the reference to the Item and therefore, can't use the id.
Upvotes: 3
Views: 382
Reputation: 1507
Although not the shortest code (but this has not been asked for) I believe this works quite straightforward using Optional
but does not involve any null
mappings and/or checks and type information (String vs. Object) is not accidentally lost:
items.stream()
.map(item -> item.strings.stream()
.filter(str -> this.operation(str, strToFind))
.findAny()
.<String>map(string -> item.id + "-" + string))
.filter(Optional::isPresent)
.map(Optional::get);
It's pretty much a combination of Jeremy Grand's and Holger's answers.
Upvotes: 2
Reputation: 298399
You can use
private void doIt(List<Item> items, String strToFind) {
items.stream()
.flatMap(item -> item.strings.stream().unordered()
.filter(str -> this.operation(str, strToFind)).limit(1)
.map(string -> item.id + "-" + string))
// example terminal operation
.forEach(System.out::println);
}
The .unordered()
and .limit(1)
exist to produce the same behavior like anyMatch()
and findAny()
of your original code. Of course, .unordered()
is not required to get a correct result.
In Java 9, you could also use
private void doIt(List<Item> items, String strToFind) {
items.stream()
.flatMap(item -> item.strings.stream()
.filter(str -> this.operation(str, strToFind))
.map(string -> item.id + "-" + string).findAny().stream())
// example terminal operation
.forEach(System.out::println);
}
keeping the findAny()
operation, but unfortunately, Java 8 lacks the Optional.stream()
method and trying to emulate it would create code less readable than the limit(1)
approach.
Upvotes: 3
Reputation: 2370
I think you might want this behaviour :
items.stream().map(item -> {
Optional<String> optional = item.strings.stream().filter(string -> operation(string, strToFind)).findAny();
if(optional.isPresent()){
return item.id + "-" + optional.get();
}
return null;
}).filter(e -> e != null);
EDIT : Because you're losing the information obtained in the filter when you're doing the map afterwards, but nothing prevents you from doing the operation in the map only and filtering afterwards.
EDIT 2 : As @Jorn Vernee pointed out, you can shorten it further :
private void doIt(List<Item> items, String strToFind) {
items.stream().map(item -> item.strings.stream().filter(string -> operation(string, strToFind)).findAny()
.map(found -> item.id + "-" + found).orElse(null)).filter(e -> e != null);
}
Upvotes: 5