Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java API class having a method with a meaningless type wildcard

Tags:

java

generics

In Java 8, the class java.util.Optional (javadoc) class offers functionality of the "Maybe" Monad or the Option Type.

More directly:

public final class java.util.Optional<T> extends Object

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

One of the methods is:

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

If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result.

The question is simply why does map() use a type wildcard on the U type.

My understanding of type wildcarding is simply that:

enter image description here

? super T designates some type from the set of classes given by the subclassing path from Object to T (both Object and T included) union the set of interfaces found on any subinterfacing path pulled in "as a sidedish" via implements.

And ? extends U simply designates some type from the set of classes that extend U (Uincluded).

So one could just write:

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

without loss of information.

Or not?

like image 877
David Tonhofer Avatar asked Mar 14 '26 00:03

David Tonhofer


1 Answers

Not quite.

A Function<? super T, U> is not the same as a Function<? super T, ? extends U>.

For example, I could still get an Optional<CharSequence>, even if I passed a Function<Object, String> to the method. If the method was defined as <U> Optional<U> map(Function<? super T, U> mapper), then this would not be possible.

That is because generics are invariant: <T> is not the same as <? extends T>. That's a design decision implemented in the Java language.

Let's see how Jon Skeet explains what would happen if generics would not be invariant:

class Animal { }

class Dog extends Animal { }

class Cat extends Animal { }
public void ouch() {
    List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
    List<Animal> animals;
    // This would be legal, right? Because a list of dogs is a list of animals.
    List<Animal> animals = dogs;
    // This would be legal, right? Because a cat could be added to a
    // list of animals, because a cat is an animal.
    animals.add(new Cat());
    // Unfortunately, we have a confused cat.
}

Although I'm not entirely sure what you mean in your comments, I'll try to elaborate.

If you have full control over the Function you provide, it doesn't matter whether the method's signature is Function<? super T, U> or Function<? super T, ? extends U>, you'll just adapt your Function accordingly. But the authors of the method probably wanted the method to be as flexible as possible, allowing one to provide a Function with its second parameter to be also a subclass of U, instead of only U itself. Actually you widen its lower bound from U to a certain subtype of U.

So the function should really read <U> Optional<? the-most-general-but-fixed-supertype-of U> map(Function<? super T, U> mapper) but expressing it that way would be awkward.

I would be indeed awkward. In addition, there is a difference between your proposed notation and the actual method signature of map(), which involve implications of lower bounds and upper bounds.

Read more:

  • A picture explaining the principle "Producer Extends Consumer Super"
  • What is the use of saying <? extends SomeObject> instead of <SomeObject>?
  • Upper and lower bounds
like image 172
MC Emperor Avatar answered Mar 15 '26 12:03

MC Emperor