Home > Software design >  Cast generic return type into lamba expression
Cast generic return type into lamba expression

Time:09-16

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 Xs are necessarily Xs.

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);
}
  • Related