关于java:用于捕获通用方法的通配符类型的私有帮助器方法

Private helper method to capture wildcard type for generic methods

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

1
2
3
4
5
6
7
8
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);
}

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

1
2
3
4
5
6
7
  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));
  }

我理解这里的helper方法能够捕获通配符类型,但是为什么不在前面的代码中呢?

编辑:在第三种情况下,如果我将Put方法中的类型参数更改为List<.?当我试图从另一个获取列表的方法调用put()方法时,Eclipse不会编译它。它说,"方法放(列表<."类型abc中的super e>,int,e)不适用于参数(list<.capture 6-of extends e>",int,e)"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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>调用这两个方法?


这是因为"取得并投入"原则,也就是生产商扩展的缩写pecs,消费者超级。

这在这个问题中得到了解释:什么是PECS(生产者扩展消费者超级)?

但基本上:

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

这里不能使用,因为List正被用作消费者(它正在接受元素),所以应该使用super而不是extends

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

编辑

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

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

这编译了:

1
2
3
4
5
6
7
8
9
10
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都必须是相同的。


仿制药有时有点困难。试着一步一步地分析它。

想象一下,在第一个示例(不编译)中,您使用类型变量E替换为Number来调用put方法。例如,当您使用List约束输入参数List时,它可能是List。参数valueE型(记住:ENumber型),因此可以是Double型。这是不允许的,因为你会试图在List中添加一个Double

在第二个示例中,您只对输入参数List有一个约束。其他参数不依赖于E。设置和加入列表总是有效的,因为所讨论的元素必须是相同的类型。

解决方案:

始终提醒缩写词pecs。参见通配符(Java)。这意味着您应该声明put方法如下(在第一个示例中):

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


List只接受e类对象,因为List可以接受e的任何子类。

当你说一个方法只能接受List时,通过List的方法会混淆编译器,因为它不知道你的列表实际包含哪种类型的对象。

请注意,在爪哇,这是非法的:

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

List list = new ArrayList();

在第二个示例中,您的列表需要e的任何子类,而您传递的是e对象列表,因此它是允许的。如List list = new ArrayList();