3

以下代码无法在 Eclipse 中编译。它说“Abc 类型的 putHelper(List,int,E) 方法不适用于参数 (List <.capture#8-of extends E>",int,E)"

private <E> void putHelper(List<E> list, int i, E value) {
    list.set(i, value);
}

public <E> void put(List<? extends E> list, int toPos, E value) {
    // list.set(toPos,value);
    putHelper(list, toPos, value);
}

我不明白为什么会这样?因为下面的代码工作正常。

  public <E> void put(List<? extends E> list,int fromPos, int toPos) {
  putHelper(list,fromPos,toPos);
  }

  private <E> void putHelper(List<E> list,int i, int j) {
  list.set(j,list.get(i));
  }

而且我知道这里的辅助方法能够捕获通配符类型,但是为什么不能在早期的代码中呢?

编辑:在第三种情况下,如果我将 put 方法中的类型参数更改为 List<.? super E> 并且当我尝试从另一个采用列表的方法调用 put() 方法时,Eclipse 不会编译它。它说,“Abc 类型的 put(List<.? super E>,int,E) 方法不适用于参数 (List <.capture#6-of extends E>",int,E)"

public static <E> void insertAndProcess(List<? extends E> list) {

// Iterate through the list for some range of values i to j

    E value = list.get(i);

//Process the element and put it back at some index

    put(list, i+1, value);

//Repeat the same for few more elements
}

private static <E> void putHelper(List<E> list, int i, E value) {
    list.set(i, value);
}

public static <E> void put(List<? super E> list, int toPos, E value) {
    putHelper(list, toPos, value);
}

在这里,insertAndProcess() 如何调用 put() 方法并在其实现中使用它,而用户仍然可以使用 ArrayList<.Integer> 调用这两个方法?

4

3 回答 3

2

这是因为Get 和 Put 原则也称为首字母缩写词PECS,它代表 Producer Extends、Consumer Super。

这个 SO 问题对此进行了解释:什么是 PECS(生产者扩展消费者超级)?

但基本上:

public <E> void put(List<? extends E> list, int toPos, E value) {
   // list.set(toPos,value);
   putHelper(list, toPos, value);
}

<? extends E>不能在这里使用,因为List它被用作消费者(它正在获取元素),所以它应该使用super而不是extends.

public <E> void put(List<? super E> list, int toPos, E value) {
   // list.set(toPos,value);
   putHelper(list, toPos, value);
}

编辑

在您的第二种情况下,List充当生产者,因为它通过调用来生产元素,get()因此您可以使用extends.

但是,在您的第三个示例中,您将获得并放入同一个列表,因此我认为您根本不能使用通配符。

这编译:

public static <E> void insertAndProcess(List<E> list) {

    // Iterate through the list for some range of values i to j
    E value = list.get(i);

    // Process the element and put it back at some index
    putHelper(list, i+1, value);

    // Repeat the same for few more elements
}

请注意,由于我们不需要使用任何通配符,因为我们从同一个列表中获取和设置,所以E无论它是什么类型都必须是相同的。

于 2014-06-24T06:14:13.853 回答
1

泛型有时有点困难。试着一步一步分析。

想象一下-在您的第一个示例(未编译)中-您调用该put方法,并将类型变量E替换为Number. 当你用它来约束输入参数list时,List<? extends E>它可能是一个List<Integer>例子。参数value是类型E(记住:ENumber),所以它可以是一个Double例子。这是不允许的,因为您会尝试将 a 添加Double到 aList<Integer>中。

在第二个示例中,您只有对输入参数的约束list。没有其他参数依赖于E。设置和进入列表将始终有效,因为所讨论的元素必须属于同一类型。

一个办法:

Always remind the acronym PECS. See Wildcard (Java). That means you should declare the put method as following (in your first example):

public <E> void put(List<? super E> list, int toPos, E value) {
    putHelper(list, toPos, value);
}
于 2014-06-24T06:17:15.100 回答
0

List<E>将只接受 E 类对象,其中 asList<? extends E>可以接受 E 的任何子类。

当您说一个方法只能接受List<E>传递List<? extends E>时,编译器会感到困惑,因为它不知道您的列表将实际包含哪种类型的 Object。

请注意,在 java 中,这是非法的:

List<E> list = new ArrayList<? extends E>();

例如List<Number> list = new ArrayList<Integer>();

在第二个示例中,您的列表需要 E 的任何子类,并且您传递的是 E 对象列表,因此它是允许的。例如List<? extends Number> list = new ArrayList<Integer>();

于 2014-06-24T06:09:49.617 回答