Java:有界通配符还是有界类型参数?

时间:2022-11-07 16:29:09

Recently, I read this article: http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html

最近,我读了这篇文章:http://download.oracle.com/javase/tutorial/extra/generics/wildcards.html

My question is, instead of creating a method like this:

我的问题是,与其创建这样的方法:

public void drawAll(List<? extends Shape> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

I can create a method like this, and it works fine:

我可以创建这样的方法,它工作得很好:

public <T extends Shape> void drawAll(List<T> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

Which way should I use? Is wildcard useful in this case?

我应该用哪种方式?在这种情况下通配符有用吗?

5 个解决方案

#1


95  

It depends on what you need to do. You need to use the bounded type parameter if you wanted to do something like this:

这取决于你需要做什么。如果您想做以下事情,您需要使用有界类型参数:

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

Here we have a List<T> shapes and a T shape, therefore we can safely shapes.add(shape). If it was declared List<? extends Shape>, you can NOT safely add to it (because you may have a List<Square> and a Circle).

这里我们有一个列表 形状和一个T形状,因此我们可以安全地进行shape .add(shape)。如果它被声明为List ,您不能安全地添加到它(因为您可能有一个列表 和一个圆圈)。

So by giving a name to a bounded type parameter, we have the option to use it elsewhere in our generic method. This information is not always required, of course, so if you don't need to know that much about the type (e.g. your drawAll), then just wildcard is sufficient.

因此,通过给有界类型参数提供一个名称,我们可以选择在通用方法的其他地方使用它。当然,这些信息并不总是必需的,所以如果您不需要对类型(例如您的drawAll)了解那么通配符就足够了。

Even if you're not referring to the bounded type parameter again, a bounded type parameter is still required if you have multiple bounds. Here's a quote from Angelika Langer's Java Generics FAQs

即使您不再引用有界类型参数,如果您有多个边界,仍然需要有界类型参数。这是一个来自Angelika Langer的Java泛型faq的引用。

What is the difference between a wildcard bound and a type parameter bound?

通配符绑定和类型参数绑定的区别是什么?

A wildcard can have only one bound, while a type parameter can have several bounds. A wildcard can have a lower or an upper bound, while there is no such thing as a lower bound for a type parameter.

通配符只能有一个边界,而类型参数可以有多个边界。通配符可以有下界或上界,而没有类型参数的下界。

Wildcard bounds and type parameter bounds are often confused, because they are both called bounds and have in part similar syntax. […]

通配符边界和类型参数边界经常被混淆,因为它们都被称为边界,并且具有部分类似的语法。[…]

Syntax:

语法:

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

A wildcard can have only one bound, either a lower or an upper bound. A list of wildcard bounds is not permitted.

通配符只能有一个界,要么是下界,要么是上界。不允许使用通配符边界列表。

A type parameter, in constrast, can have several bounds, but there is no such thing as a lower bound for a type parameter.

在constrast中,类型参数可以有几个界限,但是没有类型参数的下界。

Quotes from Effective Java 2nd Edition, Item 28: Use bounded wildcards to increase API flexibility:

引用有效的Java第二版第28项:使用有界通配符增加API的灵活性:

For maximum flexibility, use wildcard types on input parameters that represent producers or consumers. […] PECS stands for producer-extends, consumer-super […]

为了获得最大的灵活性,在表示生产者或消费者的输入参数上使用通配符类型。PECS代表生产者延伸,消费者超级

Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code. Properly used, wildcard types are nearly invisible to users of a class. They cause methods to accept the parameters they should accept and reject those they should reject. If the user of the class has to think about wildcard types, there is probably something wrong with the class's API.

不要使用通配符类型作为返回类型。它将迫使用户在客户端代码中使用通配符类型,而不是为用户提供额外的灵活性。正确使用的通配符类型对于类的用户来说几乎是不可见的。它们使方法接受它们应该接受的参数,并拒绝接受它们应该拒绝的参数。如果类的用户必须考虑通配符类型,那么类的API可能有问题。

Applying the PECS principle, we can now go back to our addIfPretty example and make it more flexible by writing the following:

应用PECS原则,我们现在可以回到我们的addIfPretty示例,通过编写以下内容使其更加灵活:

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

Now we can addIfPretty, say, a Circle, to a List<Object>. This is obviously typesafe, and yet our original declaration was not flexible enough to allow it.

现在,我们可以将一个圆添加到List。这显然是排版,但是我们最初的声明不够灵活,不能允许这样做。

Related questions


Summary

  • Do use bounded type parameters/wildcards, they increase flexibility of your API
  • 是否使用有界类型参数/通配符,它们增加了API的灵活性
  • If the type requires several parameters, you have no choice but to use bounded type parameter
  • 如果类型需要多个参数,则只能使用有界类型参数
  • if the type requires a lowerbound, you have no choice but to use bounded wildcard
  • 如果类型需要一个下界,您就没有选择,只能使用有界通配符。
  • "Producers" have upperbounds, "consumers" have lowerbounds
  • “生产者”有上限,“消费者”有底线
  • Do not use wildcard in return types
  • 在返回类型中不使用通配符

#2


5  

In your example you don't really need to use T, since you don't use that type anywhere else.

在你的例子中,你不需要使用T,因为你不需要在其他地方使用那种类型。

But if you did something like:

但是如果你做了这样的事情:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

or like polygenlubricants said, if you want to match the type parameter in the list with another type parameter:

或者像polygenlubricants说的那样,如果您想将列表中的类型参数与另一个类型参数匹配:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

In the first example you get a bit more type safety then returning just Shape, since you can then pass the result to a function that may take a child of Shape. For example you may pass a List<Square> to my method, and then pass the resulting Square to a method that only takes Squares. If you used '?' you would have to cast the resulting Shape to Square which would not be type safe.

在第一个示例中,您可以获得更多的类型安全性,然后只返回形状,因为您可以将结果传递给可能具有形状子元素的函数。例如,您可以将列表 传递给我的方法,然后将结果平方传递给一个只取平方的方法。如果你使用的?你必须将最终的形状转换成方形,这样就不安全了。

In the second example you ensure that both lists have the same type parameter (which you can't do with '?', since each '?' is different), so that you can create a list that contains all elements from both of them.

在第二个示例中,您确保两个列表具有相同的类型参数(您不能使用'?”,因为每个”?,这样你就可以创建一个包含这两个元素的列表。

#3


1  

Consider following example from The Java Programming by James Gosling 4th edition below where we want to merge 2 SinglyLinkQueue:

请考虑下面的例子,来自Java编程的James Gosling第4版,我们想合并2个单独链接队列:

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 :

上述两种方法都具有相同的功能。所以最好是哪一个?答案是2。用作者自己的话来说:

"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.

注意:在book中只给出第二种方法,类型参数名为S而不是'T'。第一种方法不在书中。

#4


1  

As far as I understand, the wildcard allows for more concise code in situations where a type parameter is not required (e.g. because it's referenced at several places or because multiple bounds are required as detailed in other answers).

据我所知,通配符允许在不需要类型参数的情况下使用更简洁的代码(例如,因为它在几个地方被引用,或者因为在其他答案中需要多个边界)。

In the link you indicate I read (under "Generic Methods") the following statements which hint in this direction:

在你指出的链接中,我在“通用方法”下阅读了以下提示我朝这个方向的语句:

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.

通配符还有一个优点,即它们可以在方法签名之外使用,作为字段、局部变量和数组的类型。

#5


0  

The second way is a bit more verbose, but it allows you to refer T inside it:

第二种方法有点啰嗦,但它允许你在其中引用T:

for (T shape : shapes) {
    ...
}

That's the only difference, as far as I understand.

据我所知,这是唯一的不同。

#1


95  

It depends on what you need to do. You need to use the bounded type parameter if you wanted to do something like this:

这取决于你需要做什么。如果您想做以下事情,您需要使用有界类型参数:

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

Here we have a List<T> shapes and a T shape, therefore we can safely shapes.add(shape). If it was declared List<? extends Shape>, you can NOT safely add to it (because you may have a List<Square> and a Circle).

这里我们有一个列表 形状和一个T形状,因此我们可以安全地进行shape .add(shape)。如果它被声明为List ,您不能安全地添加到它(因为您可能有一个列表 和一个圆圈)。

So by giving a name to a bounded type parameter, we have the option to use it elsewhere in our generic method. This information is not always required, of course, so if you don't need to know that much about the type (e.g. your drawAll), then just wildcard is sufficient.

因此,通过给有界类型参数提供一个名称,我们可以选择在通用方法的其他地方使用它。当然,这些信息并不总是必需的,所以如果您不需要对类型(例如您的drawAll)了解那么通配符就足够了。

Even if you're not referring to the bounded type parameter again, a bounded type parameter is still required if you have multiple bounds. Here's a quote from Angelika Langer's Java Generics FAQs

即使您不再引用有界类型参数,如果您有多个边界,仍然需要有界类型参数。这是一个来自Angelika Langer的Java泛型faq的引用。

What is the difference between a wildcard bound and a type parameter bound?

通配符绑定和类型参数绑定的区别是什么?

A wildcard can have only one bound, while a type parameter can have several bounds. A wildcard can have a lower or an upper bound, while there is no such thing as a lower bound for a type parameter.

通配符只能有一个边界,而类型参数可以有多个边界。通配符可以有下界或上界,而没有类型参数的下界。

Wildcard bounds and type parameter bounds are often confused, because they are both called bounds and have in part similar syntax. […]

通配符边界和类型参数边界经常被混淆,因为它们都被称为边界,并且具有部分类似的语法。[…]

Syntax:

语法:

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

A wildcard can have only one bound, either a lower or an upper bound. A list of wildcard bounds is not permitted.

通配符只能有一个界,要么是下界,要么是上界。不允许使用通配符边界列表。

A type parameter, in constrast, can have several bounds, but there is no such thing as a lower bound for a type parameter.

在constrast中,类型参数可以有几个界限,但是没有类型参数的下界。

Quotes from Effective Java 2nd Edition, Item 28: Use bounded wildcards to increase API flexibility:

引用有效的Java第二版第28项:使用有界通配符增加API的灵活性:

For maximum flexibility, use wildcard types on input parameters that represent producers or consumers. […] PECS stands for producer-extends, consumer-super […]

为了获得最大的灵活性,在表示生产者或消费者的输入参数上使用通配符类型。PECS代表生产者延伸,消费者超级

Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code. Properly used, wildcard types are nearly invisible to users of a class. They cause methods to accept the parameters they should accept and reject those they should reject. If the user of the class has to think about wildcard types, there is probably something wrong with the class's API.

不要使用通配符类型作为返回类型。它将迫使用户在客户端代码中使用通配符类型,而不是为用户提供额外的灵活性。正确使用的通配符类型对于类的用户来说几乎是不可见的。它们使方法接受它们应该接受的参数,并拒绝接受它们应该拒绝的参数。如果类的用户必须考虑通配符类型,那么类的API可能有问题。

Applying the PECS principle, we can now go back to our addIfPretty example and make it more flexible by writing the following:

应用PECS原则,我们现在可以回到我们的addIfPretty示例,通过编写以下内容使其更加灵活:

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

Now we can addIfPretty, say, a Circle, to a List<Object>. This is obviously typesafe, and yet our original declaration was not flexible enough to allow it.

现在,我们可以将一个圆添加到List。这显然是排版,但是我们最初的声明不够灵活,不能允许这样做。

Related questions


Summary

  • Do use bounded type parameters/wildcards, they increase flexibility of your API
  • 是否使用有界类型参数/通配符,它们增加了API的灵活性
  • If the type requires several parameters, you have no choice but to use bounded type parameter
  • 如果类型需要多个参数,则只能使用有界类型参数
  • if the type requires a lowerbound, you have no choice but to use bounded wildcard
  • 如果类型需要一个下界,您就没有选择,只能使用有界通配符。
  • "Producers" have upperbounds, "consumers" have lowerbounds
  • “生产者”有上限,“消费者”有底线
  • Do not use wildcard in return types
  • 在返回类型中不使用通配符

#2


5  

In your example you don't really need to use T, since you don't use that type anywhere else.

在你的例子中,你不需要使用T,因为你不需要在其他地方使用那种类型。

But if you did something like:

但是如果你做了这样的事情:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

or like polygenlubricants said, if you want to match the type parameter in the list with another type parameter:

或者像polygenlubricants说的那样,如果您想将列表中的类型参数与另一个类型参数匹配:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

In the first example you get a bit more type safety then returning just Shape, since you can then pass the result to a function that may take a child of Shape. For example you may pass a List<Square> to my method, and then pass the resulting Square to a method that only takes Squares. If you used '?' you would have to cast the resulting Shape to Square which would not be type safe.

在第一个示例中,您可以获得更多的类型安全性,然后只返回形状,因为您可以将结果传递给可能具有形状子元素的函数。例如,您可以将列表 传递给我的方法,然后将结果平方传递给一个只取平方的方法。如果你使用的?你必须将最终的形状转换成方形,这样就不安全了。

In the second example you ensure that both lists have the same type parameter (which you can't do with '?', since each '?' is different), so that you can create a list that contains all elements from both of them.

在第二个示例中,您确保两个列表具有相同的类型参数(您不能使用'?”,因为每个”?,这样你就可以创建一个包含这两个元素的列表。

#3


1  

Consider following example from The Java Programming by James Gosling 4th edition below where we want to merge 2 SinglyLinkQueue:

请考虑下面的例子,来自Java编程的James Gosling第4版,我们想合并2个单独链接队列:

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 :

上述两种方法都具有相同的功能。所以最好是哪一个?答案是2。用作者自己的话来说:

"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.

注意:在book中只给出第二种方法,类型参数名为S而不是'T'。第一种方法不在书中。

#4


1  

As far as I understand, the wildcard allows for more concise code in situations where a type parameter is not required (e.g. because it's referenced at several places or because multiple bounds are required as detailed in other answers).

据我所知,通配符允许在不需要类型参数的情况下使用更简洁的代码(例如,因为它在几个地方被引用,或者因为在其他答案中需要多个边界)。

In the link you indicate I read (under "Generic Methods") the following statements which hint in this direction:

在你指出的链接中,我在“通用方法”下阅读了以下提示我朝这个方向的语句:

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.

通配符还有一个优点,即它们可以在方法签名之外使用,作为字段、局部变量和数组的类型。

#5


0  

The second way is a bit more verbose, but it allows you to refer T inside it:

第二种方法有点啰嗦,但它允许你在其中引用T:

for (T shape : shapes) {
    ...
}

That's the only difference, as far as I understand.

据我所知,这是唯一的不同。