Home > Mobile >  How exactly does covariance and contravariance work in java streams?
How exactly does covariance and contravariance work in java streams?

Time:12-09

class User implements UserDetails

Why doesn't it work?

public Optional<UserDetails> getUserDetails(String username) {
        Optional<User> userOptional = userService.findByUsername(username);
        return userOptional;
}

.. but it works.

public Optional<UserDetails> getUserDetails(String username) {
    Optional<User> userOptional = userService.findByUsername(username);
    return userOptional.map(user -> user);
}

This also doesn't work.

public Optional<UserDetails> getUserDetails(String username) {
    Optional<User> userOptional = userService.findByUsername(username);
    return userOptional.stream().map(user -> user).findFirst();
}

The error occurs because Optional<User> cannot be converted to Optional<UserDetails>

CodePudding user response:

The second code works because Optional.map is declared like this:

public <U> Optional<U> map(Function<? super T,? extends U> mapper)

It is a generic method declared to return any Optional<U>. In your code, the generic type parameter U is inferred from the return type of the getUserDetails method to be UserDetails. If you specify the type parameter explicitly, it becomes:

return userOptional.<UserDetails>map(user -> user);

Substituting User for T and UserDetails for U, we can see that map takes a parameter of type Function<? super User,? extends UserDetails>, i.e. a function that takes a User and returns a UserDetails. user -> user, the identity function, is such a function, because a User is always a UserDetails. Therefore the code compiles.

The first code does not compile because you cannot convert from Optional<User> to Optional<UserDetails>.

Note that there is no contravariance, or covariance, for that matter, involved in the second code. It compiles simply because map returns Optional<U>, where U is an inferred type parameter.

To be able to convert from Optional<User> to Optional<UserDetails>, you need covariance. Java only has use-site variance, so you can only achieve covariance by adding ? extends to Optional<UserDetails>.

public Optional<? extends UserDetails> getUserDetails(String username) {
    Optional<User> userOptional = userService.findByUsername(username);
    return userOptional;
}

Similarly, for Stream.map, it is also a generic method.

<R> Stream<R> map(Function<? super T,? extends R> mapper)

However, in your third code, since the last call in return statement's expression is not map, the return type of the getUserDetails method is not used to infer R. R is simply inferred to be User, and findFirst returns Optional<User>.

  • Related