[英]Java Generics Wildcard capture in a generic method
I'm studying Java Generics and I'm reading the (very good) book by Naftalin and Wadler, and I got where he's talking about capturing the wildcard in a generic method like the implementation in Collections.reverse : 我正在学习Java泛型,并且正在阅读Naftalin和Wadler的(非常好)这本书,而我知道他正在谈论如何使用通用方法(如Collections.reverse中的实现)捕获通配符:
public static <T> void reverse(List<T> list){
List<T> temp=new ArrayList<>(list);
for(int i=0;i<list.size();i++)
list.set(i,temp.get(list.size()-1-i));
}
he says that the method in the Collections class is implemented using a wildcard for simplicity: 他说,为简单起见,使用通配符实现Collections类中的方法:
public static void reverse(List<?> list){
//code..
}
but using the first method body wouldn't work: 但是使用第一种方法主体将不起作用:
public static void reverse(List<?> list){
List<Object> temp=new ArrayList<Object>(list); //legal
for(int i=0;i<list.size();i++)
list.set(temp.get(list.size()-1-i)); //illegal
}
it doesn't work because it attempts to put a Object type element in a list whose type is unknown(?) and it could be everything extending Object (which is ..well,everything) 它不起作用,因为它尝试将Object类型的元素放入类型未知的列表中(?),并且可能是扩展Object的所有内容(..well,everything)
so calling the first method from the second should do the trick: 因此从第二个调用第一个方法应该可以解决问题:
public static void reverse1(List<?> list){
reverse2(list);
}
public static <T> void reverse2(List<T> list){
List<T> temp=new ArrayList<T>(list);
for(int i=0;i<list.size();i++)
list.set(i,temp.get(list.size()-1-i));
}
now,following what happens on method call,for example passing a 现在,按照方法调用发生的情况进行操作,例如传递一个
List<String> myList
1) List<String> myList
is up-casted to a local variable String<?> list
(String extending Object, which is the upper bound of the wildcard, makes List<String>
subtype of List<?>
) 1)
List<String> myList
向上转换为局部变量String<?> list
(字符串扩展对象,这是通配符的上限,使List<String>
成为List<?>
子类型)
2) list
is now passed to reverse2()
and the parameter T is inferred to be ? extends Object
2)现在将
list
传递给reverse2()
,并将参数T推断为? extends Object
? extends Object
,now how could I use this as a parameter when I instantiate new ArrayList<T>()
??? ? extends Object
,现在实例化新的ArrayList<T>()
时如何将其用作参数? this is something clearly illegal in Java code, so something else must be happening,please can youtell me what is it?? 这在Java代码中显然是非法的,因此还必须进行其他事情,请告诉我这是什么吗?
thanks 谢谢
Luca 卢卡
The T
parameter in reverse2()
isn't inferred to be ? extends Object
reverse2()
的T
参数不能推断为? extends Object
? extends Object
, and no instantiation is performed using the wildcard, ?
? extends Object
,并且不使用通配符?
进行实例化?
. 。
Inference would only occur in a method that calls reverse2()
. 推论只会在调用
reverse2()
的方法中发生。 For example, if you call Collections.emptyList()
, what is the type parameter? 例如,如果调用
Collections.emptyList()
,则类型参数是什么? In that example, it's unknown, but it can usually be inferred at the calling site: 在该示例中,它是未知的,但通常可以在调用站点上进行推断:
List<String> empty = Collections.emptyList();
is inferred to be a call to Collections.<String>emptyList()
(the explicit form). 推断是对
Collections.<String>emptyList()
的调用(显式形式)。
In your case, T
has no restriction, so any type is compatible. 在您的情况下,
T
没有限制,因此任何类型都是兼容的。 If the type variable were declared as T extends String
, however, the wildcard ?
如果类型变量声明为
T extends String
,则通配符?
would be too general to satisfy that restriction, and the call would be illegal. 太笼统而无法满足该限制,因此通话将是非法的。
ok, I got it,so what is it T then?
好,我知道了,那T是什么? I mean, what does T is inferred to be?
我的意思是,T推断是什么?
T
is a type variable in reverse2()
, and as I explained above, type inference happens in the caller, not the callee, so T
isn't "inferred" to be anything. T
是reverse2()
的类型变量,并且如上所述,类型推断发生在调用reverse2()
,而不是在被调用方中,因此T
不能“推断”为任何东西。
Maybe what you mean is what type shows up in the compiled byte code? 也许您的意思是编译后的字节码中会显示什么类型? In this case, no variables of type
T
are declared; 在这种情况下,不会声明类型
T
变量; T
is never used, and no type checking is done. 从不使用
T
,并且不进行类型检查。 So, consider the following contrived example: 因此,请考虑以下人为的示例:
final class Reverse {
static <T extends String> void reverse(List<T> list) {
List<T> tmp = new ArrayList<>(list);
for (int i = 0; i < list.size(); ++i)
list.set(i, tmp.get(list.size() - 1 - i));
}
}
Now a client that calls that method: 现在,一个调用该方法的客户端:
final class Test {
public static void main(String... argv) {
List<String> list = Arrays.asList("A", "B", "C");
Reverse.reverse(list);
System.out.println(list);
}
}
Compile these classes together and run Test
, and you'll get [C, B, A]
, as expected. 一起编译这些类并运行
Test
,您将按预期获得[C, B, A]
。 Now, without recompiling Test
, change the signature of the reverse()
method and recompile only the Reverse
class: 现在, 无需重新编译
Test
,即可更改reverse()
方法的签名并仅重新编译Reverse
类:
static <T extends Integer> void reverse(List<T> list)
Re-running Test
will produce the same result, not a failure! 重新运行
Test
将产生相同的结果,而不是失败!
Now change the implementation of the reverse()
method, and again, recompile only the Reverse
class: 现在,更改
reverse()
方法的实现,并再次仅重新编译Reverse
类:
static <T extends Integer> void reverse(List<T> list) {
List<T> tmp = new ArrayList<>(list);
for (int i = 0; i < list.size(); ++i) {
T el = tmp.get(list.size() - 1 - i);
list.set(i, el);
}
}
This time, running Test
will produce the failure you might have expected last time: 这次,运行
Test
将产生您上次可能预期的失败:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
That's because T
was actually referenced now: 这是因为现在实际上已经引用了
T
:
T el = tmp.get(list.size() - 1 - i);
Into this assignment the compiler will insert a cast to the upper bound of the type parameter, which in this case is Integer
: 在此分配中,编译器将强制类型转换插入类型参数的上限,在本例中为
Integer
:
T el = (Integer) tmp.get(list.size() - 1 - i);
If the type T
is unrestricted (its upper bound is Object
) no cast is performed, since it could never fail. 如果类型
T
是不受限制的(其上限是Object
),则不会执行任何强制类型转换,因为它永远不会失败。
list
is now passed toreverse2()
and the parameterT
is inferred to be? extends Object
现在将
list
传递给reverse2()
并推断参数T
为? extends Object
? extends Object
, […].? extends Object
,[…]。
When reverse2
is called, the wildcard is captured to a fresh type variable which is no longer a wildcard .* The type of T
is inferred to be this captured type. 调用
reverse2
,通配符将捕获到一个新的类型变量,该变量不再是通配符 。* T
的类型被推断为此捕获的类型。
The type system ensures that we can only use this feature in a safe manner. 类型系统确保我们只能以安全的方式使用此功能。
For example, suppose we had a method which accepted two lists: 例如,假设我们有一个接受两个列表的方法:
static <T> void swap2(List<T> list1, List<T> list2) {
List<T> temp = new ArrayList<>(list1);
list1.clear();
list1.addAll(list2);
list2.clear();
list2.addAll(temp);
}
If we tried to call this with wildcard captures: 如果我们尝试使用通配符捕获来调用此方法:
static void swap(List<?> list1, List<?> list2) {
swap2(list1, list2); // <- doesn't compile
}
This will fail to compile, because the compiler knows that it cannot deduce that list1
and list2
had the same type before they were passed to swap
. 这将无法编译,因为编译器知道在传递给
swap
之前,无法推断list1
和list2
具有相同的类型。
reverse2
is type-safe because it adds elements to the list which were originally in the list to begin with. reverse2
是类型安全的,因为它向列表中添加了最初在列表中开始的元素。
* Technical proof is 5.1.10 Capture Conversion : *技术证明是5.1.10捕获转换 :
If
T i
is a wildcard type argument of the form?
如果
T i
是形式的通配符类型参数?
, thenS i
is a fresh type variable […].,则
S i
是新鲜的类型变量 […]。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.