-1

I have the following method:

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);   
} 

The method createOrder will create a subtype of Order based on the Data argument. This part compiles.

But the problem is that the calling code of the method will not compile since it is called as follows:

updateEntries(List<Data> data, List<PendingOrder> orders) 

and

updateEntries(List<Data> data, List<ExecutedOrder> orders)  

both of these are subtypes of Order and hence the code won't compile on the calling side of the method.

If I update the code as follows:

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
    } 

The problem is with the line: orders.addAll(updates); That does not compile of course because the list is declared as List<? extends T> and it tries to avoid adding items of different types in the list.

So how can I use generics so I reuse the function and be able to update the list in the method?

Jim
  • 2,341
  • 1
  • 11
  • 23
  • @matt: which one is the "original list"? The calling code? In the calling code the existing code is using such references and I can't change that area of the code. I want to just reuse the method for both list types – Jim May 19 '21 at 11:12
  • "The calling code?" yes, the code you don't include, but would be very helpful for somebody to find you a solution. What you're describing isn't really possible because you have a `List` or a `List` and you want to add an `Order` to it. – matt May 19 '21 at 12:12
  • @matt: the calling code is: `updateEntries(data, listOfPendingOrders)` and `updateEntries(data, listOfExecutedOrders)`. Both are subtypes of `Order` – Jim May 19 '21 at 12:29
  • You don't actually show the creation of these lists. Or the class they're using. You should make a complete example. The answers provided should compile but since you cannot get it to compile, there are some details missing. – matt May 19 '21 at 12:40

3 Answers3

3

The problem is that updateEntries gives you back a List<Order>. You can't add the elements of a List<Order> to a List<? extends Order> because the latter - as in your example - may not expect to have any subclass of Order added to it.

For example, if updateEntries returns a List where some/all of the entries are CompletedOrders, you shouldn't muddle those up with PendingOrders. So the compiler won't allow you to do that.

You need to be able to pass in another "thing" which allows you to ensure that the things returned by updateEntries will be instances of the element type required by your orders list.

For example:

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   
} 

Alternatively, if orderFn is literally just something you can apply to each of your elements in this method, you can do something like:

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);
}

At the call site, you need to pass in the extra function, e.g.

updateEntries(listOfData, PendingOrder.class::cast, listOfPendingOrders);

which passes in a Function that just casts the Orders to PendingOrders.

Andy Turner
  • 122,430
  • 10
  • 138
  • 216
  • I don't understand how this solves the original problem. The calling method won't compile. I am calling it with lists of different subtypes – Jim May 19 '21 at 10:31
  • At the call site, you need to pass in the extra function, e.g. `updateEntries(listOfData, PendingOrder.class::cast, listOfPendingOrders)`. – Andy Turner May 19 '21 at 10:36
  • What is `PendingOrder.class::cast`? – Jim May 19 '21 at 10:37
  • It's a method reference which casts things to `PendingOrder`, so you can use it as a `Function`. – Andy Turner May 19 '21 at 10:37
  • I can not use the second example you provided. So focusing on your first snippet, it still will not compile because the original problem is there in the calling side – Jim May 19 '21 at 10:38
  • I don't know the details and limitations of your code. You're going to need to provide more information if you want a solution; but this explains what the problem is and *an* approach to solving it. – Andy Turner May 19 '21 at 10:43
  • I updated the code because I tried to simplify it originally but turns out I messed up, I am sorry about that. The structure is now in the post. My confusion is how can I add the list list, regardless of what subtype the order is. The `createOrder` returns the proper subtype based on data – Jim May 19 '21 at 10:48
  • The problem/solution hasn't really changed given your edit: `createOrder` returns an `Order` (whether or not it returns a subtype isn't visible to the compiler: the return type is `Order`, so it could be any subtype of `Order`), you can't add that to a `List extends Order>`; you need to pass in a "thing" so you can create an instance of `T` from your data, e.g. a `Function`. – Andy Turner May 19 '21 at 10:50
  • Is the solution to move that loop to another function as in your code example then? – Jim May 19 '21 at 10:51
  • You are using `List` in the method argument. But my calling code will need to call `List` and that does not compile – Jim May 19 '21 at 10:52
  • "and that does not compile" did you declare a bounded type variable, as shown in the answer, and use it as both the element type of the list and the return type of the function? – Andy Turner May 19 '21 at 10:53
  • yes, it is still not compiling – Jim May 19 '21 at 10:54
  • I suspect I confused you and you have focused on the wrong thing. Let me fix the post again – Jim May 19 '21 at 10:55
  • I updated the post. I hope this is better now. Apologies again – Jim May 19 '21 at 11:00
  • 2
    Nothing in the edits really changes the answer. `Order anOrder = createOrder(d);` is the problem: you need those `Order`s to be something compatible with the `orders` list. Casting it to `T` (as in `T anOrder = (T) createOrder(d);`) isn't safe because it's an unchecked cast: doing that prevents the compiler checking correctness, so you're likely to end up with heap pollution. You have to pass in something which guarantees you instances of `T` (or null), either from the `Data` in the list, or from the `Order` returned by `createOrder`. (cont) – Andy Turner May 19 '21 at 13:05
  • That means injecting either a `Function` or a `Function` (or a `Function super Data, ? extends T>` or a `Function super Order, ? extends T>`, if you want it to be more flexible). – Andy Turner May 19 '21 at 13:07
  • The fundamental problem here is that the the type parameter of the order list is in a covariant position in `updateEntries` and in a contravariant position for the `addAll` call, making it effectively invariant. Unless this is reflected in the type the code does not compile. Suggestions to adjust the type accordingly have been given in this answer and along similar lines in my own answer. – michid May 20 '21 at 07:46
