16

在我的最后一个问题List<Object>中(感谢所有回答我的人),我了解了和之间的区别List<?>

但是我仍然看不到通配符的用处。

我有两个ArrayList

ArrayList<Integer> li = new ArrayList<Integer>(Arrays.asList(1,2,3));
ArrayList<String> ls = new ArrayList<String>(Arrays.asList("one","two","three"));

现在,看看下面的两个代码块:

static void printList(ArrayList<?> list) 
{
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

static <T> void printList(ArrayList<T> list) 
{
    for (T elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

当我打电话时:

printList(li);
printList(ls);

两种方法都返回输出:

1 2 3
one two three

然而,第二个解决方案,在 for 循环中,Object我使用参数化类型而不是 s(我认为更优雅)。

所以,主要问题仍然存在:为什么我们需要通配符

4

8 回答 8

11

如果问题是“通配符的用处”:

当只需要关于类型参数的部分知识时,通配符很有用。“部分知识”由上下界(?super T 或? extends T)实现;如果您只使用未绑定的通配符 (?),则意味着根本没有任何知识,而且您看不到通配符真正有用的地方。

通配符可以与类型参数组合使用,以创建方法参数类型、返回类型和异常类型之间的关系。所以有用的通配符的一个例子是

 class ListManager<T> {
    public void add(T item, List<? super T> list) {
        [... some useful operation ...]
         list.add(item);
    }
}

public class Main {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<Object>();
        Integer item = 10;
        ListManager<Integer> manager = new ListManager<Integer>();
        manager.add(item, list);
    }
}

方法“ListManager.add()”在“列表”类型和“项目”类型之间创建关系。对“list”的操作始终是类型安全的,但是您可以使用方法“add”来处理不同参数类型的列表:我们对参数“list”使用了最小约束。

另见 jls7 文档

于 2012-09-10T11:36:03.753 回答
7

Java 泛型教程中它说:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

因为我们不知道 c 的元素类型代表什么,所以我们不能向它添加对象。add() 方法接受类型 E 的参数,即集合的元素类型。当实际类型参数为 ? 时,它代表某种未知类型。我们传递给 add 的任何参数都必须是这种未知类型的子类型。因为我们不知道那是什么类型,所以我们不能传入任何东西。唯一的例外是 null,它是每个类型的成员。

另一方面,给定一个 List,我们可以调用 get() 并使用结果。结果类型是未知类型,但我们始终知道它是一个对象。因此,将 get() 的结果分配给 Object 类型的变量或将其作为参数传递给期望为 Object 类型的变量是安全的。

因此,拥有通配符类型可确保我们无法add()进入列表,但null. 如果您只是调用get()列表,您知道它至少是一个对象。

于 2012-09-10T09:34:10.907 回答
5

根据Item 28-USE BOUNDED WILDCARDS TO INCREASE API FLEXIBILITY of Effective java

如果类型参数在方法声明中只出现一次,请将其替换为通配符。

于 2012-09-10T11:41:13.800 回答
4

假设你有这个类层次结构:

Fruit
Apple extends Fruit
Orange extends Fruit

...以及每种水果的一些清单:

List<Apple> apples ;List<Orange> oranges ;

现在你想要一个List可以引用这些列表中的任何一个的。

所以你声明List<? extends Fruit> fruits ;

您不能在此处使用类型变量。

于 2012-09-10T09:43:35.890 回答
2

使用通配符,您的代码是类型安全的。在

List myList;

你可以添加任何你想要的对象。但在:

List<?> myList;

你只能添加一个null

只有在需要类 (List.class) 或需要检查类型(List 的实例)时才应使用不带类型参数的 List。

于 2012-09-10T09:30:13.887 回答
2

You're right. Technically, every time you see a wildcard at the first level in the type of a parameter, you could create a new type variable for it, and use that type variable in the place of that wildcard, and it would work the same.

However, I would argue that is much less elegant than using a wildcard. A type variable is useful for express establishing constraints between different expressions, like when there are two parameters with the same type variable, or between a parameter and return type. A type variable used in just one place in the parameter is kind of a waste -- that was exactly what wildcards are meant for -- an "anonymous" type variable that is not needed anywhere else. When you just need to express "some type", without needing to constrain it with something else, why clutter your method signature with bonus type variables?

There are also other uses of wildcards that cannot be replaced type variables. For example, consider a method public List<?> foo(). It returns some kind of list, but you don't know a list of what (and it is unsafe to add anything to it). There is no way to express that using type variables without wildcards.

Also, consider wildcards in nested type parameters: you could have a variable of type List<List<?>>, i.e. a heterogenous list whose elements can be lists of different things. This also cannot be expressed without using wildcards.

于 2012-09-10T22:26:50.457 回答
1

因为

您可以在以下情况下修改列表<T> list.add((T) new Object());

但是您不能修改列表以?确保列表不会被修改。如果添加 null 以外的任何内容,编译器会生成错误。

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

因为我们不知道 c 的元素类型代表什么,所以我们不能向它添加对象。add() 方法接受类型 E 的参数,即集合的元素类型。当实际类型参数为 ? 时,它代表某种未知类型。我们传递给 add 的任何参数都必须是这种未知类型的子类型。因为我们不知道那是什么类型,所以我们不能传入任何东西。唯一的例外是 null,它是每个类型的成员。

另一方面,给定 a List<?>,我们可以调用get()并使用结果。结果类型是未知类型,但我们始终知道它是一个对象。因此,将 get() 的结果分配给 Object 类型的变量或将其作为参数传递给 Object 类型的变量是安全的

有关通配符的更多信息

于 2012-09-10T09:31:25.950 回答
1

我发现通配符的最佳用途之一是泛型类型的复杂嵌套。

一个带有 Pair<T,E> 列表的简单示例,通配符使代码更短且更具可读性,因为每个人都可以看到我只对布尔值感兴趣。

  ArrayList<Pair<Boolean, OneOrOtherLongClassName>> pairList = ...;
  for(Pair<Boolean,?> p:pairList)
  {
      if(p.first)count++;
  }
于 2012-09-13T13:26:39.677 回答