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?

Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
Alexandru Severin
  • 6,021
  • 11
  • 48
  • 71

2 Answers2

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));
Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • Actually the result code is not any shorter or much more meaningful than what we did before Java8, hmm what about performance?? – azerafati Apr 25 '15 at 03:39
  • 4
    @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. – Boris the Spider Apr 25 '15 at 09:41
  • I see, But like the OP I was also looking for something to replace my collectors spreading everywhere in my code. – azerafati Apr 25 '15 at 10:45
  • @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... – Boris the Spider Apr 25 '15 at 10:47
  • 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. – azerafati Apr 25 '15 at 11:30
  • @Bludream depending on what you are doing, you might very well be better off using [Guava classes](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Collections2.html) in combination with lambdas to provide _views_ on your collections rather than copying them. – Boris the Spider Apr 25 '15 at 11:33
  • would you take a look at my edited answer?? any comment? – azerafati Apr 25 '15 at 12:53
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

Community
  • 1
  • 1
azerafati
  • 18,215
  • 7
  • 67
  • 72
  • 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. – Boris the Spider Apr 25 '15 at 10:48
  • yes, thanks! I got it working the way you said, and I hate using `import static` too! – azerafati Apr 25 '15 at 11:33