Java 何时使用泛型方法,何时使用通配符?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18176594/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When to use generic methods and when to use wild-card?
提问by benz
I am reading about generic methods from OracleDocGenericMethod. I am pretty confused about the comparison when it says when to use wild-card and when to use generic methods. Quoting from the document.
我正在阅读OracleDocGenericMethod 中的泛型方法。当它说明何时使用通配符以及何时使用泛型方法时,我对比较感到非常困惑。从文档中引用。
interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }
We could have used generic methods here instead:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }
[…] This tells us that the type argument is being used for polymorphism; its only effect is to allow a variety of actual argument types to be used at different invocation sites. If that is the case, one should use wildcards. Wildcards are designed to support flexible subtyping, which is what we're trying to express here.
interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }
我们可以在这里使用泛型方法:
interface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }
[…] 这告诉我们类型参数被用于多态;它唯一的作用是允许在不同的调用站点使用各种实际参数类型。如果是这种情况,则应使用通配符。通配符旨在支持灵活的子类型,这就是我们在这里试图表达的。
Don't we think wild card like (Collection<? extends E> c);
is also supporting kind of
polymorphism? Then why generic method usage is considered not good in this?
难道我们不认为通配符(Collection<? extends E> c);
也支持某种多态性吗?那么为什么泛型方法的使用在这方面被认为不好呢?
Continuing ahead, it states,
继续前进,它指出,
Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type. If there isn't such a dependency, a generic method should not be used.
泛型方法允许使用类型参数来表达一个或多个参数的类型之间的依赖关系和/或其返回类型。如果没有这样的依赖关系,则不应使用泛型方法。
What does this mean?
这是什么意思?
They have presented the example
他们提出了这个例子
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }
[…]
We could have written the signature for this method another way, without using wildcards at all:
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }
[…]
我们可以用另一种方式为这个方法编写签名,根本不使用通配符:
class Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }
The document discourages the second declaration and promotes usage of first syntax? What's the difference between the first and second declaration? Both seems to be doing the same thing?
该文件不鼓励第二个声明并促进使用第一个语法?第一个和第二个声明有什么区别?两者似乎都在做同样的事情?
Can someone put light on this area.
有人可以照亮这个区域吗?
采纳答案by Rohit Jain
There are certain places, where wildcards, and type parameters do the same thing. But there are also certain places, where you have to use type parameters.
在某些地方,通配符和类型参数做同样的事情。但也有一些地方,你必须使用类型参数。
- If you want to enforce some relationship on the different types of method arguments, you can't do that with wildcards, you have to use type parameters.
- 如果您想对不同类型的方法参数强制执行某种关系,则不能使用通配符来实现,您必须使用类型参数。
Taking your method as example, suppose you want to ensure that the src
and dest
list passed to copy()
method should be of same parameterized type, you can do it with type parameters like so:
以你的方法为例,假设你想确保传递给方法的src
和dest
列表copy()
应该是相同的参数化类型,你可以使用类型参数来做到这一点,如下所示:
public static <T extends Number> void copy(List<T> dest, List<T> src)
Here, you are ensured that both dest
and src
have same parameterized type for List
. So, it's safe to copy elements from src
to dest
.
在这里,您可以确保dest
和src
具有相同的参数化类型List
。因此,从src
to复制元素是安全的dest
。
But, if you go on to change the method to use wildcard:
但是,如果您继续更改使用通配符的方法:
public static void copy(List<? extends Number> dest, List<? extends Number> src)
it won't work as expected. In 2nd case, you can pass List<Integer>
and List<Float>
as dest
and src
. So, moving elements from src
to dest
wouldn't be type safe anymore.
If you don't need such kind of relation, then you are free not to use type parameters at all.
它不会按预期工作。在第二种情况下,您可以通过List<Integer>
andList<Float>
作为dest
and src
。因此,从src
to移动元素dest
不再是类型安全的。如果您不需要这种关系,那么您可以完全不使用类型参数。
Some other difference between using wildcards and type parameters are:
使用通配符和类型参数之间的其他一些区别是:
- If you have only one parameterized type argument, then you can use wildcard, although type parameter will also work.
- Type parameters support multiple bounds, wildcards don't.
Wildcards support both upper and lower bounds, type parameters just support upper bounds. So, if you want to define a method that takes a
List
of typeInteger
or it's super class, you can do:public void print(List<? super Integer> list) // OK
but you can't use type parameter:
public <T super Integer> void print(List<T> list) // Won't compile
- 如果您只有一个参数化类型参数,那么您可以使用通配符,尽管类型参数也可以使用。
- 类型参数支持多个边界,通配符不支持。
通配符支持上限和下限,类型参数只支持上限。所以,如果你想定义一个采用 a
List
类型Integer
或其超类的方法,你可以这样做:public void print(List<? super Integer> list) // OK
但你不能使用类型参数:
public <T super Integer> void print(List<T> list) // Won't compile
References:
参考:
回答by kan
Wildcard method is also generic - you could call it with some range of types.
通配符方法也是通用的 - 您可以使用某些类型范围来调用它。
The <T>
syntax defines a type variable name. If a type variable has any use (e.g. in method implementation or as a constraint for other type), then it makes sense to name it, otherwise you could use ?
, as anonymous variable. So, looks like just a short-cut.
的<T>
语法定义了一个类型变量名。如果一个类型变量有任何用途(例如在方法实现中或作为其他类型的约束),那么命名它是有意义的,否则你可以使用?
, 作为匿名变量。所以,看起来只是一个捷径。
Moreover, the ?
syntax is not avoidable when you declare a field:
此外,?
声明字段时无法避免语法:
class NumberContainer
{
Set<? extends Number> numbers;
}
回答by le-doude
In your first question: It means that if there is a relation between the parameter's type and the method's return type then use a generic.
在您的第一个问题中:这意味着如果参数的类型和方法的返回类型之间存在关系,则使用泛型。
For example:
例如:
public <T> T giveMeMaximum(Collection<T> items);
public <T> Collection<T> applyFilter(Collection<T> items);
Here you are extracting some of the T following a certain criteria. If T is Long
your methods will return Long
and Collection<Long>
; the actual return type is dependent on the parameter type, thus it is useful, and advised, to use generic types.
在这里,您将按照特定标准提取一些 T。如果 T 是Long
你的方法将返回Long
和Collection<Long>
; 实际的返回类型取决于参数类型,因此使用泛型类型很有用,并且建议使用。
When this is not the case you can use wild card types:
如果不是这种情况,您可以使用通配符类型:
public int count(Collection<?> items);
public boolean containsDuplicate(Collection<?> items);
In this two example whatever the type of the items in the collections the return types will be int
and boolean
.
在这两个示例中,无论集合中的项是什么类型,返回类型都是int
and boolean
。
In your examples:
在您的示例中:
interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
those two functions will return a boolean whatever is the types of the items in the collections. In the second case it is limited to instances of a subclass of E.
无论集合中项目的类型是什么,这两个函数都将返回一个布尔值。在第二种情况下,它仅限于 E 的子类的实例。
Second question:
第二个问题:
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src) {
...
}
This first code allow you to pass an heterogeneous List<? extends T> src
as a parameter. This list can contain multiple elements of different classes as long as they all extends the base class T.
第一个代码允许您将异构List<? extends T> src
作为参数传递。这个列表可以包含不同类的多个元素,只要它们都扩展了基类 T。
if you had:
如果你有:
interface Fruit{}
and
和
class Apple implements Fruit{}
class Pear implements Fruit{}
class Tomato implements Fruit{}
you could do
你可以
List<? extends Fruit> basket = new ArrayList<? extends Fruit>();
basket.add(new Apple());
basket.add(new Pear());
basket.add(new Tomato());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket);// works
On the other hand
另一方面
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src) {
...
}
constrain List<S> src
to be of one particular class S that is a subclass of T. The list can only contain elements of one class (in this instance S) and no other class, even if they implement T too. You wouldn't be able to use my previous example but you could do:
约束List<S> src
为一个特定类 S,它是 T 的子类。该列表只能包含一个类(在此实例中为 S)的元素,而不能包含其他类的元素,即使它们也实现了 T。您将无法使用我之前的示例,但您可以这样做:
List<Apple> basket = new ArrayList<Apple>();
basket.add(new Apple());
basket.add(new Apple());
basket.add(new Apple());
List<Fruit> fridge = new ArrayList<Fruit>();
Collections.copy(fridge, basket); /* works since the basket is defined as a List of apples and not a list of some fruits. */
回答by Buhake Sindi
I will try and answer your question, one by one.
我会试着一一回答你的问题。
Don't we think wild card like
(Collection<? extends E> c);
is also supporting kind of polymorphism?
难道我们不认为通配符
(Collection<? extends E> c);
也支持某种多态性吗?
No. The reason is that the bounded wildcardhas no defined parameter type. It is an unknown. All it "knows" is that the "containment" is of a type E
(whatever defined). So, it cannot verify and justify whether the value provided matches the bounded type.
否。原因是有界通配符没有定义的参数类型。这是一个未知数。它“知道”的是“收容”是一种类型E
(无论定义什么)。因此,它无法验证和证明提供的值是否与有界类型匹配。
So, it's no sensible to have polymorphic behaviours on wildcards.
因此,通配符上的多态行为是不明智的。
The document discourages the second declaration and promotes usage of first syntax? What's the difference between the first and second declaration? Both seems to be doing the same thing?
该文件不鼓励第二个声明并促进使用第一个语法?第一个和第二个声明有什么区别?两者似乎都在做同样的事情?
The first option is better in this case as T
is always bounded, and source
will definitely have values (of unknowns) that subclasses T
.
在这种情况下,第一个选项更好,因为T
它总是有界的,并且source
肯定会有子类T
.
So, suppose that you want to copy all list of numbers, the first option will be
因此,假设您要复制所有数字列表,第一个选项将是
Collections.copy(List<Number> dest, List<? extends Number> src);
src
, essentially, can accept List<Double>
, List<Float>
, etc. as there is an upper bound to the parameterized type found in dest
.
src
, 本质上可以接受List<Double>
,List<Float>
等,因为在 中找到的参数化类型有一个上限dest
。
The 2nd option will force you to bind S
for every type you want to copy, like so
第二个选项将强制您绑定S
要复制的每种类型,如下所示
//For double
Collections.copy(List<Number> dest, List<Double> src); //Double extends Number.
//For int
Collections.copy(List<Number> dest, List<Integer> src); //Integer extends Number.
As S
is a parameterized type that needs binding.
AsS
是需要绑定的参数化类型。
I hope this helps.
我希望这有帮助。
回答by chammu
Consider following example from The Java Programming by James Gosling 4th edition below where we want to merge 2 SinglyLinkQueue:
考虑下面 James Gosling 的 The Java Programming 第 4 版中的以下示例,我们希望合并 2 个 SinglyLinkQueue:
public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
// merge s element into d
}
public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
// merge s element into d
}
Both of the above methods have the same functionality. So which is preferable? Answer is 2nd one. In the author's own words :
上述两种方法具有相同的功能。那么哪个更可取?答案是第二个。用作者自己的话来说:
"The general rule is to use wildcards when you can because code with wildcards is generally more readable than code with multiple type parameters. When deciding if you need a type variable, ask yourself if that type variable is used to relate two or more parameters, or to relate a parameter type with the return type. If the answer is no, then a wildcard should suffice."
“一般规则是尽可能使用通配符,因为带有通配符的代码通常比带有多个类型参数的代码更具可读性。在决定是否需要一个类型变量时,问问自己该类型变量是否用于关联两个或多个参数,或者将参数类型与返回类型相关联。如果答案是否定的,那么通配符就足够了。”
Note: In book only second method is given and type parameter name is S instead of 'T'. First method is not there in the book.
注意:书中只给出了第二种方法,类型参数名称是 S 而不是 'T'。书中没有第一种方法。
回答by Vivek Kumar
One other difference which is not listed here.
此处未列出的另一个区别。
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // correct
}
}
But the following will result in compile time error.
但以下会导致编译时错误。
static <T> void fromArrayToCollection(T[] a, Collection<?> c) {
for (T o : a) {
c.add(o); // compile time error
}
}
回答by Martin Maletinsky
As far as I understand, there is only one use case when wildcard is strictly needed (i.e. can express something that you can not express using explicit type parameters). This is when you need to specify a lower bound.
据我了解,只有一种用例是严格需要通配符的(即可以表达使用显式类型参数无法表达的内容)。这是您需要指定下限的时候。
Apart from that however wildcards serve to write more concise code, as described by the following statements in the document you mention:
除此之外,通配符还可以编写更简洁的代码,如您提到的文档中的以下语句所述:
Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type. If there isn't such a dependency, a generic method should not be used.
[...]
Using wildcards is clearer and more concise than declaring explicit type parameters, and should therefore be preferred whenever possible.
[...]
Wildcards also have the advantage that they can be used outside of method signatures, as the types of fields, local variables and arrays.
泛型方法允许使用类型参数来表达一个或多个参数的类型之间的依赖关系和/或其返回类型。如果没有这样的依赖关系,则不应使用泛型方法。
[...]
使用通配符比声明显式类型参数更清晰、更简洁,因此应尽可能首选。
[...]
通配符的另一个优点是它们可以在方法签名之外使用,如字段、局部变量和数组的类型。
回答by Arasn
Mainly -> Wildcards enforce generics at the parameter/argument level of a Non-Generic method. Note. It can also be performed in genericMethod by default, but here instead of ? we can use T itself.
主要 -> 通配符在非泛型方法的参数/参数级别强制泛型。笔记。默认情况下,它也可以在 genericMethod 中执行,但在这里而不是 ? 我们可以使用 T 本身。
package generics;
包泛型;
public class DemoWildCard {
public static void main(String[] args) {
DemoWildCard obj = new DemoWildCard();
obj.display(new Person<Integer>());
obj.display(new Person<String>());
}
void display(Person<?> person) {
//allows person of Integer,String or anything
//This cannnot be done if we use T, because in that case we have to make this method itself generic
System.out.println(person);
}
}
class Person<T>{
}
SO wildcard has its specific usecases like this.
SO 通配符具有这样的特定用例。