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

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

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

其中’SubFoo’是实现Foo的具体类,而Foo是一个接口。

我用这段代码得到的错误:

  • 在第3行:“无法实例化ArrayList ”
  • 在第4行:“方法添加(捕获#1-of?extends Foo)在类型List 中不适用于参数(SubFoo)”

更新:感谢Jeff C,我可以将第3行更改为“new ArrayList ();”。 但我仍然遇到第4行的问题。

改为使用它:

 public List getFoos() { List foos = new ArrayList(); /* Or List */ foos.add(new SubFoo()); return foos; } 

一旦你将foos声明为List List ,编译器不知道添加SubFoo是安全的。 如果ArrayList分配给foos怎么foos ? 这将是一个有效的赋值,但添加SubFoo会污染集合。

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

当方法的参数/结果是List时,使用类型实例化或通配符确定

  1. 可以作为参数传递给方法的List类型
  2. 可以从方法结果填充的List类型
  3. 可以写入方法列表的元素类型
  4. 从方法中的列表中读取元素时可以填充的类型

参数/返回类型: List< Foo>

  1. 可以作为参数传递给方法的List类型:
    • List< Foo>
  2. 可以从方法结果填充的List类型:
    • List< Foo>
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
  3. 可以写入方法列表的元素类型:
    • Foo和子类型
  4. 在方法中从列表中读取元素时可以填充的类型:
    • Foo和超类型(最多Object

参数/返回类型: List< ? extends Foo> List< ? extends Foo>

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

参数/返回类型: List List

  1. 可以作为参数传递给方法的List类型:
    • List< Foo>
    • List< Superfoo>
    • List< SuperSuperFoo>
    • List< ? super Foo>
    • List< ? super SuperFoo>
    • List< ? super SuperSuperFoo>
  2. 可以从方法结果填充的List类型:
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? super SubSubFoo>
  3. 可以写入方法列表的元素类型:
    • Foo和超类型
  4. 在方法中从列表中读取元素时可以填充的类型:
    • Foo和超类型(最多Object

口译/评论

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

尝试:

 public List getFoos() { List foos = new ArrayList(); foos.add(new SubFoo()); return foos; } 

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

‘foos’变量的声明可以有通配符,但由于你知道精确的类型,因此在那里引用相同的类型信息更有意义。 你现在所说的foos拥有Foo的一些特定子类型,但我们不知道哪个。 可能不允许添加SubFoo,因为SubFoo不是“Foo的所有子类型”。 将声明更改为’List foos =’可以解决第二个编译错误。

最后,我将返回类型更改为“List ”,因为此方法的客户端将无法对当前定义的返回值执行太多操作。 您应该很少在返回类型中使用通配符。 如果需要,请使用参数化方法签名,但更喜欢有界类型仅出现在方法参数中,因为这会将其留给可以传递特定类型并相应地操作的调用者。

以下工作正常:

 public List getFoos() { List foos = new ArrayList(); foos.add(new SubFoo()); return foos; } 

要了解generics如何工作,请查看此示例:

  List sfoo = new ArrayList(); List foo; List tmp; tmp = sfoo; foo = (List) tmp; 

问题是,这不是为本地/成员变量而设计的,但对于function签名而言,这就是为什么它如此ass倒退。