我遇到了这个问题:
public class Test {
static class TestType {}
static class Pair<A,B>{}
public static void main( String [] args ) throws Exception {
Collection<? extends TestType> val = null;
List<? extends TestType> single =
testSingle( val ); // OK
Pair<String,List<? extends TestType>> pair =
testPair( val ); // ERROR
}
static <T extends TestType> List<T> testSingle( Collection<T> val ){
return null;
}
static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
return null;
}
}
为什么第一个工作,第二个工作?
错误消息ist:
Type mismatch: cannot convert from Test.Pair<String,List<capture#3-of ? extends Test.TestType>> to Test.Pair<String,List<? extends Test.TestType>>
编辑:
使用Braj的答案我设置此项以在不使用泛型类型T时清除问题:
public class Test {
static class TestType {}
static class Implementation extends TestType {}
static class Pair<A,B>{}
public static void main( String [] args ) throws Exception {
Collection<Implementation> val = null;
List<Implementation> sinle = testSingle( val ); // OK
Pair<String,List<Implementation>> pair = testPair( val ); // OK
Pair<String,List<Implementation>> pair2 = testPair2( val ); // ERROR
Pair<String,List<Implementation>> pair3 = testPair3( val ); // ERROR
Pair<String,? extends List<Implementation>> pair4 = testPair4( val ); // ERROR
run( val );
}
private static void run( Collection<? extends TestType> val ){
List<? extends TestType> single = testSingle( val ); // OK
Pair<String,List<? extends TestType>> pair = testPair( val ); // ERROR
Pair<String,List<? extends TestType>> pair2 = testPair2( val ); // ERROR
Pair<String,List<? extends TestType>> pair3 = testPair3( val ); // OK
Pair<String,? extends List<? extends TestType>> pair4 = testPair4( val ); // OK
Pair<String,? extends List<? extends TestType>> pairX = testPair( val ); // OK
//My-Name-Is
@SuppressWarnings( "unchecked" )
Pair<String,List<? extends TestType>> fixed =
(Pair<String,List<? extends TestType>>)
(Pair<String,?>) testPair( val ); // OK but ugly and stupid(?)
}
private static <T extends TestType> List<T> testSingle( Collection<T> val ){
return null;
}
private static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
return null;
}
// Braj1
private static <T extends TestType> Pair<String, List<TestType>> testPair2(Collection<T> val) {
return null;
}
// Braj2
private static <T extends TestType> Pair<String, List<? extends TestType>> testPair3(Collection<T> val) {
return null;
}
// Seelenvirtuose
private static <T extends TestType> Pair<String, ? extends List<? extends TestType>> testPair4(Collection<T> val) {
return null;
}
// This one works in the way I wanted.
private static <T extends TestType> void runOK( Collection<T> val ){
List<T> single = testSingle( val ); // OK
Pair<String,List<T>> pair = testPair( val ); // OK
}
}
EDIT2:我可以使用:
在run()中修复此问题@SuppressWarnings( "unchecked" )
Pair<String,List<? extends TestType>> fixed =
(Pair<String,List<? extends TestType>>)
(Pair<String,?>) testPair( val );
但这是相当丑陋和愚蠢的(?)。
EDIT3:我编辑了上面的内容,包括Seelenviruose的答案,它仍然变得更加怪异。
我仍然不知道为什么需要这样做......
EDIT4:最后让它工作没有丑陋的演员:如果我使用<T extends TestType> run(...)
,编译器不会抱怨。我在上面改了。
答案 0 :(得分:3)
第二种方法调用的返回类型是
Pair<String,List<? extends TestType>>
为什么是返回类型?好吧,编译器从输入参数 val 中推断出类型为Collection<? extends TestType>
的类型。
上限有界通配符是破坏泛型固有不变性的Java机制。有了这样的结构,以下是可能的:
Collection<? extends Number> c1 = new LinkedList<Integer>();
Collection<? extends Number> c2 = new HashSet<Double>();
注意,我介绍了两种多态性。一个用于集合类型,另一个用于类型变量。
如果没有上限带通配符,则以下构造是编译器错误:
Collection<Number> c3 = new LinkedList<Integer>(); // compiler error
为什么会出错?简单:如果您有Collection<Number>
,您可以在其中添加Double
对象。但这不允许进入LinkedList<Integer>
。因此,错误。
现在,这个上边界通配符的缺点是,您无法将任何内容(null
除外)添加到Collection<? extends Number>
。
剪切:如果你理解它到现在为止,其余部分应该很容易。
您现在在通用返回类型中引入了一个附加层。你没有返回一个东西的列表,你返回一对字符串和一些列表。
因此,上限现在不能位于列表元素的T
类型上。它必须放在列表本身!
使用以下方法声明
<T extends TestType> Pair<String, ? extends List<T>> testPair(Collection<T> val) { ... }
将允许以下呼叫:
Pair<String, ? extends List<? extends TestType>> pair = testPair(val);
但是......我怀疑这样一对可能有什么合理的。如果是这样,您必须重新考虑您的设计。
答案 1 :(得分:2)
为什么第一个工作,第二个不工作?
因为List<ASpecificSubTypeOfTestType>
(第一种方法的返回)是List<? extends TestType>
的子类型,但是Pair<String, List<ASpecificSubTypeOfTestType>>
(第二种方法的返回)不是{{1}的子类型}。
让我们暂时将您的示例从Pair<String, List<? extends TestType>>
更改为Pair
,将List
更改为TestType
:
Object
从技术角度来看,public class Test {
public static void main( String [] args ) throws Exception {
Collection<?> val = null;
List<?> single = testSingle( val ); // OK
List<List<?>> pair = testList( val ); // ERROR
}
static <T> List<T> testSingle( Collection<T> val ){
return null;
}
static <T> List<List<T>> testList( Collection<T> val ){
return null;
}
}
是List<SomeSpecificType>
的子类型,但List<?>
不是List<List<SomeSpecificType>>
的子类型,原因与{{1}相同}}不是List<List<?>>
的子类型 - 类型参数是不同的具体类型(一个是List<String>
,另一个是List<Object>
)。
为了更实际的推理,List<SomeSpecificType>
会为某些List<?>
返回testList
。我们不知道List<List<T>>
是什么,但我们知道它是某种具体类型。返回的此T
是一个只能包含T
的列表。如果S不是T,则它不能包含List<List<T>>
,因为List<T>
和List<S>
不是彼此的子类型。即使我们不知道T是什么,我们知道存在一些T的选择,并且所有元素列表必须将该T作为类型参数。
另一方面,您分配给它的类型List<S>
是一个可以同时包含每种类型列表的列表。因此,您可以同时输入List<T>
和List<List<?>>
等等,这样就可以了。无论您选择List<Integer>
是什么,都不能使用List<String>
执行此操作。
因此,这两种类型显然是不相容的。
可能出现什么问题?使用List<List<T>>
引用,您可以将T
和List<List<?>>
插入列表中。然后使用函数内的List<Integer>
引用,您可以将所有元素提取为List<String>
,而不是它们。所以这是不安全的。
您可能会说,如果我从未将内容放入List<List<T>>
引用中该怎么办?这样安全吗?但如果是这种情况,则应使用List<T>
。 List<List<?>>
通配符使其成为使用者,因此除了List<? extends List<?>>
之外,您无法向其中插入任何内容。它还使类型兼容(? extends
是null
)的子类型。
故事的寓意是,更深层次的通配符并不意味着你的想法。
答案 2 :(得分:1)
尝试
private static <T extends TestType> Pair<String, List<TestType>> testPair(Collection<T> val) {...}
Pair<String, List<TestType>> pair = testPair(val);
OR
private static <T extends TestType> Pair<String, List<? extends TestType>> testPair(Collection<T> val) {...}
Pair<String, List<? extends TestType>> pair = testPair(val);
答案 3 :(得分:0)
我仔细阅读了答案并解释了为什么它不起作用,但我没有注意到如何解决它的任何提议。我通过使该方法成为通用方法在我的情况下解决了它。
public main(){
Map<String, List<Integer>> stuff = service.buildStuff();
consumeStuff(stuff);
}
private <T extends Number> consumeStuff(Map<String, List<T>> stuff){
...
}
因此,方法consumeStuff()期望第二种类型的map是一个List,它具有扩展Number的类型(所以Integer或Double)。通过使用一个map(main()中的var stuff)和List的显式类型Integer意味着Java可以在调用泛型方法consumeStuff()时推断出类型。
在consumeStuff内部,我们可以将内部列表的内容视为数字。我们可以深入研究它,从列表中获取一个值并将其传递给接受类型Number的方法/构造函数。
答案 4 :(得分:-1)
与此同时,我发现了有关Wildcard-Capture的页面:http://docs.oracle.com/javase/tutorial/java/generics/capture.html
据我所知,问题是编译生成了不同的类型
Pair<String,List<? extends TestType>>
Pair<String,List<capture#3-of ? extends TestType>>
。在保留原始方法签名的同时解决此问题的“修复”是使用<T extends TestType>
参数化调用方法。对于示例,它看起来像这样:
public class Test {
static class TestType {}
static class Pair<A,B>{}
public static <T extends TestType> void main( String [] args ) throws Exception {
Collection<T> val = null;
List<T> single = testSingle( val ); // OK
Pair<String,List<T>> pair = testPair( val ); // OK
}
static <T extends TestType> List<T> testSingle( Collection<T> val ){
return null;
}
static <T extends TestType> Pair<String,List<T>> testPair( Collection<T> val ){
return null;
}
}
(这是我第一次写这样的主要内容:)
这个强制编译器为变量和方法的返回类型使用exacly相同的类型。
我仍然认为仿制药在某种程度上“错误”并且在这方面无法预测。