我有List<SubClass>
我希望将其视为List<BaseClass>
。看起来这不应该是一个问题,因为将SubClass
转换为BaseClass
非常容易,但是我的编译器抱怨演员阵容是不可能的。
那么,获取与List<BaseClass>
相同的对象的引用的最佳方法是什么?
现在我只是制作一个新列表并复制旧列表:
List<BaseClass> convertedList = new ArrayList<BaseClass>(listOfSubClass)
但据我了解,必须创建一个全新的列表。如果可能的话,我想参考原始列表!
答案 0 :(得分:155)
此类赋值的语法使用通配符:
List<SubClass> subs = ...;
List<? extends BaseClass> bases = subs;
重要的是要认识到List<SubClass>
不可与List<BaseClass>
互换。保留对List<SubClass>
的引用的代码会希望列表中的每个项目都是SubClass
。如果代码的另一部分将列表称为List<BaseClass>
,则在插入BaseClass
或AnotherSubClass
时,编译器不会抱怨。但这会导致第一段代码ClassCastException
,假设列表中的所有内容都是SubClass
。
通用集合的行为与Java中的数组不同。数组是协变的;也就是说,允许这样做:
SubClass[] subs = ...;
BaseClass[] bases = subs;
这是允许的,因为数组“知道”其元素的类型。如果有人试图在数组中存储不是SubClass
实例的内容(通过bases
引用),则会抛出运行时异常。
通用集合不“知道”其组件类型;这些信息在编译时被“擦除”。因此,当发生无效存储时,它们无法引发运行时异常。相反,当从集合中读取值时,将在代码中的某个远距离,难以关联的点处引发ClassCastException
。如果您注意有关类型安全性的编译器警告,您将在运行时避免这些类型错误。
答案 1 :(得分:35)
erickson已经解释了为什么你不能这样做,但这里有一些解决方案:
如果您只想从基本列表中取出元素,原则上您的接收方法应声明为List<? extends BaseClass>
。
但是如果它不是并且您无法更改它,则可以使用Collections.unmodifiableList(...)
包装列表,这允许返回参数参数的超类型的List。 (它通过在插入尝试时抛出UnsupportedOperationException来避免类型安全问题。)
答案 2 :(得分:10)
正如@erickson解释的那样,如果你真的想要一个对原始列表的引用,那么如果你想在原始声明下再次使用它,请确保没有代码向该列表插入任何内容。获得它的最简单方法是将其强制转换为普通的旧的非泛型列表:
List<BaseClass> baseList = (List)new ArrayList<SubClass>();
如果您不知道列表会发生什么,我建议您更改列表中接受列表所需的任何代码。
答案 3 :(得分:2)
我错过了答案,在这里您只是使用两次强制转换将原始列表转换了。所以这里是为了完整性:
List<BaseClass> baseList = (List<BaseClass>)(List<?>)subList;
不复制任何内容,并且操作很快。但是,您在这里欺骗编译器,因此必须绝对确保不要以subList
开始包含其他子类型的项的方式修改列表。当处理不可变列表时,这通常不是问题。
答案 4 :(得分:1)
List<BaseClass> convertedList = Collections.checkedList(listOfSubClass, BaseClass.class)
答案 5 :(得分:1)
你要做的是非常有用的,我发现我需要在我编写的代码中经常这样做。一个用例示例:
假设我们有一个接口Foo
,我们有一个zorking
包,其中包含ZorkingFooManager
,用于创建和管理package-private ZorkingFoo implements Foo
的实例。 (一种非常常见的情况。)
因此,ZorkingFooManager
需要包含private Collection<ZorkingFoo> zorkingFoos
,但需要公开public Collection<Foo> getAllFoos()
。
大多数java程序员在实现getAllFoos()
之前不会三思而后行,因为分配一个新的ArrayList<Foo>
,用zorkingFoos
中的所有元素填充它并返回它。我喜欢这样的想法,即在全球数百万台机器上运行的java代码消耗的所有时钟周期中,大约30%的时间没有做任何事情,只是创建了这些无用的ArrayLists副本,这些副本在创建后会被垃圾收集微秒。
这个问题的解决方案当然是向下转换集合。这是最好的方法:
static <T,U extends T> List<T> downCastList( List<U> list )
{
return castList( list );
}
这将我们带到castList()
函数:
static <T,E> List<T> castList( List<E> list )
{
@SuppressWarnings( "unchecked" )
List<T> result = (List<T>)list;
return result;
}
由于java语言的异常,中间result
变量是必需的:
return (List<T>)list;
产生“未经检查的强制转换”异常;到现在为止还挺好;但随后:
@SuppressWarnings( "unchecked" ) return (List<T>)list;
非法使用抑制警告注释。
因此,即使在@SuppressWarnings
语句中使用return
不是犹太教,但在分配上使用它显然很好,因此额外的“结果”变量可以解决这个问题。 (无论如何,它应该由编译器或JIT优化。)
答案 6 :(得分:0)
这样的事也应该有效:
public static <T> List<T> convertListWithExtendableClasses(
final List< ? extends T> originalList,
final Class<T> clazz )
{
final List<T> newList = new ArrayList<>();
for ( final T item : originalList )
{
newList.add( item );
}// for
return newList;
}
真的不知道Eclipse中为什么需要clazz ..
答案 7 :(得分:0)
下面是一个有用的代码段。它构造了一个新的数组列表,但是头顶的JVM对象创建是重要的。
我看到其他答案不一定很复杂。
List<BaseClass> baselist = new ArrayList<>(sublist);
答案 8 :(得分:0)
如何投射所有元素。它将创建一个新列表,但将引用旧列表中的原始对象。
List<BaseClass> convertedList = listOfSubClass.map(x -> (BaseClass)x).collect(Collectors.toList());
答案 9 :(得分:0)
这是使用泛型将子类列表转换为超类的完整代码。
传递子类类型的调用者方法
List<SubClass> subClassParam = new ArrayList<>();
getElementDefinitionStatuses(subClassParam);
接受基类任何子类型的Callee方法
private static List<String> getElementDefinitionStatuses(List<? extends
BaseClass> baseClassVariableName) {
return allElementStatuses;
}
}