(?) 通配符泛型的不规则性
Irregularities with the (?) wildcard generic type
我认为泛型中的类型 ?
是 特定的未知类型 。这意味着,声明一个该类型的列表将阻止我们向其中添加任何类型的对象。
List<?> unknownList;
unknownList.add(new Object()); // This is an error.
编译器按预期报错。
但是当未知类型是二级泛型时,编译器似乎并不关心。
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>());
我以为编译器可能根本不关心第二级的泛型参数,但事实并非如此,
List<First<Integer>> firstIntegerList;
firstIntegerList.add(new First<String>()); // This gives a compiler error as expected.
那么,为什么编译器允许我们添加任何类型的元素,而在第二个示例中只有一个未知元素(因此没有任何元素)是可接受的?
注:编译器Java1.8
您可以向 List<T>
添加任何可以存储在 T
类型的引用中的内容:
T item = ...
List<T> list = new ArrayList<>();
list.add(item);
First<?>
是 First<T>
的超类型;因此您可以将对 First<T>
的引用存储在 First<?>
:
类型的变量中
First<?> first = new First<String>();
所以,用 T
代替上面的 First<?>
:
First<?> item = new First<String>();
List<First<?>> list = new ArrayList<>();
list.add(item);
OP 示例中发生的所有事情是省略了临时变量 item
:
firstUnknownList.add(new First<String>());
但是,如果您使用 firstIntegerList
示例执行此操作:
First<Integer> item = new First<String>(); // Compiler error.
List<First<Integer>> list = new ArrayList<>();
list.add(item);
很明显为什么不允许这样做:您不能分配 item
。
也可以看到,您无法对该列表的内容进行任何不安全操作。
如果向接口添加几个方法:
interface First<T> {
T producer();
void consumer(T in);
}
现在,考虑一下您可以使用添加到列表中的元素做什么:
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 */);
}
所以实际上您无法对该列表中的元素做任何违反类型安全的事情(前提是您不做任何故意违反它的事情,例如使用原始类型)。您可以证明泛型 producer/consumer 方法同样安全或被编译器禁止。
所以没有理由不允许允许你这样做。
我会把First
界面改成Box
界面
Box<?> uknownBox
里面有东西的灰色盒子
Box<Apple> appleBox
苹果盒子
List<Box<Apple>> appleBoxList
多盒苹果
List<Box<?>> uknownBoxList
多个未知灰框
appleBoxList.add(new Box<Orange>())
- 无法将装有橙子的盒子添加到苹果盒子列表中
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
这不会编译以防止将灰色(可能不是苹果)框添加到苹果框列表。因为之前的操作是合法的。
一切都是为了 subtype/supertype-relationships。
List<?>
是一个包含未知(但特定)类型元素的列表。您永远不知道此列表中 确切 包含哪种类型。所以你可能不会向它添加对象,因为它们可能是错误的类型:
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);
可能也很清楚你可以做到
List<Number> numbers = null;
Integer integer = null;
numbers.add(integer);
之所以有效,是因为 Number
是 Integer
的真正超类型。将更具体类型的对象添加到列表中并不违反类型安全。
关于第二个例子的关键点是:
First<?>
是每个 First<T>
的超类型
你总是可以像
First<Integer> fInt = null;
First<Integer> fString = null;
First<?> f = null;
f = fInt; // works
f = fString; // works
所以你可以将 First<String>
添加到 List<First<?>>
的原因与你可以将 Integer
添加到 List<Number>
的原因相同:元素您要添加的是列表中预期元素的真实 子类型 。
I believe that the type ? in generics is a specific unknown type.
这有点不准确。是的,一个通配符类型代表一个未知的类型,但在不同的时间可能代表不同的类型:
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();
唯一不变的是,其类型包含通配符的表达式将始终生成其类型符合该通配符的值。由于每个值都有一个不仅仅是通配符的类型,所以可以说通配符在任何时候都代表(更多)"specific" 类型。
我认为泛型中的类型 ?
是 特定的未知类型 。这意味着,声明一个该类型的列表将阻止我们向其中添加任何类型的对象。
List<?> unknownList;
unknownList.add(new Object()); // This is an error.
编译器按预期报错。
但是当未知类型是二级泛型时,编译器似乎并不关心。
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>());
我以为编译器可能根本不关心第二级的泛型参数,但事实并非如此,
List<First<Integer>> firstIntegerList;
firstIntegerList.add(new First<String>()); // This gives a compiler error as expected.
那么,为什么编译器允许我们添加任何类型的元素,而在第二个示例中只有一个未知元素(因此没有任何元素)是可接受的?
注:编译器Java1.8
您可以向 List<T>
添加任何可以存储在 T
类型的引用中的内容:
T item = ...
List<T> list = new ArrayList<>();
list.add(item);
First<?>
是 First<T>
的超类型;因此您可以将对 First<T>
的引用存储在 First<?>
:
First<?> first = new First<String>();
所以,用 T
代替上面的 First<?>
:
First<?> item = new First<String>();
List<First<?>> list = new ArrayList<>();
list.add(item);
OP 示例中发生的所有事情是省略了临时变量 item
:
firstUnknownList.add(new First<String>());
但是,如果您使用 firstIntegerList
示例执行此操作:
First<Integer> item = new First<String>(); // Compiler error.
List<First<Integer>> list = new ArrayList<>();
list.add(item);
很明显为什么不允许这样做:您不能分配 item
。
也可以看到,您无法对该列表的内容进行任何不安全操作。
如果向接口添加几个方法:
interface First<T> {
T producer();
void consumer(T in);
}
现在,考虑一下您可以使用添加到列表中的元素做什么:
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 */);
}
所以实际上您无法对该列表中的元素做任何违反类型安全的事情(前提是您不做任何故意违反它的事情,例如使用原始类型)。您可以证明泛型 producer/consumer 方法同样安全或被编译器禁止。
所以没有理由不允许允许你这样做。
我会把First
界面改成Box
界面
Box<?> uknownBox
里面有东西的灰色盒子
Box<Apple> appleBox
苹果盒子
List<Box<Apple>> appleBoxList
多盒苹果
List<Box<?>> uknownBoxList
多个未知灰框
appleBoxList.add(new Box<Orange>())
- 无法将装有橙子的盒子添加到苹果盒子列表中
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
这不会编译以防止将灰色(可能不是苹果)框添加到苹果框列表。因为之前的操作是合法的。
一切都是为了 subtype/supertype-relationships。
List<?>
是一个包含未知(但特定)类型元素的列表。您永远不知道此列表中 确切 包含哪种类型。所以你可能不会向它添加对象,因为它们可能是错误的类型:
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);
可能也很清楚你可以做到
List<Number> numbers = null;
Integer integer = null;
numbers.add(integer);
之所以有效,是因为 Number
是 Integer
的真正超类型。将更具体类型的对象添加到列表中并不违反类型安全。
关于第二个例子的关键点是:
First<?>
是每个 First<T>
你总是可以像
First<Integer> fInt = null;
First<Integer> fString = null;
First<?> f = null;
f = fInt; // works
f = fString; // works
所以你可以将 First<String>
添加到 List<First<?>>
的原因与你可以将 Integer
添加到 List<Number>
的原因相同:元素您要添加的是列表中预期元素的真实 子类型 。
I believe that the type ? in generics is a specific unknown type.
这有点不准确。是的,一个通配符类型代表一个未知的类型,但在不同的时间可能代表不同的类型:
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();
唯一不变的是,其类型包含通配符的表达式将始终生成其类型符合该通配符的值。由于每个值都有一个不仅仅是通配符的类型,所以可以说通配符在任何时候都代表(更多)"specific" 类型。