将元素添加到泛型列表

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&lt;? extends T&gt;,并且它试图避免在列表中添加不同类型的项目。

那么我怎样才能使用泛型,以便我重用函数并能够更新方法中的列表?

【问题讨论】:

@matt: 哪个是“原始列表”?调用代码?在调用代码中,现有代码正在使用此类引用,我无法更改该代码区域。我只想为两种列表类型重用该方法 "调用代码?"是的,您不包含的代码,但对有人找到您的解决方案非常有帮助。您所描述的内容实际上是不可能的,因为您有一个 List&lt;Execute&gt;List&lt;PendingOrder&gt; 并且您想在其中添加一个 Order @matt:调用代码是:updateEntries(data, listOfPendingOrders)updateEntries(data, listOfExecutedOrders)。两者都是Order的子类型 您实际上并没有展示这些列表的创建过程。或者他们正在使用的课程。你应该做一个完整的例子。提供的答案应该可以编译,但由于您无法编译,因此缺少一些细节。 【参考方案1】:

问题是updateEntries 给你一个List&lt;Order&gt;。您不能将 List&lt;Order&gt; 的元素添加到 List&lt;? extends Order&gt; 中,因为后者(如您的示例中所示)可能不希望添加任何 Order 的子类。

例如,如果updateEntries 返回一个List,其中部分/所有条目是CompletedOrders,则不应将它们与PendingOrders 混淆。所以编译器不允许你这样做。

您需要能够传入另一个“事物”,以确保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,它只是将 Orders 转换为 PendingOrders。

【讨论】:

我不明白这是如何解决原始问题的。调用方法不会编译。我用不同子类型的列表来称呼它 在调用处,需要传入额外的函数,例如updateEntries(listOfData, PendingOrder.class::cast, listOfPendingOrders). 什么是PendingOrder.class::cast 这是一个将事物转换为PendingOrder的方法引用,因此您可以将其用作Function&lt;Order, PendingOrder&gt; 编辑中的任何内容都不会真正改变答案。 Order anOrder = createOrder(d); 是问题所在:您需要那些 Orders 与 orders 列表兼容。将其转换为T(如T anOrder = (T) createOrder(d);)是不安全的,因为它是未经检查的转换:这样做会阻止编译器检查正确性,因此您最终可能会遇到堆污染。您必须从列表中的DatacreateOrder 返回的Order 传递一些保证T(或null)实例的东西。 (续)【参考方案2】:

请看一下&lt;? extends T&gt;&lt;? super T&gt;之间的区别。

这里有一个很好的答案: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 它说为什么它没有编译?本杰明,为什么两者都使用 而不仅仅是使用 【参考方案3】:

问题在于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 在他的回答中提出的那样通过强制转换,要么像我的回答那样模仿一种家庭类型的多态性,或者像你在回答中提出的那样通过额外的功能来做同样的事情。

以上是关于将元素添加到泛型列表的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能在编译时将整数添加到泛型集合中,即使使用引用类型作为数字创建的泛型? [复制]

泛型通配符规则

Kotlin:从泛型集合到泛型数组

我丢,去面试初级Java开发岗位,被问到泛型?

java从toArray返回Object[]到泛型的类型擦除

Java基础:泛型