简体   繁体   English

通用方法中的Java通用通配符捕获

[英]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. Treverse2()的类型变量,并且如上所述,类型推断发生在调用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 to reverse2() and the parameter T 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之前,无法推断list1list2具有相同的类型。

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是形式的通配符类型参数 ? , then S i is a fresh type variable […]. ,则S i新鲜的类型变量 […]。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM