通配符使用的泛型方法调用

4

我有一个名为submit的方法:

<T> Future<Optional<T>> submit(Value<T> value) {
    //...
}

以及对该方法的调用。

Value<?> value = null;
Future<Optional<?>> future = submit(value);

但是,只有当我将签名更改为Future<? extends Optional<?>> future时,它才会编译,因为submit返回Future<Optional<caputure of ?>>,而不是Future<Optional<?>>,因为泛型是不变的。是否有一种方法可以向编译器提供提示并调用submit方法,使其返回Future<Optional<?>>

这段代码片段:

<T, E extends Future<Optional<T>>> E submit(Value<T> value) {
  // ...
}
Value<?> value = null;
Future<Optional<?>> future = submit(value);

编译可以通过,但是在返回值之前我需要进行不安全的类型转换为E。

这里有一个解释泛型逆变性的答案,但它仍然没有回答如何处理捕获类型的问题。

多个通配符在泛型方法中使Java编译器(和我!)非常困惑


为什么这是个问题?就我所知,实际上没有什么区别,future.get()将返回一个Optional<?>对象。 - biziclop
4个回答

2
尽管我不确定为什么使用Future<? extends Optional<?>>会成为问题,但可能会有解决方案,这取决于您愿意接受多少扭曲和“技巧”。
即使使用附加类型参数E,也无法以所需的形式编译它,因为它不是类型安全的。至少,编译器不能确保它是类型安全的。它不是类型安全的原因可以概括如下:收到Future<Optional<?>>的某人可以修改Future,并分配任何Optional - 即使它具有与最初创建的类型不同的类型。在另一个位置,某人可能知道具有其原始类型的Future,并在尝试使用它时收到ClassCastException。(我用更简单的例子解释了这一点:https://dev59.com/wH3aa4cB1Zd3GeqPaTxb#22193221
但是...
...所有这些都与此无关。 Future接口不允许“设置”任何新值。因此,将其转换为具有其原始类型超类型的Future是完全可行的。
注意:这类似于您可以编写的事实。
List<Integer> integers = new ArrayList<Integer>();
List<Number> numbers = Collections.unmodifiableList(integers);

这是有效的(即“类型安全”的),因为您无法使用无效的Number实例(如Double)污染Integer列表,因为无论如何都无法修改该列表!因此,一种类型安全、无警告和有效的解决方案可能是引入一种方法,将Future“包装”并将其作为Future返回。再次强调:这不是很美观,可能不值得努力,但至少有一个选项:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class GenericOptionalFutureMethod
{
    void call()
    {
        Value<?> value = null;
        Future<Optional<?>> future = 
            FutureWrapper.<Optional<?>>wrap(submit(value));
    }

    <T> Future<Optional<T>> submit(Value<T> value) {
        return null;
    }
}

class FutureWrapper 
{
    static <T> Future<T> wrap(final Future<? extends T> future)
    {
        return new Future<T>()
        {
            @Override
            public boolean cancel(boolean mayInterruptIfRunning)
            {
                return future.cancel(mayInterruptIfRunning);
            }

            @Override
            public boolean isCancelled()
            {
                return future.isCancelled();
            }

            @Override
            public boolean isDone()
            {
                return future.isDone();
            }

            @Override
            public T get() throws InterruptedException, ExecutionException
            {
                return future.get();
            }

            @Override
            public T get(long timeout, TimeUnit unit)
                throws InterruptedException, ExecutionException,
                TimeoutException
            {
                return future.get();
            }
        };
    }

}

1
据我所知,没有办法告诉编译器你的代码是正确的。你可以尝试将?替换为Object:
Value<Object> value = null;
Future<Optional<Object>> future = submit(value);

不幸的是,Value<?> value 作为参数传递给了我,而我无法更改它的签名。 - Nutel
你可以将其转换为 Value<Object>。我认为 ClassCastException 的风险非常低,因为无论实际的泛型是什么,它始终是 Object 的某种派生。 - mvieghofer
@mvieghofer 你可以这样做,但不一定要这样做。将 Foo<?> 强制转换为 Foo<Object> 几乎肯定是设计错误的迹象。 - biziclop
@biziclop 我同意你的观点,但我认为将 Value<?> 传递给任何方法(似乎在这里是这种情况)也是不好的设计。最好的方法是摆脱 Value<?> 参数。 - mvieghofer
@mvieghofer 那么这个怎么样?(http://docs.oracle.com/javase/7/docs/api/java/util/Collections.html#reverse(java.util.List)) 传递一个无界通配符参数通常是糟糕设计的标志,但它也有其合法用途。 - biziclop
@biziclop 是的,没错。显然有合法的使用情况。 - mvieghofer

0

我真的不知道问题出在哪里。

Future<? extends Optional<?>> future = submit(value);
Optional<?> f = future.get();

编译并正确运行。

您链接的答案已经很清楚地解释了:

Foo<Bar> 是一个 Foo<?>

但是

Foo<Bar<X>> 不是一个 Foo<Bar<?>>,它是一个 Foo<? extends Bar<?>>


这是表达它的方式。List示例向你展示原因。 - biziclop
为什么带有 E 的代码片段可以运行?在这种情况下,似乎 ? 没有被捕获。 - Nutel
@user2961393 因为 E 必须绑定到一个特定的类,这个类在整个表达式中都具有相同的值。而 ? 的意思是任何东西都可以放在那里。我仍然不明白为什么你想要做一个不安全的转换来避免正确的表示法。 - biziclop
你的示例只是更改了future的签名,使其与Future<Optional<capture of ?>>匹配,仍然捕获了?,应该有一种更清晰的解决方案,不需要“丢失”future的类型,代码Future<Optional<?>> future = submit(value);从类型系统的角度来看仍然是正确的。 - Nutel
例如,List<Option<E>> 表示当调用该方法时,列表中所有的 Option 实例都将具有相同的类型参数,例如 Option<String>。而 List<Option<?>> 则表示列表的第一个元素可以是 Option<String>,第二个元素可以是 Option<Integer>,以此类推。这两个声明是截然不同的。 - biziclop
显示剩余2条评论

0

你可以将方法更改为使用通配符运算符作为参数,因为你没有在 T 上指定任何类型。在我看来,这不会有太大的变化。

Future<Optional<?>> submit(Value<?> value) {
    //...
}     

当然它会有很大的变化,因为在带有 ? 版本中输入参数的类型不必匹配返回值。所以你可以将 Value<String> 传递给方法,但返回 Future<Optional<Long>> ,这可能不是该方法应该执行的操作。 - mvieghofer
提交方法应该是有类型的,也就是返回类型和参数类型应该匹配。 - Nutel
是的,我知道,但他在他的示例中并没有这样做,这意味着他不关心返回值的特定类型。这就是为什么我写下它在这种情况下不会有太大变化的原因。 - squallsv
如果你说你需要匹配参数,那么为什么不使用特定的参数传递“value”,而是使用通配符运算符呢? - squallsv

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接