Java通配符不按预期通过继承传递(意外的通配符捕获)

问题描述 投票:3回答:2

我正在寻找一种解决方案,允许我将任何内部类型的Deques添加到我的List。现在可以使用以下代码。

/*
 * Please ignore the missing instantiations. It's only a technical
 * precompiler demonstration.
 */

final Deque<String> concreteDeque = null;
final Deque<?> unclearDeque = null;

final List<Deque<?>> list = null;

/*
 * The precompiler demands a parameter of type Deque<?>. That means any
 * Deque. Great! :)
 */
list.add(concreteDeque); // no precompiler error
list.add(unclearDeque); // no precompiler error

final Deque<?> deque = list.get(0); // no precompiler error, great! :)

现在,我想创建自己更具体的界面。

/**
 * A list that holds instances of {@link Deque}. Only for demonstrations.
 *
 * @author Barock
 *
 * @param <DT>
 *            The type that should pass to the {@link Deque}.
 */
public interface DequeList<DT> extends List<Deque<DT>> { }

但是使用这个界面我的代码不再起作用了。

final Deque<String> concreteDeque = null;
final Deque<?> unclearDeque = null;

final DequeList<?> dequeList = null;

// Now the precompiler announces wildcard capture for adding elements!?
dequeList.add(concreteDeque); // precompiler error
dequeList.add(unclearDeque); // precompiler error

final Deque<?> deque = dequeList.get(0); // still no precompiler error. :)

我假设我的DequeList<?>相当于List<Deque<?>>。为什么显然不是?

java generics wildcard
2个回答
2
投票

List<?>List<List<?>>之间存在差异(代替列表,您可以放置​​任何通用集合)。

List<?>专为只读目的而设计。假设List<?>已经包含“某种类型”的元素。由于“某种类型”过于笼统,您无法确定它是什么类型,因此您不能简单地添加因类型安全而导致的任何内容,否则列表将包含异构元素(不同类型)。

考虑以下示例:

List<String> someLetters = Arrays.asList("a", "b", "c", "d", "e");
List<Integer> someInts = Arrays.asList(1, 2, 3, 4, 5);

displayContent(someLetters);
displayContent(someInts);

public static void displayContent(List<?> list) {
    System.out.println(list);
}

将此列表输入Integer时,在someLetters中添加说List<?>是否合法?也许在其他语言中它是可能的,但不是在Java中,因为指定类型的Collection必须是同质的。注意<?>指定隐藏的“某种类型”,但它绝对不意味着您可以添加任何您想要的内容。在上面的示例中,方法displayContent不知道列表的真实类型,因此任何添加都是非法的:

public static void displayContent(List<?> list) {
    // illegal as it may break list's homogenity...
    list.add(1);   
    // illegal as it may break list's homogenity...
    list.add("f");
    System.out.println(list);
}

List<List<?>>的情况下,你有由只读列表组成的列表,允许你添加你想要的任何只读列表但不允许你添加从这个“列表列表”获得的列表中的东西,也就是说,你不能做像上面提到的原因,这样的list.get(0).add(something);

编辑

我不知道为什么DequeList<?>不等同于List<Deque<E>,但我刚刚发现了另一件可能对其他试图解决这个问题的人有用的东西。出于某种原因,你不能添加某种类型的deque。即在Deque<?>DequeList<?>,但你可以添加任何类型的deque i。即DequeDequeList<?>

Deque<String> concreteDeque = null;
Deque<?> unclearDeque = null;
Deque rawDeque = null;

List<Deque<?>> list = null;
list.add(concreteDeque); // OK
list.add(unclearDeque);  // OK
list.add(rawDeque);      // OK

DequeList<?> myList = null; // should be equivalent to List<Deque<?>> 
myList.add(concreteDeque);  // ERROR
myList.add(unclearDeque);   // ERROR
myList.add(rawDeque);       // OK

似乎在“延伸”后,Deque<?>成为强大的DequeList类型。由于DequeList可能含有特定类型e的deques。 G。 Deque<String>,你不能添加Deque中指定或隐藏的任何其他类型的<?>。不幸的是,它没有回答为什么有可能添加rawDeque


0
投票

自从他解释了大部分问题以来,我已经对matoni的答案进行了评价,但我相信他的解释是不完整的。

是的,List<?>将拒绝任何通用参数,特别是它将拒绝任何对add方法的调用,这使得它实际上是只读的。完全相同的事情发生在DequeList<?>,它也是只读的。

然而,人类可以轻易地告诉List<Deque<?>>安全的任何东西也是DequeList<?>的安全形式,反之亦然。编译器无法解决这个问题,为什么会这样?

让我们尝试一下:如果我们试图实例化我们的DequeList并给它与你在第一个片段中使用的声明类型相同的声明类型怎么办?如果我们设法声明是否使用相同的类型,编译器将允许我们对它进行相同的操作。

public static class DequeList<DT> implements List<Deque<DT>> {
    // implement all methods with default implementations to make the compiler happy
}

// oups, doesn't compile: "Wildcard type '?' cannot be instantiated directly"
List<Deque<?>> dequeList = new DequeList<?>();

好吧它没用。显然,通配符只能用于更改现有通用对象的声明类型,而不能用于实例化新对象。我们试试别的:

DequeList<?> dequeList1 = null;
// again, doesn't compile: "Incompatible types. Required: List<java.util.Deque<?>>. Found: DequeList<capture<?>>"
List<Deque<?>> dequeList2 = dequeList1;

因此,似乎List<Deque<?>>DequeList<?>只是编译器的不兼容类型。

我没有完整的解释,但似乎编译器的限制,无法推断这些类型实际上是相同的,我不认为这是不应该编译的根本原因。

© www.soinside.com 2019 - 2024. All rights reserved.