Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
181 views
in Technique[技术] by (71.8m points)

java - Optional "ifPresent" generic wildcard

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.

question from:https://stackoverflow.com/questions/65849597/optional-ifpresent-generic-wildcard

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...