I use this lambda expression to find a nested object
Optional<? extends OnlineResource> onlineResourceOptional =
metadata.getDistributionInfo().stream()
.filter(Objects::nonNull)
.flatMap(distribution -> distribution.getTransferOptions().stream())
.filter(Objects::nonNull)
.flatMap(digitalTransferOptions -> digitalTransferOptions.getOnLines().stream())
.filter(Objects::nonNull)
.filter(onlineResource -> onlineResource.getProtocol().equals("OGC:STA"))
.findFirst()
;
It returns a generic Optional<? extends OnlineResource>
because of the flatmap
method but I want it to return a Optional<OnlineResource>
. How can I achieve that?
CodePudding user response:
It seems that either or both methods, getTransferOptions()
and/or getOnLines()
return a collection with a wildcard type, like Collection<? extends …>
. This is strongly discouraged:
Using a wildcard as a return type should be avoided because it forces programmers using the code to deal with wildcards.
Which means precisely the problem you ran into. You should preferably fix these methods rather than the caller. If this is not possible, you may workaround the issue by specifying an explicit type for the flatMap
operation instead of relying on type inference.
E.g., if you have code like
public static void main(String[] args) {
Optional<? extends Number> result = Stream.of("")
.flatMap(x -> method(x).stream())
.filter(x -> true)
.findFirst();
}
private static List<? extends Number> method(Object arg) {
return Arrays.asList(42, 1.23);
}
change it to
public static void main(String[] args) {
Optional<Number> result = Stream.of("")
.<Number>flatMap(x -> method(x).stream())
.filter(x -> true)
.findFirst();
}
private static List<? extends Number> method(Object arg) {
return Arrays.asList(42, 1.23);
}
to flatMap
to a Stream<Number>
, rather than Stream<? extends Number>
. This works, because the flatMap
has been declared as
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
allowing the specified function to return a stream of subtypes. This is an application of the PECS rule.
But as said, it’s preferable to fix the collection returning methods. More than often, they also call methods following the PECS rule and hence, allow changing an element type from ? extends E
to E
.
It might be helpful to know the following features:
If you already have an immutable collection like those created with List.of(…)
, you can use methods List.copyOf(…)
or Set.copyOf(…)
to widen their element type without a copy.
List<String> list1 = List.of("foo", "bar");
List<? extends Object> list2 = list1;
List<Object> list3 = List.copyOf(list2);
System.out.println(list1 == (Object)list3); // prints true
This works, because the immutability prevent callers from adding an Object
to this List<String>
. Likewise, you can create an unmodifiable view to any collection to avoid dealing with wildcard types:
List<String> list1 = List.of("foo", "bar");
List<? extends Object> list2 = list1;
List<Object> list3 = Collections.unmodifiableList(list2);
This creates a new List
instance, but without copying the contents, so it’s still cheap. But, as said, more than often, there are already methods involved which allow changing the type to the desired wildcard free type by just inserting the explicit type at an earlier point.
CodePudding user response:
Add a cast. It's safe, since all ? extends X
s are necessarily X
s.
Optional<Number> foo = Optional.of("ABC")
.flatMap(Main::length); // Does not compile
Optional<Number> bar = Optional.of("DEFG")
.flatMap(Main::length)
.map(Number.class::cast); // Safe
private static Optional<? extends Number> length(String str) {
return Optional.ofNullable(str).map(String::length);
}