I found in the OpenJDK (11) the implementation of Optional.ifPresent() method. Here it is:
public void ifPresent(Consumer<? super T> action) {
if (this.value != null) {
action.accept(this.value);
}
}
And I can't understand why this particular template is written: <? super T>
For example, we have classes A, B, C:
private static class A {
protected void call() {
System.out.println("Call A");
}
}
private static class B extends A {
@Override
protected void call() {
System.out.println("Call B");
}
}
private static class C extends B {
@Override
protected void call() {
System.out.println("Call C");
}
}
And some calling main method:
public static void main(String[] args) {
final Optional<B> opt = Optional.of(new B());
opt.ifPresent(A::call);
}
I could call my main method with opt.ifPresent(A::call); and with opt.ifPresent(B::call); and can't with opt.ifPresent(C::call);. Ok, I understand that. But why exactly <? super T>? I can achieve the same behavior with <T> template. Can someone help me understand this and give me an example of why this is necessary?
Update:
Finally, I found an example where the <T> type isn't enough in the ifPresent(Consumer<? super T> action) signature.
Example: Let's imagine that the signature of our method is:
public void ifPresent(Consumer<T> action)
In this case we can't use the method that way:
Optional<B> opt = Optional.of(new B());
opt .ifPresent(new Consumer<Object>() {
@Override
public void accept(final Object obj) {
//compiler error: 'java: incompatible types'
}
});
But in the case with:
public void ifPresent(Consumer<? super T> action)
it's possible, and code compiles without any error.
It is a question of covariance and contravariance of generic types.
Function types are contravariant in their parameters. If B is a subclass of A, then Consumer<A> could be considered a subclass of Consumer<B>: whenever you need a Consumer<B> you can use a Consumer<A>, because it accepts A as a parameter, so it accepts also B.
On the other hand functions are covariant in their return type: a Supplier<B> have a return value which can be cast to A, so you could consider it a subclass of Supplier<A>.
Unfortunately such definitions do not work well with mutable structures: if you allow List<B> to be a subclass of List<A> then you can write:
final List<B> listB = new ArrayList<>();
final List<A> listA = listB;
listA.add(new A()); // it is no longer a List<B>
final B b = listB.get(0); // ClassCastException
Therefore Java chose invariant generics: Consumer<B> is neither a subclass nor a superclass of Consumer<A>. But we want to remember that Consumer<B> is de facto a subclass of Consumer<A> so we use bounds in the signatures of functions.
Of course one might write:
public <U super T> void ifPresent(Consumer<U super T> action) { ... }
(the T parameter refers to the type parameter of Optional<T>), but U is never used in the signature. So we use the wildcard ? super T.
Other languages like Scala allow to specify if it is safe to use a generic type in a covariant or contravariant way in the class definition. So Scala whould define:
def ifPresent(Consumer[T] action): Unit { ... }
since the compiler already knows, that the parameter T in Consumer is contravariant.
Remark: if you don't write to a List, you can also use it in a covariant way through the type List<? extends A>. The compiler will not allow you to call add(new A()) on such an object, since ? can also be a subclass of A, in which case the call would break the List. But a call to get() will certainly return something assignable to A.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With