How can I implement a method that accepts a Consumer<Optional<T>> that is contravariant in T?

元气小坏坏 提交于 2020-01-06 17:59:52

问题


In the following sample, I can pass a Consumer<Optional<Integer> to foo, but not a Consumer<Optional<Number>>. On the other hand, I can pass either type to foo2, but then I can't call the accept method of the consumer from the method body. Is there a way to change the foo method so that this works? My initial intuition was to try void foo(Consumer<Result<? super T>> c) but that apparently doesn't mean what I would assume.

import java.util.Optional;
import java.util.function.Consumer;

public class test<T> {

    public void foo(Consumer<Optional<T>> c) {
        Optional<T> t = null;
        c.accept(t); // compiles
    }

    public void foo2(Consumer<? extends Optional<? super T>> c) {
        Optional<T> t = null;
        c.accept(t); // doesn't compile
    }

    public static void bar() {
        test<Integer> t = null;
        Consumer<Optional<Number>> crn = null;
        Consumer<Optional<Integer>> cri = null;

        t.foo(cri); // compiles
        t.foo(crn); // doesn't compile

        t.foo2(cri); // compiles
        t.foo2(crn); // compiles
    }
}

回答1:


The reason for this is that Optional isn't special from the point of view of the type system: we know that Optional only has a provider method (Optional.get()) and that it has no consumer methods (like Optional.set(T)); but the compiler doesn't.

So, the compiler it won't let you pass an Optional<Integer> where an Optional<Number> is required: it is preventing you from ever calling that mythical set method, in case you passed in a Double instead of an Integer.

The only way around this is to change the Optional<T> into an Optional<S>, where S is a supertype of T. You can do this by either:

  • Casting - which you know is safe, because of the immutability of Optional and its lack of consumer methods; but you get an unchecked warning (which is actually fine to suppress, because of the properties of Optional).
  • Creating a new Optional of the right type - maybe more pure, but has the runtime overhead of creating the new instance.

In order to write such a thing in a method, you would have to write it as a static method (probably in the test class, but it could be elsewhere); Java's type system isn't expressive enough to be able to write the required constraints on an instance method's signature:

public static <T, S extends T> void foo3(Consumer<Optional<T>> c, test<S> test) {
    Optional<S> s = null;

    @SuppressWarnings("unchecked")  // Safe because of properties of Optional.
    Optional<T> t = (Optional<T>) (Optional<?>) s;

    c.accept(t);
}

and invoke like this (using the values of cri, crn and t from the question code):

foo3(cri, t); // compiles
foo3(crn, t); // compiles

Ideone demo



来源:https://stackoverflow.com/questions/44841592/how-can-i-implement-a-method-that-accepts-a-consumeroptionalt-that-is-contra

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!