简体   繁体   English

Java Generics - 这两个方法声明是等价的吗?

[英]Java Generics - are these two method declarations equivalent?

Given some class SomeBaseClass , are these two method declarations equivalent? 给定一些SomeBaseClass类,这两个方法声明是否等效?

public <T extends SomeBaseClass> void myMethod(Class<T> clz)

and

public void myMethod(Class<? extends SomeBaseClass> clz)

For the caller: yes, they are equivalent. 对于呼叫者:是的,它们是等效的。

For the code inside the method: no. 对于方法内部的代码:没有。

The difference is that within the code of the first example you can use the type T (for example to hold an object created by clz.newInstance() ), while in the second you can't. 区别在于,在第一个示例的代码中,您可以使用类型T(例如,用于保存由clz.newInstance()创建的对象),而在第二个示例中则不能。

No, they're not. 不,他们不是。 With the first definition, you can use the type T inside the method definition, eg create an ArrayList<T> or return T. With the second definition, that's not possible. 使用第一个定义,您可以在方法定义中使用类型T,例如创建一个ArrayList<T>或返回T.使用第二个定义,这是不可能的。

Bounded wildcards are subject to certain restrictions to avoid heap pollution. 有界通配符受到某些限制,以避免堆污染。

When you use the wildcard ? 当你使用通配符? extends X you know you can read generic information, but you cannot write. 扩展X你知道你可以阅读通用信息,但你不能写。

For instance 例如

List<String> jedis = new ArrayList<String>();
jedis.add("Obiwan");

List<? extends CharSequence> ls = jedis
CharSequence obiwan = ls.get(0); //Ok
ls.add(new StringBuffer("Anakin")); //Not Ok

The compiler avoided heap pollution when you tried to add a CharSequence (ie StringBuffer) to the collection. 当您尝试将CharSequence(即StringBuffer)添加到集合时,编译器避免了堆污染。 Because the compiler cannot be sure (due to wildcards) that the actual implementation of the collection is of type StringBuffer. 因为编译器无法确定(由于通配符)集合的实际实现是StringBuffer类型。

When you use ? 你用的时候? super X you know you can write generic information, but you cannot be sure of the type of what you read. 超级X你知道你可以写一般信息,但你不能确定你所读的类型。

For instance 例如

List<Object> jedis = new ArrayList<Object>();
jedis.add("Obiwan"); 

List<? super String> ls = jedis;
ls.add("Anakin"); //Ok
String obiwan = ls.get(0); //Not Ok, we can´t be sure list is of Strings.

In this case, due to wildcards, the compiler knows that the actual implementation of the collection could be anything in the ancestors of String. 在这种情况下,由于通配符,编译器知道集合的实际实现可以是String的祖先中的任何内容。 Thus it cannot guarantee that what you will get will be a String. 因此,它不能保证你将得到的将是一个字符串。 Right? 对?

This same restrictions are the ones you would be subject too in any declaration with bounded wildcards. 在具有有界通配符的任何声明中,您也会遇到同样的限制。 These are typically known as the get/put principle. 这些通常被称为get / put原则。

By using a type parameter T you change the story, from the method standpoint you are not using a bounded wildcard but an actual type and therefore you could "get" and "put" things into instances of the class and the compiler would not complain. 通过使用类型参数T,您可以更改故事,从方法角度来看,您没有使用有界通配符而是实际类型,因此您可以“获取”并“将”放入类的实例中,编译器不会抱怨。

For instance, consider the code in Collections.sort method. 例如,考虑Collections.sort方法中的代码。 If we write a method as follows, we would get a compile error: 如果我们编写如下的方法,我们会得到一个编译错误:

public static void sort(List<? extends Number> numbers){
    Object[] a = numbers.toArray();
    Arrays.sort(a);
    ListIterator<? extends Number> i = numbers.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((Number)a[j]); //Not Ok, you cannot be sure the list is of Number
    }
}

But if you write it like this, you can do the work 但如果你这样写,你可以做的工作

