将元素添加到泛型列表
Posted
技术标签:
【中文标题】将元素添加到泛型列表【英文标题】:Adding elements to a generics list 【发布时间】:2021-05-19 10:24:24 【问题描述】:我有以下方法:
private void updateEntries(List<Data> data, List<Order> orders)
// processing code here
List<Order> updates = new ArrayList<>();
for(Data d: data)
Order anOrder = createOrder(d);
if(anOrder != null)
updates.add(anOrder);
orders.clear();
orders.addAll(updates);
方法createOrder
将基于Data
参数创建Order
的子类型。这部分编译。
但问题是方法的调用代码不会编译,因为它是这样调用的:
updateEntries(List<Data> data, List<PendingOrder> orders)
和
updateEntries(List<Data> data, List<ExecutedOrder> orders)
这两个都是Order
的子类型,因此代码不会在方法的调用端编译。
如果我更新代码如下:
private void updateEntries(List<Data> data, List<? extends Order> orders)
// processing code here
List<Order> updates = new ArrayList<>();
for(Data d: data)
Order anOrder = createOrder(d);
if(anOrder != null)
updates.add(anOrder);
orders.clear();
orders.addAll(updates); // <= does not compile
问题出在以下行:orders.addAll(updates);
这当然不会编译,因为列表被声明为List<? extends T>
,并且它试图避免在列表中添加不同类型的项目。
那么我怎样才能使用泛型,以便我重用函数并能够更新方法中的列表?
【问题讨论】:
@matt: 哪个是“原始列表”?调用代码?在调用代码中,现有代码正在使用此类引用,我无法更改该代码区域。我只想为两种列表类型重用该方法 "调用代码?"是的,您不包含的代码,但对有人找到您的解决方案非常有帮助。您所描述的内容实际上是不可能的,因为您有一个List<Execute>
或 List<PendingOrder>
并且您想在其中添加一个 Order
。
@matt:调用代码是:updateEntries(data, listOfPendingOrders)
和updateEntries(data, listOfExecutedOrders)
。两者都是Order
的子类型
您实际上并没有展示这些列表的创建过程。或者他们正在使用的课程。你应该做一个完整的例子。提供的答案应该可以编译,但由于您无法编译,因此缺少一些细节。
【参考方案1】:
问题是updateEntries
给你一个List<Order>
。您不能将 List<Order>
的元素添加到 List<? extends Order>
中,因为后者(如您的示例中所示)可能不希望添加任何 Order
的子类。
例如,如果updateEntries
返回一个List
,其中部分/所有条目是CompletedOrder
s,则不应将它们与PendingOrder
s 混淆。所以编译器不允许你这样做。
您需要能够传入另一个“事物”,以确保updateEntries
返回的事物将是您的orders
列表所需的元素类型的实例。
例如:
private <T extends Order> void updateEntries(List<Data> data, Function<Order, T> orderFn, List<T> orders)
// processing code here
// Pass in orderFn here too:
List<T> updates = updateEntries(data, orderFn);
orders.clear();
orders.addAll(updates); // <- does not compile
或者,如果orderFn
只是您可以在此方法中应用于每个元素的内容,您可以执行以下操作:
private <T extends Order> void updateEntries(List<Data> data, Function<Order, T> orderFn, List<T> orders)
// processing code here
orders.clear();
updateEntries(data).stream().map(orderFn).forEach(orders::add);
在调用处,需要传入额外的函数,例如
updateEntries(listOfData, PendingOrder.class::cast, listOfPendingOrders);
它传入一个 Function
,它只是将 Order
s 转换为 PendingOrder
s。
【讨论】:
我不明白这是如何解决原始问题的。调用方法不会编译。我用不同子类型的列表来称呼它 在调用处,需要传入额外的函数,例如updateEntries(listOfData, PendingOrder.class::cast, listOfPendingOrders)
.
什么是PendingOrder.class::cast
?
这是一个将事物转换为PendingOrder
的方法引用,因此您可以将其用作Function<Order, PendingOrder>
。
编辑中的任何内容都不会真正改变答案。 Order anOrder = createOrder(d);
是问题所在:您需要那些 Order
s 与 orders
列表兼容。将其转换为T
(如T anOrder = (T) createOrder(d);
)是不安全的,因为它是未经检查的转换:这样做会阻止编译器检查正确性,因此您最终可能会遇到堆污染。您必须从列表中的Data
或createOrder
返回的Order
传递一些保证T
(或null)实例的东西。 (续)【参考方案2】:
请看一下<? extends T>
和<? super T>
之间的区别。
这里有一个很好的答案:Difference between <? super T> and <? extends T> in Java
如果我是对的,你的代码应该是这样的(但我没有测试过):
private <T extends Order> void updateEntries(List<Data> data, List<T> orders)
// processing code here
List<T> updates = new ArrayList<>();
for(Data d: data)
T anOrder = (T)createOrder(d);
if(anOrder != null)
updates.add(anOrder);
orders.clear();
orders.addAll(updates);
【讨论】:
不,在调用方仍然无法编译 “orders.addAll(updates);”上是否还有编译器错误? 我已经稍微更改了我的代码。所以它应该适合您的需求。 调用代码updateEntries(data, pendingOrderList)
没有编译
@Jim 它说为什么它没有编译?本杰明,为什么两者都使用 问题在于createOrder
函数创建了Order
的子类型而没有明确说明。将该签名更改为
<T extends Order> T createOrder(Data data)
并在updateEntries
方法中明确说明T
类型:
<T extends Order> void updateEntries(List<Data> data, List<T> orders)
如果您因任何原因无法更改 createOrder
的签名,请将调用包装到私有方法中并在其中添加强制转换。
【讨论】:
这种方法的问题在于它不是类型安全的:createOrder
必须对T
进行未经检查的强制转换。此处对此进行了更多描述:errorprone.info/bugpattern/TypeParameterUnusedInFormals
@AndyTurner,同意。然而,问题的根源是,没有满足 OP 原始问题的类型。另请参阅我的评论。另一个答案的差异。考虑到这一点,必须给予一些东西。要么像 Benjamin Schüller 在他的回答中提出的那样通过强制转换,要么像我的回答那样模仿一种家庭类型的多态性,或者像你在回答中提出的那样通过额外的功能来做同样的事情。以上是关于将元素添加到泛型列表的主要内容,如果未能解决你的问题,请参考以下文章
为啥我不能在编译时将整数添加到泛型集合中,即使使用引用类型作为数字创建的泛型? [复制]