简体   繁体   English

(?)通配符泛型类型的不规则

[英]Irregularities with the (?) wildcard generic type

I believe that the type ? 我相信那种? in generics is a specific unknown type . 在泛型中是一种特定的未知类型 Which means, declaring let's say a list of that type would prevent us from adding any type of object into it. 这意味着,声明让我们说这种类型的列表会阻止我们在其中添加任何类型的对象。

List<?> unknownList;
unknownList.add(new Object()); // This is an error.

The compiler gives an error as expected. 编译器按预期给出错误。

But when the unknown type is a second level generics, the compiler doesn't seem to care. 但是当未知类型是二级泛型时,编译器似乎并不关心。

class First<T> {}

List<First<?>> firstUnknownList;

// All these three work fine for some reason.
firstUnknownList.add(new First<>());
firstUnknownList.add(new First<Integer>());
firstUnknownList.add(new First<String>());

I thought probably the compiler doesn't care about generic parameter in the second level at all, but it's not the case, 我想可能编译器根本不关心二级中的泛型参数,但事实并非如此,

List<First<Integer>> firstIntegerList;
firstIntegerList.add(new First<String>()); // This gives a compiler error as expected.

So, why does the compiler allow us adding any kind of element when only an unknown element (and hence nothing) is acceptable in the second example? 那么,为什么编译器允许我们在第二个例子中只接受一个未知元素(因而没有任何东西)时添加任何类型的元素?

Note: Compiler Java 1.8 注意:编译器Java 1.8

You can add anything to a List<T> that you can store in a reference of type T : 您可以向List<T>添加任何可以存储在类型T的引用中的内容:

T item = ...
List<T> list = new ArrayList<>();
list.add(item);

First<?> is a supertype of First<T> ; First<?>First<T>的超类型; so you can store a reference to a First<T> in a variable of type First<?> : 所以你可以在First<?>类型的变量中存储对First<T>的引用:

First<?> first = new First<String>();

So, substituting T for First<?> above: 所以,用T代替上面的First<?>

First<?> item = new First<String>();
List<First<?>> list = new ArrayList<>();
list.add(item);

All that is happening in OP's example is that the temporary variable item is omitted: OP的例子中发生的所有事情都是省略了临时变量item

firstUnknownList.add(new First<String>());

However, if you do this with the firstIntegerList example: 但是,如果使用firstIntegerList示例执行此firstIntegerList

First<Integer> item = new First<String>(); // Compiler error.
List<First<Integer>> list = new ArrayList<>();
list.add(item);

it is clear why that's not allowed: you can't make the assignment of item . 很明显为什么不允许这样做:你不能进行item的分配。


It's also possible to see that you can't do anything unsafe with the contents of that list. 也可以看到你不能对该列表的内容做任何不安全的事情。

If you add a couple of methods to the interface: 如果向界面添加几个方法:

interface First<T> {
  T producer();
  void consumer(T in);
}

Now, consider what you can do with the elements that you added to the list: 现在,考虑一下您可以对添加到列表中的元素执行的操作:

for (First<?> first : firstUnknownList) {
  // OK at compile time; OK at runtime unless the method throws an exception.
  Object obj = first.producer();

  // OK at compile time; may fail at runtime if null is not an acceptable parameter.
  first.consumer(null);

  // Compiler error - you can't have a reference to a ?.
  first.consumer(/* some maybe non-null value */);
}

so there isn't actually anything that you can really do with elements of that list that would violate type safety (provided you don't do anything willful to violate it, like using raw types). 所以实际上没有任何东西你可以真正做到该列表中违反类型安全的元素(假如你不做任何故意违反它,比如使用原始类型)。 You can demonstrate that generic producer/consumer methods are similarly safe or forbidden by the compiler. 您可以证明编译器同样安全或禁止通用生产者/消费者方法。

So there is no reason not to allow you to do this. 因此,没有理由不让你这样做。

I'll change First interface to Box interface 我将First界面更改为Box界面

Box<?> uknownBox gray box with something in it Box<?> uknownBox灰色框中有一些东西

Box<Apple> appleBox box with apple Box<Apple> appleBox盒子与苹果

List<Box<Apple>> appleBoxList many boxes with apples List<Box<Apple>> appleBoxList许多带苹果的盒子

List<Box<?>> uknownBoxList many unknown gray boxes List<Box<?>> uknownBoxList许多未知的灰色框

appleBoxList.add(new Box<Orange>()) - can't add box with oranges to list of apple boxes appleBoxList.add(new Box<Orange>()) - 无法将橙色框添加到苹果框列表中

unknownBoxList.add(new Box<?>()) - we don't know what's in that gray boxes, adding one more unknown gray box changes nothing unknownBoxList.add(new Box<?>()) - 我们不知道那个灰色框中有什么,添加一个未知的灰色框什么都没改变

unknownBoxList.add(new Box<Orange>()) - same rules when you add specific boxes
unknownBoxList.add(new Box<Apple>()) - since you are not allowed to 'open' them

unknownBoxList = appleBoxList this does not compile to prevent adding gray (possibly not apple) boxes to apple box list. unknownBoxList = appleBoxList这不编译以防止将灰色(可能不是苹果)框添加到apple box列表。 because of that previous operation is legal. 因为以前的操作是合法的。

It's all about subtype/supertype-relationships. 这都与子类型/超类型关系有关。

List<?> is a list that contains elements of an unknown (but specific) type. List<?>是一个包含未知(但特定)类型元素的列表。 You never know which type exactly is contained in this list. 您永远不知道此列表中包含的确切类型。 So you may not add objects to it, because they may be of the wrong type: 所以你可能不会向它添加对象,因为它们可能是错误的类型:

List<Integer> ints = new ArrayList<Integer>();
List<?> unknowns = ints;

// It this worked, the list would contain a String....
unknowns.add("String"); 

// ... and this would crash with some ClassCastException
Integer i = ints.get(0);

It may also be clear that you can do 你也可以做到

List<Number> numbers = null;
Integer integer = null;
numbers.add(integer); 

This works because Number is a true supertype of Integer . 这是有效的,因为Number是一个真正的Integer超类型。 It does not violate the type safety to add an object of a more specific type to a list. 将更具体类型的对象添加到列表中不违反类型安全性。


The key point regarding the second example is: 关于第二个例子的关键点是:

First<?> is a supertype of every First<T> First<?>是每个First<T>的超类型

You can always to something like 你可以随时做类似的事情

First<Integer> fInt = null;
First<Integer> fString = null;

First<?> f = null;
f = fInt; // works
f = fString; // works

So the reason of why you can add a First<String> to a List<First<?>> is the same as why you can add an Integer to a List<Number> : The element that you want to add is of a true subtype of the elements that are expected in the list. 因此,为什么可以将第一个First<String>添加到List<First<?>>的原因与为什么可以将一个Integer添加到List<Number> :要添加的元素是真的列表中预期的元素的子类型

I believe that the type ? 我相信那种? in generics is a specific unknown type. 在泛型中是一种特定的未知类型。

This is slightly inaccurate. 这有点不准确。 Yes, a wildcard type stands for an unknown type, but may stand for different types at different times: 是的,通配符类型代表未知类型,但可能在不同时间代表不同类型:

List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();

The only invariant is that an expression whose type contains a wildcard will always yield a value whose type conforms to that wildcard. 唯一的不变量是类型包含通配符的表达式将始终生成一个类型符合该通配符的值。 Since every value has a type that is not just a wildcard, one could say that wildcard stands for a (more) "specific" type at any time. 由于每个值都有一个不仅仅是通配符的类型,因此可以说通配符随时代表(更多)“特定”类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM