假设我有以下内容:
class x {
public static void main(String [] args) {
List <?> a = new LinkedList<Object>();
List <? extends Object> b = new LinkedList<Object>();
List <? super Object> c = new LinkedList<Object>();
abc(a, "Hello"); // (1) Error
abc(b, "Hello"); // (2) Error
abc(c, "Hello"); // (3) ok
def(b); // (4) ok
// Showing inference at work
Integer[] a = {10, 20, 30}; // (5)
T is inferred to be ? extends Object
Method signature: ppp(? extends Object, ? extends Object[])
Method call signature: ppp(String, Integer[]);
ppp("Hello", a); // ok
}
static <T> void abc(List<T> a, T b) {}
static <T> void def(List<T> a) {}
static <T> void ppp(T t1, T[] t2){}
}
Run Code Online (Sandbox Code Playgroud)
首先,请看第5节显示工作中的推理.现在第5节是一个工作部分.
如果这就是它,那么为什么第(1)和(2)条有错误呢?
从我的角度来看,所有这三种方法调用都会产生相同的推断,因为在abc方法调用中没有使用实际的类型参数.
方法参数<T> abc(List <T> a,T b>)
推断<Object> abc(List <Object>,Object)//(4)
请记住,方法abc()和def()是我的方法.编译器不知道我想用这个方法做List.我可能只打印列表大小,甚至可能根本不做任何事情,如上所示.所以这里没有得到或设置.
CONTINUATION - >这对我来说非常混乱.
class y {
public static void main(String [] args) {
List <Integer> a = new LinkedList<Integer>();
List <Object> b = new LinkedList<Object>();
ppp("Hello", new Integer(1)); // (20) ok
qqq("Hello", a); // (21) error
qqq("Hello", b); // (22) ok
}
static <T> void ppp(T t1, T t2) {}
static <T> void qqq(T t1, List <T> t2) {}
}
Run Code Online (Sandbox Code Playgroud)
请注意,除了第二个参数是List而不是Integer之外,第21节与第20节相同.
第20条是好的cos'T被推断为Object.
第22条没问题.第20
条的理由相同.第21条失败了?也可以推断T也是Object - 也会起作用吗?
关于通配符的难点在于意识到? extends Foo并不意味着"任何扩展Foo的东西",而是意味着"某些特定类型扩展了Foo".因为你不在那个定义之内,所以你无法知道它的具体子类型Foo.
更新:
正如我所说,这很复杂.以下是对您的代码的一些评论.
// a list of some specific type, and you don't know what type that is.
// it's a sub-type ob Object, yes, which means that you can do
// Object foo = a.get(0); , but the compiler has no way of knowing
// whether it's a String so you can't pass in a String
List <?> a = new LinkedList<Object>();
// same here. '?' and '? extends Object' are equivalent
List <? extends Object> b = new LinkedList<Object>();
// this is a list of Objects and superclasses thereof.
// since there are no superclasses of Object, this is equivalent to
// List<Object>. And through inheritance, a String is an Object, so
// you can pass it in.
List <? super Object> c = new LinkedList<Object>();
Run Code Online (Sandbox Code Playgroud)
更新2:
这里的问题是你正在处理固定但不可解析的变量.
// you can pass in a List<String> and a String,
// but if you pass in a List<?>, the compiler has no idea what
// '?' is and just can't substitute 'String'.
// 'T' doesn't help you here, because 'T' can't match both
// '?' and 'String'.
static <T> void abc(List<T> a, T b) {}
// this works because 'T' substitutes '?' and doesn't have to worry
// about a 2nd parameter
static <T> void def(List<T> a) {}
Run Code Online (Sandbox Code Playgroud)
阅读这个问题,它可能会对这个问题有所了解:
通过在每种情况下创建一个,您已经设置了一点稻草人LinkedList<Object>。这可能会让人很难发现问题。你必须记住的是,当编译器到达这些方法调用时,它不知道你创建了一个LinkedList<Object>. LinkedList<Integer>例如,它可能是一个。
因此,让我们通过更有趣的初始化来查看您的代码:
List<Integer> integers = new LinkedList<Integer>();
List <?> a = integers;
List <? extends Object> b = integers;
List <? super Object> c = new LinkedList<Object>();
//INVALID. T maps to a type that could be Object OR anything else. "Hello"
//would only be type-assignable to T if T represented String, Object, CharSequence,
//Serializable, or Comparable
abc(a, "Hello");
//INVALID. T maps to a type that could be Object OR anything else. "Hello"
//would only be type-assignable to T if T represented String, Object, CharSequence,
//Serializable, or Comparable
abc(b, "Hello");
//VALID. T maps to an unknown super type of Object (which can only be Object itself)
//since String is already type-assignable to Object, it is of course guaranteed to be
//type-assignable to any of Object's super types.
abc(c, "Hello");
Integer i1 = integers.get(0);
Integer i2 = integers.get(1);
Run Code Online (Sandbox Code Playgroud)
不难看出 abc 的实现是否是这样的:
//a perfectly valid implementation
static <T> void abc(List<T> a, T b) {
a.add(b);
}
Run Code Online (Sandbox Code Playgroud)
ClassCastException初始化时你会得到一个i1.
在我看来,所有这 3 个方法调用都会生成以下推论,因为 abc 静态方法调用上没有使用实际类型参数。
Run Code Online (Sandbox Code Playgroud)method parameter <T> abc (List <T> a, T b>) inferred <Object> abc (List <Object>, Object) // (4)
这是绝对错误的。在您的任何T示例中都没有推断出这一点,即使在 的情况下也没有推断出这一点。T 被解析为 a 的捕获,除非您可以将 String 分配给该捕获(就像它的情况一样),否则您将遇到类型错误。Object? super Object? super Object
关于您的更新(我已将您的通用数组替换为通用数组,List<T>因为通用数组不必要地使问题变得模糊):
// Showing inference at work
List<Integer> a = Arrays.asList(10, 20, 30); // (5)
T is inferred to be ? extends Object
Method signature: ppp(? extends Object, List<? extends Object>)
Method call signature: ppp(String, List<Integer>);
ppp("Hello", a); // ok
Run Code Online (Sandbox Code Playgroud)
这是不正确的。你犯的关键错误就在这里:
Method signature: ppp(? extends Object, List<? extends Object>)
Run Code Online (Sandbox Code Playgroud)
这根本不是捕获引擎所做的或应该将您的调用转换成的内容。它解析T为<? extends Object>的一个特定捕获<? extends Object>。我们就这样称呼它吧capture-1-of<? extends Object>。因此你的方法必须是这样的:
Method signature: ppp(capture-1-of<? extends Object>, List<capture-1-of<? extends Object>>)
Run Code Online (Sandbox Code Playgroud)
这意味着两个参数之间存在绑定...它们必须解析为相同的捕获。一般来说,很难告诉编译器两个东西是相同的捕获。事实上,即使这也不是有效的调用ppp(即使它们显然是相同的捕获):
List<? extends Integer> myList;
ppp(myList.get(0), myList);
Run Code Online (Sandbox Code Playgroud)
我们可以调用的一种方法ppp是通过通用中介:
public static <T> void pppCaller(List<T> items) {
ppp(items.get(0), items);
}
pppCaller(myList);
Run Code Online (Sandbox Code Playgroud)
使用通配符列表调用 ppp的唯一可靠方法是像这样调用它:
List<? extends Integer> myList = new ArrayList<Integer>();
ppp(null, myList);
Run Code Online (Sandbox Code Playgroud)
这是因为这null是唯一可以分配给任何内容的东西。另一方面,如果你有这个方法:
private static <T> void qqq(T item1, T item2) {}
Run Code Online (Sandbox Code Playgroud)
你确实可以像这样调用它:
List<? extends Integer> myList;
qqq(myList.get(0), myList.get(1));
Run Code Online (Sandbox Code Playgroud)
因为在这种情况下,推理可以将T泛化为Object。由于List<? extends Integer>与 不协变List<Object>,因此它不能对 做同样的事情ppp()。
然而,大多数人为解决这个问题所做的就是放松他们的方法签名。相反,声明ppp如下:
public static <T> ppp(T item, List<? super T> items) {
}
Run Code Online (Sandbox Code Playgroud)
这遵循肖恩在他的“PECS”帖子中提出的指导方针
如果(你的方法)产生,则使用extends,如果消耗,则使用super。
关于您的最新编辑:
public static void main(String [] args) {
List <Integer> a = new LinkedList<Integer>();
qqq("Hello", a); // (21) error
}
static <T> void qqq(T t1, List <T> t2) {}
Run Code Online (Sandbox Code Playgroud)
Object不是的有效推论T。我认为这是您所缺少的基本内容,所以我会说清楚:
AList<Integer>不可类型分配给List<Object>
一点也不。如果是的话,你可以这样做,这显然违反了类型安全:
List<Integer> myInts = new ArrayList<Integer>();
List<Object> myObjects = myInts; //doesn't compile!
myObjects.add("someString");
Integer firstInt = myInts.get(0); //ClassCastException!
Run Code Online (Sandbox Code Playgroud)
因此T不能推断为Object,因为它需要将 a 分配List<Integer>给类型为 的变量List<Object>。