public static <T extends Number> void sort(List<T> numbers){
    Object[] a = numbers.toArray();
    Arrays.sort(a);
    ListIterator<T> i = numbers.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

And you could even invoke the method with collections bounded with wildcards thanks to a thing called capture conversion: 你甚至可以使用带有通配符的集合来调用方法,这要归功于一个叫做捕获转换的东西:

List<? extends Number> ints = new ArrayList<Integer>();
List<? extends Number> floats = new ArrayList<Float>();
sort(ints);
sort(floats);

This could not be achieved otherwise. 否则无法实现这一点。

In summary, as others said from the caller standpoint they are alike, from the implementation standpoint, they are not. 总而言之,正如其他人从呼叫者的角度所说的那样,从实施的角度来看,他们是相似的。

No. On top of my head, I can think of the following differences: 不。最重要的是,我可以想到以下差异:

  1. The two versions are not override-equivalent. 这两个版本不是覆盖等效的。 For instance, 例如,

     class Foo { public <T extends SomeBaseClass> void myMethod(Class<T> clz) { } } class Bar extends Foo { public void myMethod(Class<? extends SomeBaseClass> clz) { } } 

    does not compile: 不编译:

    Name clash: The method myMethod(Class) of type Bar has the same erasure as myMethod(Class) of type Foo but does not override it 名称冲突:类型为Bar的myMethod(Class)方法与Foo类型的myMethod(Class)具有相同的擦除但不覆盖它

  2. If a type parameter appears more than once in a method signature, it always represents the same type, but if a wildcard appears more than once, each occurrence may refer to a different type. 如果类型参数在方法签名中出现多次,则它始终表示相同的类型,但如果通配符出现多次,则每次出现可能引用不同的类型。 For instance, 例如,

     <T extends Comparable<T>> T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; } 

    compiles, but 编译但是

     Comparable<?> max(Comparable<?> a, Comparable<?> b) { return a.compareTo(b) > 0 ? a : b; } 

    does not, because the latter may be called by 没有,因为后者可能被称为

     max(Integer.MAX_VALUE, "hello"); 
  3. The method body may refer to the actual type used by the caller using a type parameter, but not using a wildcard type. 方法体可以使用类型参数来引用调用者使用的实际类型,但不使用通配符类型。 For instance: 例如:

     <T extends Comparable<T>> T max(T... ts) { if (ts.length == 0) { return null; } T max = ts[0]; for (int i = 1; i < ts.length; i++) { if (max.compareTo(ts[i]) > 0) { max = ts[i]; } } return max; } 

    compiles. 编译。

@Mark @Joachim @Michael @Mark @Joachim @Michael

see the example in JLS3 5.1.10 Capture Conversion 请参阅JLS3 5.1.10捕获转换中的示例

public static void reverse(List<?> list) { rev(list);}
private static <T> void rev(List<T> list){ ... }

so the <?> version can do anything the <T> version can do. 所以<?>版本可以做<T>版本可以做的任何事情。

this is easy to accept if the runtime is reified. 如果运行时具体化,这很容易接受。 a List<?> object must be a List<X> object of some specific non-wildcard X anyway, and we can access this X at runtime. 无论如何, List<?>对象必须是某个特定非通配符XList<X>对象,我们可以在运行时访问此X So there's no difference using a List<?> or a List<T> 所以使用List<?>List<T>没有区别

With type erasure, we have no access to T or X , so there's no difference either. 对于类型擦除,我们无法访问TX ,因此也没有区别。 We can insert a T into a List<T> - but where can you get a T object, if T is private to the invocation, and erased? 我们可以在List<T>插入一个T - 但是如果T对调用是私有的,那么你在哪里可以得到一个T对象并被删除? There are two possibilities: 有两种可能性:

  1. the T object is already stored in the List<T> . T对象已存储在List<T> so we are manipulating elements themselves. 所以我们自己操纵元素。 As the reverse/rev example shows, there's no problem doing this to List<?> either 正如reverse/rev示例所示,对List<?>执行此操作也没有问题

  2. it comes out-of-band . 它出现在带外 There's other arrangement made by the programmer, so that an object somewhere else is guaranteed to be of type T for the invocation. 程序员还有其他的安排,所以其他地方的对象保证为调用类型T Unchecked casting must be done to override compiler. 必须完成未经检查的转换才能覆盖编译器。 Again, no problem to do the same thing to List<?> 同样,对List<?>做同样的事情也没问题

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

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