关于java:如何将元素添加到通配符通用集合中?

How can elements be added to a wildcard generic collection?

为什么我会用这个Java代码获得编译器错误?

1
2
3
4
5
6
1  public List<? extends Foo> getFoos()
2  {
3    List<? extends Foo> foos = new ArrayList<? extends Foo>();
4    foos.add(new SubFoo());
5    return foos;
6  }

其中"subfoo"是实现foo的具体类,foo是一个接口。

使用此代码时出错:

  • 第3行:"不能实例化arraylist<?扩展Fo>
  • 第4行:"方法添加(捕获1-of?扩展foo)在类型列表中扩展foo>不适用于参数(subfoo)"

更新:感谢Jeff C,我可以将第3行改为"new arraylist();"。但我仍然对第4行有问题。


改为使用:

1
2
3
4
5
6
1  public List<? extends Foo> getFoos()
2  {
3    List<Foo> foos = new ArrayList<Foo>(); /* Or List<SubFoo> */
4    foos.add(new SubFoo());
5    return foos;
6  }

一旦将foo声明为List,编译器就不知道添加子oo是安全的。如果一个ArrayList被分配给foos呢?这将是一个有效的分配,但添加子对象会污染集合。


我只是想通过总结用类型或通配符实例化的列表参数的属性来添加到这个旧线程中……

当一个方法有一个作为列表的参数/结果时,类型实例化或通配符的使用决定了

  • 可以作为参数传递给方法的列表类型
  • 可从方法结果填充的列表类型
  • 可写入方法中列表的元素类型
  • 从方法中的列表中读取元素时可以填充的类型
  • 参数/返回类型:List< Foo>

  • 可以作为参数传递给方法的列表类型:
    • List< Foo>
  • 可从方法结果填充的列表类型:
    • List< Foo>
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
  • 可在方法中写入列表的元素类型:
    • Foo和子类型
  • 从方法中的列表中读取元素时可以填充的类型:
    • Foo和supertypes(至多Object)
  • 参数/返回类型:List< ? extends Foo>

  • 可以作为参数传递给方法的列表类型:
    • List< Foo>
    • List< Subfoo>
    • List< SubSubFoo>
    • List< ? extends Foo>
    • List< ? extends SubFoo>
    • List< ? extends SubSubFoo>
  • 可从方法结果填充的列表类型:
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
    • List< ? extends SuperSuperFoo>
  • 可在方法中写入列表的元素类型:
    • 没有!无法添加。
  • 从方法中的列表中读取元素时可以填充的类型:
    • Foo和supertypes(至多Object)
  • 参数/返回类型:List

  • 可以作为参数传递给方法的列表类型:
    • List< Foo>
    • List< Superfoo>
    • List< SuperSuperFoo>
    • List< ? super Foo>
    • List< ? super SuperFoo>
    • List< ? super SuperSuperFoo>
  • 可从方法结果填充的列表类型:
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? super SubSubFoo>
  • 可在方法中写入列表的元素类型:
    • Foo和supertypes
  • 从方法中的列表中读取元素时可以填充的类型:
    • Foo和supertypes(至多Object)
  • 解释/评论

    • 外部调用方的需求驱动方法声明的设计,即公共API(通常是主要考虑因素)
    • 内部方法逻辑的需求驱动对内部声明和构造的实际数据类型的任何附加决策(通常是次要考虑因素)
    • 如果调用者代码总是专注于操纵foo类,则使用List,因为它最大限度地提高了读写的灵活性。
    • 如果调用者可能有许多不同类型,集中于操作不同的类(不总是foo),并且foo类型层次结构中只有一个最上面的类,并且如果方法是内部写入列表,并且调用者列表操作正在读取,则使用List。在这里,在返回List< ? extends UpperMostFoo>之前,该方法可以在内部使用List< UpperMostFoo>并向其添加元素。
    • 如果有许多不同类型的调用者,专注于操作不同的类(不总是foo),并且如果需要对列表进行读写,并且foo类型层次结构中只有一个最低的类,那么使用List< ? super LowerMostFoo>是有意义的。


    尝试:

    1
    2
    3
    4
    5
    public List<Foo> getFoos() {
        List<Foo> foos = new ArrayList<Foo>();
        foos.add(new SubFoo());
        return foos;
    }

    泛型ArrayList构造函数需要有一个要参数化的特定类型,不能使用"?"通配符。将实例化更改为"new arraylist()"将解决第一个编译错误。

    "foos"变量的声明可以有通配符,但由于您知道确切的类型,因此在那里引用相同的类型信息更为合理。您现在所说的foo包含foo的某些特定子类型,但我们不知道是哪个。可能不允许添加子oo,因为子oo不是"foo的所有子类型"。将声明更改为'listfoos='可解决第二个编译错误。

    最后,我会将返回类型更改为"list",因为此方法的客户端无法像当前定义的那样对返回值执行太多操作。在返回类型中很少使用通配符。如果需要,可以使用参数化的方法签名,但更喜欢有界类型只出现在方法参数中,因为这样就留给了调用方,调用方可以传递特定的类型并相应地操作它们。


    以下各项工作正常:

    1
    2
    3
    4
    5
    public List<? extends Foo> getFoos() {
        List<Foo> foos = new ArrayList<Foo>();
        foos.add(new SubFoo());
        return foos;
    }

    要了解泛型的工作原理,请查看以下示例:

    1
    2
    3
    4
    5
    6
        List<SubFoo> sfoo = new ArrayList<SubFoo>();
        List<Foo> foo;
        List<? extends Foo> tmp;

        tmp = sfoo;
        foo = (List<Foo>) tmp;

    问题是,它不是为局部/成员变量设计的,而是为函数签名设计的,这就是为什么它是如此向后的。