This question already has answers here:
Using Java 8's Optional with Stream::flatMap
(12 answers)
Closed 6 years ago.
Is there a more elegant way of practically achieving this in Java 8?
list.stream()
.map(e -> myclass.returnsOptional(e))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
I'm talking about filter(Optional::isPresent) followed by map(Optional::get), I want to elegantly collect in a list only Optional results which have a value.
In your case you can use one flatMap instead of combinations of map filter and again map.
To Do that it's better to define a separate function for creating a Stream: public private static Stream<Integer> createStream(String e) to not have several lines of code in lambda expression.
Please see my full Demo example:
public class Demo{
public static void main(String[] args) {
List<String> list = Arrays.asList("1", "2", "Hi Stack!", "not", "5");
List<Integer> newList = list.stream()
.flatMap(Demo::createStream)
.collect(Collectors.toList());
System.out.println(newList);
}
public static Stream<Integer> createStream(String e) {
Optional<Integer> opt = MyClass.returnsOptional(e);
return opt.isPresent() ? Stream.of(opt.get()) : Stream.empty();
}
}
class MyClass {
public static Optional<Integer> returnsOptional(String e) {
try {
return Optional.of(Integer.valueOf(e));
} catch (NumberFormatException ex) {
return Optional.empty();
}
}
}
in case returnsOptional cannot be static you will need to use "arrow" expression instead of "method reference"
Not sure if its so different but you could just filter based on your optional instead of getting the optional and filtering next.
Something like this?
list.stream()
.filter(e -> myclass.returnsOptional(e).isPresent())
.collect(Collectors.toList());
Note: This will only work if returnsOptional returns the same object type as your original list item types.
Related
ArrayList<Optional<Test>> valueList= vv.values().stream().collect(Collectors.toList());
I'm getting the values into a list with the ArrayList<Optional<Test>> as the return type but I need to convert it to ArrayList<Test> without an optional return type.
How do I convert ArrayList<Optional<Test>> to ArrayList<Test> in Java 8 or Java 7?
1.Using filter()
One option in Java 8 is to filter out the values with Optional::isPresent and then perform mapping with the Optional::get function to extract values:
ArrayList<Test> testlist = valueList.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
2.Using flatMap()
Another option is use flatMap with a lambda expression that converts an empty Optional to an empty Stream instance, and non-empty Optional to a Stream instance containing only one element:
ArrayList<Test> testlist = valueList.stream()
.flatMap(t -> t.isPresent() ? Stream.of(t.get()) : Stream.empty())
.collect(Collectors.toList());
You can apply the same approach using method reference to converting an Optional to Stream:
ArrayList<Test> testlist = valueList.stream()
.flatMap(t -> t.map(Stream::of).orElseGet(Stream::empty))
.collect(Collectors.toList());
3.Java 9's Optional::stream
In Java 9, the stream() method has been added to the Optional class to improve its functionality.This is similar to the one showed in section 2, but this time we are using a predefined method for converting Optional instance into a Stream instance, If the Optional contains a value, then return a Stream containing the value. Otherwise, it returns an empty stream.
ArrayList<Test> testlist = valueList.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
Using Java 8 Streams you can do it like this:
public static <T> List<T> unpackOptionals(List<Optional<T>> listOfOptionals) {
return listOfOptionals.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
Using Java 9 or above, you can use the method Optional.stream() to do the job:
public static <T> List<T> unpackOptionals(List<Optional<T>> listOfOptionals) {
return listOfOptionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
In Java 7 you shouldn't have any Optional (they were added in Java 8)
I have an Optional object that contains a list. I want to map each object in this list to another list, and return the resulting list.
That is:
public List<Bar> get(int id) {
Optional<Foo> optfoo = dao.getById(id);
return optfoo.map(foo -> foo.getBazList.stream().map(baz -> baz.getBar()))
}
Is there a clean way of doing that without having streams within streams?
I think that flatMap might be the solution but I can't figure out how to use it here.
There isn't. flatMap in case of Optional is to flatten a possible Optional<Optional<T>> to Optional<T>. So this is correct.
public List<Bar> get(Optional<Foo> foo) {
return foo.map(x -> x.getBazList()
.stream()
.map(Baz::getBar)
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
A Java 9 approach would be the folloing:
public List<Bar> get(Optional<Foo> foo) {
return foo.map(Foo::getBazList)
.stream()
.flatMap(Collection::stream)
.map(Baz::getBar)
.collect(Collectors.toList());
}
That said, you should avoid using Optionals as parameters, see here.
I'd like to convert the following code, which breaks from the outer loop, into Java 8 Streams.
private CPBTuple getTuple(Collection<ConsignmentAlert> alertsOnCpdDay)
{
CPBTuple cpbTuple=null;
OUTER:
for (ConsignmentAlert consignmentAlert : alertsOnCpdDay) {
List<AlertAction> alertActions = consignmentAlert.getAlertActions();
for (AlertAction alertAction : alertActions) {
cpbTuple = handleAlertAction(reportDTO, consignmentId, alertAction);
if (cpbTuple.isPresent()) {
break OUTER;
}
}
}
return cpbTuple;
}
Every answer here uses flatMap, which until java-10 is not lazy. In your case that would mean that alertActions is traversed entirely, while in the for loop example - not. Here is a simplified example:
static class User {
private final List<String> nickNames;
public User(List<String> nickNames) {
this.nickNames = nickNames;
}
public List<String> getNickNames() {
return nickNames;
}
}
And some usage:
public static void main(String[] args) {
Arrays.asList(new User(Arrays.asList("one", "uno")))
.stream()
.flatMap(x -> x.getNickNames().stream())
.peek(System.out::println)
.filter(x -> x.equalsIgnoreCase("one"))
.findFirst()
.get();
}
In java-8 this will print both one and uno, since flatMap is not lazy.
On the other hand in java-10 this will print one - and this is what you care about if you want to have your example translated to stream-based 1 to 1.
Something along the lines of this should suffice:
return alertsOnCpdDay.stream()
.flatMap(s-> s.getAlertActions().stream())
.map(s-> handleAlertAction(reportDTO, consignmentId, s))
.filter(s-> s.isPresent())
.findFirst().orElse(null);
That said, a better option would be to change the method return type to Optional<CPBTuple> and then simply return the result of findFirst(). e.g.
private Optional<CPBTuple> getTuple(Collection<ConsignmentAlert> alertsOnCpdDay) {
return alertsOnCpdDay.stream()
.flatMap(s-> s.getAlertActions().stream())
.map(s-> handleAlertAction(reportDTO, consignmentId, s))
.filter(s-> s.isPresent())
.findFirst();
}
This is better because it better documents the method and helps prevent the issues that arise when dealing with nullity.
Since you break out of the loops upon the first match, you can eliminate the loops with a Stream with flatMap, which returns the first available match:
private CPBTuple getTuple(Collection<ConsignmentAlert> alertsOnCpdDay) {
return alertsOnCpdDay.stream()
.flatMap(ca -> ca.getAlertActions().stream())
.map(aa -> handleAlertAction(reportDTO, consignmentId, aa))
.filter(CPBTuple::isPresent)
.findFirst()
.orElse(null);
}
Try this out,
alertsOnCpdDay.stream()
.map(ConsignmentAlert::getAlertActions)
.flatMap(List::stream)
.map(alertAction -> handleAlertAction(reportDTO, consignmentId, alertAction))
.filter(CPBTuple::isPresent)
.findFirst().orElse(null);
This question already has answers here:
How can I turn a List of Lists into a List in Java 8?
(12 answers)
Closed 5 years ago.
I would like to map each entry in my list by calling expand(), which returns multiple entries, and then collect the result as a list.
Without streams, I would accomplish this like:
List<String> myList = new ArrayList<>();
List<String> expanded = new ArrayList<>();
for (String s : myList) {
expanded.addAll(expand(s));
}
return expanded;
private List<String> expand(String x) {
return Arrays.asList(x, x, x);
}
How can I accomplish this with streams? This gives a compilation error:
return myList.stream().map(this::expand).collect(Collectors.toList());
flatMap should help you :
return myList.stream()
.flatMap(x -> expand(x).stream())
.collect(Collectors.toList());
return myList.stream().map(this::expand).collect(Collectors.toList());
returns List<List<String>> because myList.stream().map(this::expand) returns a stream typed as Stream<List<String>> as you pass to map() a variable declared List<String> variable and not String.
You don't want that.
So chain Stream.map() with Stream.flatMap() to amalgamate Stream<List<String>> to Stream<String> :
return myList.stream()
.map(this::expand)
.flatMap(x->x.stream())
.collect(Collectors.toList());
use flatMap to convert the Stream<List<String> to a Stream<String>:
return myList.stream().map(this::expand).flatMap(Collection::stream).collect(Collectors.toList());
I want to convert an ArrayList<String> to Set<ScopeItem> with Java streams.
ScopeItem is a enum;
items is an ArrayList<String>;
Set<ScopeItem> scopeItems = items.stream()
.map(scopeString -> ScopeItem.valueOf(scopeString))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
On a string that isn't in the enum this throws the following:
java.lang.IllegalArgumentException: No enum const...
Ideally, I would like to skip past any Strings that don't match.
I think maybe a using flatmap? Any ideas how to do it?
You could add the following method to your ScopeItem:
public static ScopeItem valueOfOrNull(String name) {
try {
return valueOf(name);
} catch (IllegalArgumentException e) {
// no such element
return null;
}
}
and use that to map your enum values:
.map(scopeString -> ScopeItem.valueOfOrNull(scopeString))
Subsequent .filter() on non-null values (which you already have) will filter-out those nulls that correspond to non-matching strings.
You can put a try-catch inside your map to return null instead of throwing an exception:
Set<ScopeItem> scopeItems = items.stream()
.map(scopeString ->
{
try
{
return ScopeItem.valueOf(scopeString);
}
catch (IllegalArgumentException e)
{
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
You could also use filter beforehand to check whether the array of values contains the string you're looking for:
Set<ScopeItem> scopeItems = items.stream()
.filter(scopeString -> Arrays.stream(ScopeItem.values())
.anyMatch(scopeItem -> scopeItem.name().equals(scopeString)))
.map(ScopeItem::valueOf)
.collect(Collectors.toSet());
Unlike others, I won't recommend using exceptions, as I feel they should be used for exceptional situations, and not for something that will likely to occur. A simple solution, is to have a static set of acceptable strings, and simply check, if a string you want to use valueOf with is in said set.
package test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class Test {
public static enum ScopeItem {
ScopeA,
ScopeB;
private static Set<String> castableStrings;
static {
castableStrings = new HashSet<>();
for (ScopeItem i : ScopeItem.values()) {
castableStrings.add(i.name());
}
}
public static boolean acceptable(String s) {
return castableStrings.contains(s);
}
}
public static void main(String[] args) {
List<String> items = Arrays.asList("ScopeA", "RandomString", "ScopeB");
Set<ScopeItem> scopeItems = items.stream()
.filter(ScopeItem::acceptable)
.map(ScopeItem::valueOf)
.collect(Collectors.toSet());
System.out.println(scopeItems.size());
}
}
There's a more elegant approach here. You don't have to add any new fields to your enum; you can simply run a stream against it as well and determine if there's any matches in your collection.
The below code assumes an enum declaration of:
enum F {
A, B, C, D, E
}
and looks as thus:
List<String> bad = Arrays.asList("A", "a", "B", "b", "C", "c");
final Set<F> collect = bad.stream()
.filter(e -> Arrays.stream(F.values())
.map(F::name)
.anyMatch(m -> Objects.equals(e, m)))
.map(F::valueOf)
.collect(Collectors.toSet());
Two parts to pay attention to here:
We do the internal filter on the values of our enum, and map that to a String through F::name.
We determine if there's any match on the elements of our base collection with the elements of our enum in a null-safe way (Objects.equals does The Right Thing™ with nulls here)
You could use Apache Common's commons-lang3 EnumUtils.getEnum() instead of valueOf(). This returns null if there is no matching enum entry (which you can then filter exactly as you have in your code).
The easiest and clearest method would be to filter your Enum's values() method on items.contains():
Set<ScopeItem> enumVals = Arrays.stream(ScopeItem.values())
.filter(e -> items.contains(e.name()))
.collect(Collectors.toSet());
No added functionality just to get the stream to do what you want, and it is obvious what this does at a glance.