-1

Please have a look at the difference between <? extends T> and <? super T>.

There is a good answer here: Difference between <? super T> and <? extends T> in Java

If i'am right your code should look like this (but i did not test it):

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);   
} 
Benjamin Schüller
  • 1,884
  • 1
  • 12
  • 25
  • No, that still does not compile in the calling side – Jim May 19 '21 at 11:32
  • Do you have still the compiler error on "orders.addAll(updates); "? – Benjamin Schüller May 19 '21 at 11:40
  • I have changed my code a little bit. So it should fit your needs. – Benjamin Schüller May 19 '21 at 11:59
  • The calling code `updateEntries(data, pendingOrderList)` does not compile – Jim May 19 '21 at 12:05
  • Also with my corrected code? – Benjamin Schüller May 19 '21 at 12:19
  • @Jim Does it say why it isn't compiling? Benjamin, why both with the and not just use a ? – matt May 19 '21 at 12:20
  • @Jim here is a small example of it compiling and working. If this doesn't work for you it is because you are omitting some details. https://ideone.com/pNSIXx – matt May 19 '21 at 12:24
  • @matt: But in your snippet you dont add anything inside the function – Jim May 19 '21 at 12:28
  • @Jim I am just trying to demonstrate the calling code. You're saying the part `updateEntries(data, pendingOrderList)` doesn't compile. What is the error you're getting from the compiler? – matt May 19 '21 at 12:36
  • The problem with this approach is that it's not type-safe: `(T) createOrder(d)` is an unchecked cast to `T`. If `createOrder` returns something that's not a `T` (say, a `CompletedOrder` rather than a `PendingOrder`, if `orders` is a `List`), you won't find out until after `updateEntries` has completed. – Andy Turner May 20 '21 at 08:22
-1

The problem is with the createOrder function that creates a subtype of Order without being explicit about it. Change that signature to

<T extends Order> T createOrder(Data data) 

and be explicit about the type T in the updateEntries method:

<T extends Order> void updateEntries(List<Data> data, List<T> orders) 

If you cannot change the signature of createOrder for any reason, wrap the call into a private method and add a cast there.

michid
  • 8,478
  • 3
  • 28
  • 49
  • The problem with this approach is that it's not type-safe: `createOrder` would have to do an unchecked cast to `T`. This is described more here: http://errorprone.info/bugpattern/TypeParameterUnusedInFormals – Andy Turner May 20 '21 at 08:16
  • @AndyTurner, agree. The origin of the problem however is, that there is no type that satisfies the OP's original question. See also my comment re. variance in the other answer. With this in mind, something has to give. Either by casting, like Benjamin Schüller proposes in his answer, or by emulating a sort of family type polymorphism like my answer does or doing the same via a extra function like you propose in your answer. – michid May 20 '21 at 10:15