简单的课程:
class Pair<K,V> {
}
还有一些任务:
Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,Long>> c2 = c1; // ok
Collection<Pair<String,?>> c3 = c1; // this does not compile
Collection<? extends Pair<String,?>> c4 = c1; // ok
为什么第三个子弹不能编译而第四个完全合法?
编译错误:
Type mismatch: cannot convert from Collection<Pair<String,Long>> to Collection<Pair<String,?>>
答案 0 :(得分:16)
我将尝试使用两个简单的规则来解释Java泛型。这些规则足以回答你的问题,基本上足以记住几乎所有情况:
X<A>
,否则永远不会分配两种通用类型X<B>
和A = B
。即,默认情况下,泛型是不变的。X<A>
:
X<?>
X<? extends T>
iff A
可分配给T
(将规则递归应用于A
和T
)X<? super T>
iff T
可分配给A
(将规则递归应用于T
和A
)c3 = c1
在您的示例中,您尝试将Collection<Pair<String,Long>>
分配给Collection<Pair<String,?>>
。也就是说,在您的情况下A = Pair<String,Long>
和B = Pair<String,?>
。由于这些类型不相等,它们不可分配;他们违反了规则1 。
问题是,为什么通配符没有帮助?答案很简单:
规则2 NOT 传递。即,X<X<A>>
不能被X<X<?>>
所指定,最外层必须有一个通配符;否则规则2不适用于最外层。
c4 = c1
这里,你在外部类型中有一个通配符。由于它属于外部类型,因此规则2开始执行:A = Pair<String,?>
可分配给B = ? extends Pair<String,Long>
(同样,因为规则2)。因此,这是合法的。
以下是检查任何复杂泛型类型的方法:只需使用这两个规则逐级检查每个泛型级别。从最外层开始。一旦某个级别违反规则,您就知道该分配是非法的;如果所有级别都遵守规则,那么分配是合法的。让我们再次考虑你的类型:
X = Collection<Pair<String,Long>>
Y = Collection<Pair<String,?>>
Z = Collection<? extends Pair<String,?>>
X可分配给Y吗?
// Outermost level:
A = Pair<String,Long>, B = Pair<String,?>
=> B is no wildcard and A != B (Rule 1), so this is illegal!
X可分配给Z吗?
// Outermost level:
A = Pair<String,Long>, B = ? extends Pair<String,?>
=> We got a wildcard, so Rule 2 states this is legal if the inner level is legal
// Inner level: (we have to check both parameters)
A = String, B = String => Equal, Rule 1 applies, fine!
A = Long, B = ? => B is wildcard, Rule 2 applies, fine!
每个级别的通用嵌套要么必须完全相同(A=B
),要么B
需要包含此级别的通配符。
答案 1 :(得分:3)
首先,让我们通过删除额外的类型参数来简化代码:
Collection<List<Long>> c1 = new ArrayList<List<Long>>();
Collection<List<Long>> c2 = c1; // ok
Collection<List<?>> c3 = c1; // this does not compile
Collection<? extends List<?>> c4 = c1; // ok
我们知道List<? extends T>
本质上意味着“List
你可以从T
获得List<?>
,而List<? extends Object>
与List
是一样的。
因此,上述变量的类型可以解释如下:
Object
的集合,可让您从中获取List
Object
的集合,可让您从中获取// The following line compiles,
// because `ArrayList<String>` is a `List` you can get `Object`s from
c3.add(new ArrayList<String>());
// The following line does not compile,
// because type of c4 doesn't allow you to put anything into it
c4.add(new ArrayList<String>());
特别是,这种解释会产生以下结果:
c3 = c1
现在,c3.add(new ArrayList<String>())
被允许,您可以看到c1
会破坏Collection<List<Long>> c1 = new ArrayList<List<Long>>();
Collection<List<?>> c3 = c1;
c3.add(Arrays.asList("foo"));
for (List<Long> l: c1) {
for (Long value: l) {
// Oops, value is not a Long!
}
}
的类型安全性:
{{1}}
答案 2 :(得分:2)
要记住的关键是嵌套通配符不能捕获。
这意味着&#34;正常&#34;您期望从顶级通配符(即通配符代表一个特定某些东西)的行为不适用于嵌套通配符。相反,嵌套通配符代表任何类型。
例如,请接受此声明:
List<?> l;
这意味着l
是一个特定类型的List
。容易。
但是这个怎么样?
Collection<List<?>> c;
不 Collection
个List
个特定类型。这是Collection
个List
s,,其中是一种特定类型。
例如,你期待这样的事情发生:
Collection<List<?>> c = new ArrayList<List<Long>>(); // Not valid, but pretend it is
c.add(new ArrayList<Long>()); // Valid
c.add(new ArrayList<Integer>()); // Invalid, because c is a Collection of Lists of Long
但请考虑一下:
List<?> l = new ArrayList<String>();
c.add(l); // Should this compile?
l
的类型与c
的类型参数完全匹配,对吧?因此,即使l
不是c
,您也不应该将l
添加到List<Long>
吗?
还要考虑这个:
c.iterator().next(); // Assume there is an element to return
这应该返回什么类型? iterator()
会返回Iterator<E>
,next()
会返回E
,这意味着...... c.iterator().next()
会返回List<?>
。这不是您期望的List<Long>
。那是为什么?
因为嵌套通配符不能捕获。这是这里的关键区别。 List<?>
中的通配符不会捕获单个类型&#34;整体&#34;。它为Collection
中的每个元素捕获单个类型。
因此,这是完全有效的代码:
Collection<List<?>> odd = new ArrayList<List<?>>();
odd.add(new ArrayList<String>());
odd.add(new ArrayList<Long>());
List<?> l = odd.iterator().next();
// returns the ArrayList<String>, but because odd is parameterized with
// List<?> we can technically end up with a list of anything
记住这一点,让我们看看你的例子。
Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,Long>> c2 = c1;
这很直观。这些类型完全匹配,因此c1
可分配给c2
。
Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,?>> c3 = c1;
现在,让我们回顾一下。 Collection<Pair<String,?>>
Collection
Pair
String
个Collection
和Pair
个{1}}。它是String
个// Assume an appropriate object was assigned to c3
Pair<String, ?> p1 = new Pair<String, String>("Hello", "World");
Pair<String, ?> p2 = new Pair<String, List<String>>("Lorem", new ArrayList<>());
Pair<String, ?> p3 = new Pair<String, Map<String, Integer>>("Ispum", new HashMap<>());
c3.add(p1);
c3.add(p2);
c3.add(p3);
的{{1}},每个都是一对c3
和一些未知类型,它们可能与另一对类型相同或不同采集。所以这是有效的:
c1
因为这对c1
有效,但不应对c3
有效,因此不允许ArrayList<Pair<String, Long>>
分配Pair<String, Long>
,因为它会允许您将内容放入Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<? extends Pair<String,?>> c4 = c1;
Pair<String, ?>
,而不是List<?>
。
List<Integer>
现在,这有点棘手。顶级捕获一个扩展Pair<String, Long>
的特定类型。由于通配符是特定类型的超类型(例如? extends Pair<String, ?>
是Pair<String, Long>
的超类型),? extends Pair<String, ?>
可以捕获{{1}},因为前者扩展了后者。因此,由于{{1}}与{{1}}分配兼容,因此分配有效。
从这里的各种答案可以看出,有多种方法可以解释嵌套通配符的行为。我想要一个直观的解释,我希望我能实现。
答案 3 :(得分:1)
Java泛型的一个优点是在编译时强化类型检查。所以在泛型中声明的任何内容都应完全匹配。为了使它成为一个简单的答案,我会使用一些例子。
初始化您可以喜欢的数字列表。
List<Number> numbers = new ArrayList<Number>();
这在技术上意味着List“numbers”可以存储任何数字或子类的对象。但是我们不能用子类型初始化这个列表。通用标记<>
中给出的任何内容都应与字符匹配。 (Java 7提供了类型推断)
List<Number> intNumbers = new ArrayList<Interger>(); // Compile Error
List<Number> doubleNumbers = new ArrayList<Double>(); // Compile Error
List<List<String>> list = new ArrayList<ArrayList<String>>(); // Compile Error
即使Integer和Double是Number的子类,泛型也会阻止这些初始化。 <>
中指定的泛型参数应完全匹配。
现在如果赋值紧密绑定,数字列表如何存储子类对象?答案是add(), addlAll().. etc
方法接受E
或任何扩展E
的方法,其中E
是我们提供的泛型类型。因此,如果列表“数字”E是Number
,那么以下陈述完全有效。
List<Integer> intNumbers = new ArrayList<Integer>();
List<Number> numbers = new ArrayList<Number>();
numbers.add(new Integer(1));
numbers.add(new Double(1.0));
numbers.addAll(intNumbers);
另外一个例外是通配符。通配符用于接受任何参数。因此,以下陈述是有效的。
List<?> numbers = new ArrayList<Number>();
Map<?, ?> unKnownMap = new HashMap<String, String>();
// OR
List<Integer> intNumbers = new ArrayList<Integer>();
List<?> numbers = intNumbers;
类似地
Pair<String,?> p = new Pair<String, Long>(); // Is valid.
但是现在我们无法向此列表添加任何内容,因为元素应该扩展?
并且这是一个未知类型,唯一允许的元素是null
,它是每种类型的成员。 Java不会从赋值中推断出这一点。所以下面的结果是错误的
List<?> numbers = new ArrayList<Number>(); // Works fine.
numbers.add(new Integer(1)); // Compile Error
numbers.add(new Double(1.0)); // Compile Error
此外,通配符推断仅在一个级别上发生。任何嵌套都应该像通常的泛型一样完全匹配。所以
List<List<?>> list = new ArrayList<List<String>>(); // Compile error because nested List<String> doesn not exactly match List<?>
List<List<?>> list = new ArrayList<List<?>>(); // OK - Valid
List<List<Integer>> intList = new ArrayList<List<Integer>>();
List<List<?>> numbers = intList; // Compile error because nested List<Integer> doesn not exactly match List<?>
所以这是你的情况
Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,Long>> c2 = c1; // ok
Collection<Pair<String,?>> c3 = c1; // Compile error because nested Pair<String,?> doesn not exactly match Pair<String,Long>
所以你可以做类似
的事情Collection<Pair<String,?>> c3 = new ArrayList<Pair<String,?>>(c2);
//OR
Collection<Pair<String,?>> c3 = new ArrayList<Pair<String,?>>();
c3.addAll(c2);
案例4:当你再次说出List<? extends List<?>>
时,它是第一级。这意味着它会检查所讨论的元素是否扩展List<?>
。
List<? extends List<?>> list = new ArrayList<List<String>>(); // Works similar to List<?> l = new ArrayList<String>();
List<? extends List<List<?>>> numbers = new ArrayList<List<List<String>>>(); // Compile error - Nested level similar to List<List<String>> lst = new ArrayList<List<String>>();
因此Collection<Pair<String,?>> c3 = new ArrayList<Pair<String, Long>>();
与Pair<String,?> p = new Pair<String, Long>();