通过 List<?> 将 List<subclass> 向上转型为 List<superclass>

14

我有一个类A和一个继承自A的类B。

在另一个类C中,我有一个字段。

private List<B> listB;

现在,由于某种不寻常的原因,我必须在C中实现这个方法

public List<A> getList();

我尝试通过将listB字段强制向上转型为List<A>,使用List<?>进行转型:

public List<A> getList(){
    return (List<A>)(List<?>)listB;
}

客户端应该怎么做

List<A> list = getList();
for(A a:list){
    //do something with a
}

我做了一些测试,似乎它能正常工作,但老实说我不确定所有可能的影响。

这个解决方案正确吗?并且它是最好的解决方案吗?

感谢您的回答。


Stream<A> getAStream() { return listB.stream().map(A.class::cast); } 用于只读列表访问。 - Joop Eggen
2个回答

16

不,这通常不是类型安全的。客户端不应该能够执行此操作。

List<A> list = getList();

否则他们可以编写

list.add(new C()); // Where C extends A

如果原来的代码将列表视为List<B>类型,当它尝试使用该列表时,假定每个元素都与B兼容,就会出现问题。

你可以包装原始列表使其只读,或者让getList返回一个List<? extends A>,这意味着客户端无法向其中添加项目。

如果您使用的列表实现是不可修改的,则实际上不会引起问题-但我仍然建议在可能的情况下避免使用它。


后一种解决方案在这里似乎更好。如果一个接口指定返回 List<? extends A>,那么具体类实际上可以将其细化为 List<B>。或者,C 可以有一个类型参数 class C<T extends A>,带有 List<T> getList() - Mark Peters
顺便说一下,它的封装非常容易:return Collections.<A>unmodifiableList(listB);(我不确定你的情况是否需要显式类型)。这个库很聪明地让你轻松完成这项工作。 - Mark Peters
即使没有元素类型的问题,允许修改这样的内部集合可能不是您想要的。我认为复制可能比包装为不可修改更好,这样当真正的集合被修改时,仍然持有引用的客户端代码不会中断。 - Tom Hawtin - tackline

3
这种情况的问题在于客户端可能会不知情地将A对象插入到实际上只能包含更特定的B对象列表中:
c.getList().add(new A());

这将导致您的代码尝试从列表中获取一个对象并假设它是一个B,但实际上它不是,会导致各种问题。
如果您只想让客户端遍历列表,最好提供一个Iterable<A>
public Iterable<A> getAs() { return this.theListOfAs; }

通过这个Iterable,一个人只能检查和删除元素,但不能添加它们。

如果你想禁用删除功能,可以将ListIterable包装在你自己的实现中,在调用remove()时抛出UnsupportedOperationException


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接