How to use collect call in Java 8?
Asked Answered
J

2

23

Lets say we have this boring piece of code that we all had to use:

ArrayList<Long> ids = new ArrayList<Long>();
for (MyObj obj : myList){
    ids.add(obj.getId());
}

After switching to Java 8, my IDE is telling me that I can replace this code with collect call, and it auto-generates:

ArrayList<Long> ids = myList.stream().map(MyObj::getId).collect(Collectors.toList());

However its giving me this error:

collect(java.util.stream.Collector) in Steam cannot be applied to: (java.util.stream.Collector, capture, java.util.List>)

I tried casting the parameter but its giving me undefined A and R, and the IDE isn't giving any more suggestions.

I'm curious as how can you use collect call in this scenario, and I couldn't find any information that could guide me properly. Can anyone shed a light?

Jazminejazz answered 13/11, 2014 at 9:25 Comment(0)
B
47

The issue is that Collectors.toList, not surprisingly, returns a List<T>. Not an ArrayList.

List<Long> ids = remove.stream()
        .map(MyObj::getId)
        .collect(Collectors.toList());

Program to the interface.

From the documentation:

Returns a Collector that accumulates the input elements into a new List. There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier).

Emphasis mine - you cannot even assume that the List returned is mutable, let alone that it is of a specific class. If you want an ArrayList:

ArrayList<Long> ids = remove.stream()
        .map(MyObj::getId)
        .collect(Collectors.toCollection(ArrayList::new));

Note also, that it is customary to use import static with the Java 8 Stream API so adding:

import static java.util.stream.Collectors.toCollection;

(I hate starred import static, it does nothing but pollute the namespace and add confusion. But selective import static, especially with the Java 8 utility classes, can greatly reduce redundant code)

Would result in:

ArrayList<Long> ids = remove.stream()
        .map(MyObj::getId)
        .collect(toCollection(ArrayList::new));
Billmyre answered 13/11, 2014 at 9:29 Comment(7)
Actually the result code is not any shorter or much more meaningful than what we did before Java8, hmm what about performance??Ujiji
@Bludream it's not about it being shorter or more meaningful, it's about introducing a new paradigm to Java - functional programming. This allows you to do things in a very different way to previous iterations of Java. Functional programming comes into its own when you need to abstract behaviour rather than data. Further, this above could be a slightly long one-liner; it was certainly not possible to do this in one line previously.Billmyre
I see, But like the OP I was also looking for something to replace my collectors spreading everywhere in my code.Ujiji
@Bludream the OP was looking to assign the result of a collect operation to an ArrayList - I don't see anything regarding the spreading of collectors. I'm not even entirely sure what that means...Billmyre
well, the first lines of code in the question where you create an empty array and fill it using a loop are known as collector blocks.Ujiji
@Bludream depending on what you are doing, you might very well be better off using Guava classes in combination with lambdas to provide views on your collections rather than copying them.Billmyre
would you take a look at my edited answer?? any comment?Ujiji
U
2

I use a lot of collector blocks where I create an empty Array and fill it using a loop so I decided I need a utility class of my own not to write the same lines again ad again, here it is:

public class Collections {

    public static <T, O> List<T> collect(Set<O> items, Function<? super O, ? extends T> mapper) {

    return items.stream().map(mapper).collect(Collectors.toCollection(ArrayList::new));
}

}

and use it like this

List<Product> prods = Collections.collect(basket.getOrderItems(), OrderItem::getProduct);

or like this

List<Long> prods = Collections.collect(basket.getOrderItems(), (item)->item.getProduct().getId());

Though it might look like much more easier to read, it seems streams are a little slower in these kind of scenarios, look here

Ujiji answered 25/4, 2015 at 4:1 Comment(2)
You can use Item::getProduct. Learning to use method references properly is an important part of learning Java 8. Adding an import static also removes the need for referencing the method directly.Billmyre
yes, thanks! I got it working the way you said, and I hate using import static too!Ujiji

© 2022 - 2024 — McMap. All rights reserved.