如何实现接受Consumer <optional <t>&gt;的方法?那是T中的逆变?

时间:2017-06-30 08:03:40

标签: java generics generic-variance

在以下示例中,我可以将Consumer<Optional<Integer>传递给foo,但不能传递给Consumer<Optional<Number>>。另一方面,我可以将任一类型传递给foo2,但是我不能从方法体中调用使用者的accept方法。有没有办法更改foo方法,以便这有效?我最初的直觉是尝试void foo(Consumer<Result<? super T>> c),但这显然并不意味着我会假设。

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 个答案:

答案 0 :(得分:1)

原因是Optional从类型系统的角度来看并不特殊:我们知道Optional只有提供者方法(Optional.get())并且它没有消费者方法(如Optional.set(T));但是编译器没有。

所以,它赢了的编译器让你传递Optional<Integer>需要Optional<Number>的地方:它阻止你调用那个神秘的set方法,万一您传递的是Double而不是Integer

解决此问题的唯一方法是将Optional<T>更改为Optional<S>,其中ST的超类型。您可以通过以下任一方式执行此操作:

  • 铸造 - 你知道这是安全的,因为Optional的不变性和缺乏消费者的方法;但你得到一个未经检查的警告(由于Optional的属性,实际上可以抑制它。)
  • 创建正确类型的新Optional - 可能更纯,但是具有创建新实例的运行时开销。

为了在方法中编写这样的东西,你必须把它写成一个静态方法(可能在test类中,但它可能在其他地方); Java的类型系统不够富有表现力,无法在实例方法的签名上编写所需的约束:

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);
}

并像这样调用(使用问题代码中的cricrnt值:

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

Ideone demo