How can I use a generic class with wildcard declaration?

Roe*_*oel 7 java generics wildcard

I have the following member in my class:

List<? extends SomeObject> list;
Run Code Online (Sandbox Code Playgroud)

When I try to do:

list.add(list.get(0));
Run Code Online (Sandbox Code Playgroud)

I get:

Test.java:7: error: no suitable method found for add(CAP#1)
        list.add(list.get(0));
            ^
    method Collection.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
    method List.add(CAP#2) is not applicable
      (argument mismatch; Object cannot be converted to CAP#2)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object from capture of ? extends Object
    CAP#2 extends Object from capture of ? extends Object
Run Code Online (Sandbox Code Playgroud)

My question is twofold:

  1. Why doesn't it compile? Why can't I pass get()'s result to add()?

  2. And how can I achieve this in another way without resorting to casting?


I understand that in a method with <T extends SomeObject> I can't just say:

T someObject = list.get(0);
list.add(someObject);
Run Code Online (Sandbox Code Playgroud)

since my T could be another extension than the ? extension.

I also understand I can't say:

List<? extends SomeObject> list1;
List<? extends SomeObject> list2;
list1.add(list2.get(0));
Run Code Online (Sandbox Code Playgroud)

But since the add and the get should work with the same generic type in list.add(list.get(0)) I don't understand why the compiler doesn't accept it.


What I really need is

[something of type T where T is whatever was used to instantiate list] someObject = list.get(0);
list.add(someObject);
Run Code Online (Sandbox Code Playgroud)

so that I can later

list.add(someObject);
Run Code Online (Sandbox Code Playgroud)

I don't think I should have to template my whole class to achieve this, should I?

class MyClass<T extends SomeObject> {
List<T> list;
Run Code Online (Sandbox Code Playgroud)

and then later a method with

T someObject = list.get(0);
Run Code Online (Sandbox Code Playgroud)

of course works, but screws other parts of my code.

So the first question is why doesn't this work, second question is what's the best workaround?

And*_*ner 3

我的问题是双重的,为什么我不能这样做:

list.add(list.get(0));
Run Code Online (Sandbox Code Playgroud)

因为编译器不够聪明,无法知道您正在将某些内容从list后面添加到list. 一旦被求值,编译器就不认为list.get(0)有任何关系:它只是类型的“某些表达式” 。list? extends SomeObject

要解决此问题,请添加一个具有自己的类型变量的方法:

private <T> void addFirst(List<T> list) {
  list.add(list.get(0));
}
Run Code Online (Sandbox Code Playgroud)

list.add(list.get(0));并用以下调用替换原来的:

addFirst(list);
Run Code Online (Sandbox Code Playgroud)

这仅在方法上定义了类型变量,不需要在类外部可见,因此不需要类级别的类型变量。


也许值得指出的是,这与Collections.swap方法类似:使用set而不是使用add,但是,从泛型的角度来看,它是同一件事:

@SuppressWarnings({"rawtypes", "unchecked"})
public static void swap(List<?> list, int i, int j) {
    // instead of using a raw type here, it's possible to capture
    // the wildcard but it will require a call to a supplementary
    // private method
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}
Run Code Online (Sandbox Code Playgroud)

这采用了一种技术上类型安全的方法,并且确实避免了强制转换;但它有点恶心,因为它使用原始类型。

我想这只是出于向后兼容性的原因。如果有机会再次编写它,您可以像addFirst上面的方法一样定义一个类型变量。