Collector to join incoming lists - java

I have a method that receives a collector. The collector should combine incoming lists:
reducing(Lists.newArrayList(), (container, list) -> {
container.addAll(list);
return container;
})
This seems like a very common scenario to me, and I believe Java's own collector should have something to cover this use case, but I cannot remember what. Besides, I want it to return ImmutableList.

To obtain a "flattened" list of elements from a nested collection firstly you need to apply flatMapping() which takes a function that turns an element to a stream and a collector as second argument. And to get an immutable list apply toUnmodifiableList() as a downstream collector inside the flatMapping() (as already mentioned by racraman and hfontanez in their comments, since they pointed that earlier the credits for this answer should belong to them).
List<List<Integer>> nestedList = List.of(
List.of(1, 2),
List.of(3, 4)
);
nestedList.stream()
.collect(Collectors.flatMapping(List::stream,
Collectors.toUnmodifiableList()))
.forEach(System.out::println);
output
1
2
3
4
A more common use-case for Collector.flatMapping() is when each element of the stream holds a reference to a collection, rather than being a collection itself.
Consider the following scenario.
We have a collection of Orders. Each Order has an id and a collection of Items. And we want to obtain a map from this collection of orders so that based on the order id we can get a list of items.
In this situation Collector.flatMapping() is indispensable because by applying the flatMap() in the middle of the stream we will lose access to the orderId in the collect(). Hence, flattening should happen inside the collect() operation.
A method that implements the logic described above might look this:
public Map<String, List<Item>> getItemsByOrderId(Collection<Order> orders) {
return orders.stream()
// some logic here
.collect(Collectors.groupingBy(Order::getId,
Collectors.flatMapping(order ->
order.getItems().stream().filter(somePredicate),
Collectors.toList())));
}

We can also do flatMap() operation and toList() collector, to collect multiple incoming lists to one resultant list.
List<String> joinedList = Stream.of(
asList("1", "2"),
asList("3", "4")).flatMap(list -> list.stream()
)
.collect(toList());
System.out.println("Printing the list named as joinedList: " + joinedList);
Output:
Printing the list named joinedList: [1, 2, 3, 4]

Related

What's the difference between list interface sort method and stream interface sorted method?

I'm interested in sorting an list of object based on date attribute in that object. I can either use list sort method.
list.sort( (a, b) -> a.getDate().compareTo(b.getDate()) );
Or I can use stream sorted method
List<E> l = list.stream()
.sorted( (a, b) -> a.getDate().compareTo(b.getDate()))
.collect(Collectors.toList());
Out of both above option which should we use and why?
I know the former one will update my original list and later one will not update the original but instead give me a fresh new list object.
So, I don't care my original list is getting updated or not. So which one is good option and why?
If you only need to sort your List, and don't need any other stream operations (such as filtering, mapping, etc...), there's no point in adding the overhead of creating a Stream and then creating a new List. It would be more efficient to just sort the original List.
If you wish to known which is best, your best option is to benchmark it: you may reuse my answer JMH test.
It should be noted that:
List::sort use Arrays::sort. It create an array before sorting. It does not exists for other Collection.
Stream::sorted is done as state full intermediate operation. This means the Stream need to remember its state.
Without benchmarking, I'd say that:
You should use collection.sort(). It is easier to read: collection.stream().sorted().collect(toList()) is way to long to read and unless you format your code well, you might have an headache (I exaggerate) before understanding that this line is simply sorting.
sort() on a Stream should be called:
if you filter many elements making the Stream effectively smaller in size than the collection (sorting N items then filtering N items is not the same than filtering N items then sorting K items with K <= N).
if you have a map transformation after the sort and you loose a way to sort using the original key.
If you use your stream with other intermediate operation, then sort might be required / useful:
collection.stream() // Stream<U> #0
.filter(...) // Stream<U> #1
.sorted() // Stream<U> #2
.map(...) // Stream<V> #3
.collect(toList()) // List<V> sorted by U.
;
In that example, the filter apply before the sort: the stream #1 is smaller than #0, so the cost of sorting with stream might be less than Collections.sort().
If all that you do is simply filtering, you may also use a TreeSet or a collectingAndThen operation:
collection.stream() // Stream<U> #0
.filter(...) // Stream<U> #1
.collect(toCollection(TreeSet::new))
;
Or:
collection.stream() // Stream<U>
.filter(...) // Stream<U>
.collect(collectingAndThen(toList(), list -> {
list.sort();
return list;
})); // List<V>
Streams have some overheads because it creates many new objects like a concrete Stream, a Collector, and a new List. So if you just want to sort a list and doesn't care about whether the original gets changed or not, use List.sort.
There is also Collections.sort, which is an older API. The difference between it and List.sort can be found here.
Stream.sorted is useful when you are doing other stream operations alongside sorting.
Your code can also be rewritten with Comparator:
list.sort(Comparator.comparing(YourClass::getDate)));
First one would be better in term of performance. In the first one, the sort method just compares the elements of the list and orders them. The second one will create a stream from your list, sort it and create a new list from that stream.
In your case, since you can update the first list, the first approach is the better, both in term of performance and memory consumption. The second one is convenient if you need to and with a stream, or if you have a stream and want to end up with a sorted list.
You use the first method
list.sort((a, b) -> a.getDate().compareTo(b.getDate()));
it's much faster than the second one and it didn't create a new intermediate object. You could use the second method when you want to do some additional stream operations (e.g. filtering, map).

Cactoos flatMap analogy

Is there flatMap analogy in Cactoos library? I need exactly what flatMap can, but without streams:
The flatMap() operation has the effect of applying a one-to-many transformation to the elements of the stream, and then flattening the resulting elements into a new stream.
E.g. if I have some values in list, and each value has children items, and I want to get all items from each value, I can use flatMap:
List<Value> values = someValues();
List<Item> items = values.stream()
.flatMap(val -> val.items().stream()) // val.items() returns List<Item>
.collect(Collectors.toList());
How to do the same thing using Cactoos instead of streams API?
You can use Joined, it is the equivalent of flattening an Iterable.
For example, you would write:
new Joined<>(new Mapped<>(val -> val.items(), someValues()));

How do I iterate through two streams using java 8 lambda expressions and keeping a count

I have two lists streams one of strings (counties) and one of objects(txcArray). I need to iterate through both lists and compare an instance of counties with an instance of txcArray and they match increment a counter and if they don't I would move on. I need to do this using java 8 lambda expressions and this is what I have so far.
counties.stream().forEach(a-> {
txcArray.stream()
.filter(b->b.getCounty().equals(a))
.map(Map<String,Integer>)
});
Your mistake is using forEach.
List<Long> counts = counties.stream()
.map(a -> txcArray.stream().filter(b -> b.getCounty().equals(a)).count())
.collect(Collectors.toList());
However, this is not very efficient, performing counties.size() × txcArray.size() operations. This can get out of hands easily, when the lists are larger.
It’s better to use
Map<String, Long> map = txcArray.stream()
.collect(Collectors.groupingBy(b -> b.getCounty(), Collectors.counting()));
List<Long> counts = counties.stream()
.map(a -> map.getOrDefault(a, 0L))
.collect(Collectors.toList());
This will perform counties.size() + txcArray.size() operations, which will be more efficient for larger lists, therefore, preferable, even if it’s not a single stream operation but using an intermediate storage.

Combine stream of Collections into one Collection - Java 8

So I have a Stream<Collection<Long>> that I obtain by doing a series of transformations on another stream.
What I need to do is collect the Stream<Collection<Long>> into one Collection<Long>.
I could collect them all into a list like this:
<Stream<Collection<Long>> streamOfCollections = /* get the stream */;
List<Collection<Long>> listOfCollections = streamOfCollections.collect(Collectors.toList());
And then I could iterate through that list of collections to combine them into one.
However, I imagine there must be a simple way to combine the stream of collections into one Collection<Long> using a .map() or .collect(). I just can't think of how to do it. Any ideas?
This functionality can be achieved with a call to the flatMap method on the stream, which takes a Function that maps the Stream item to another Stream on which you can collect.
Here, the flatMap method converts the Stream<Collection<Long>> to a Stream<Long>, and collect collects them into a Collection<Long>.
Collection<Long> longs = streamOfCollections
.flatMap( coll -> coll.stream())
.collect(Collectors.toList());
You could do this by using collect and providing a supplier (the ArrayList::new part):
Collection<Long> longs = streamOfCollections.collect(
ArrayList::new,
ArrayList::addAll,
ArrayList::addAll
);
You don't need to specify classes when not needed.
A better solution is:
Collection<Long> longs = streamOfCollections.collect(
ArrayList::new,
Collection::addAll,
Collection::addAll
);
Say, you don't need an ArrayList but need a HashSet, then you also need to edit only one line.

Collection to stream to a new collection

I'm looking for the most pain free way to filter a collection. I'm thinking something like
Collection<?> foo = existingCollection.stream().filter( ... ). ...
But I'm not sure how is best to go from the filter, to returning or populating another collection. Most examples seem to be like "and here you can print". Possible there's a constructor, or output method that I'm missing.
There’s a reason why most examples avoid storing the result into a Collection. It’s not the recommended way of programming. You already have a Collection, the one providing the source data and collections are of no use on its own. You want to perform certain operations on it so the ideal case is to perform the operation using the stream and skip storing the data in an intermediate Collection. This is what most examples try to suggest.
Of course, there are a lot of existing APIs working with Collections and there always will be. So the Stream API offers different ways to handle the demand for a Collection.
Get an unmodifiable List implementation containing all elements (JDK 16):
List<T> results = l.stream().filter(…).toList();
Get an arbitrary List implementation holding the result:
List<T> results = l.stream().filter(…).collect(Collectors.toList());
Get an unmodifiable List forbidding null like List.of(…) (JDK 10):
List<T> results = l.stream().filter(…).collect(Collectors.toUnmodifiableList());
Get an arbitrary Set implementation holding the result:
Set<T> results = l.stream().filter(…).collect(Collectors.toSet());
Get a specific Collection:
ArrayList<T> results =
l.stream().filter(…).collect(Collectors.toCollection(ArrayList::new));
Add to an existing Collection:
l.stream().filter(…).forEach(existing::add);
Create an array:
String[] array=l.stream().filter(…).toArray(String[]::new);
Use the array to create a list with a specific specific behavior (mutable, fixed size):
List<String> al=Arrays.asList(l.stream().filter(…).toArray(String[]::new));
Allow a parallel capable stream to add to temporary local lists and join them afterward:
List<T> results
= l.stream().filter(…).collect(ArrayList::new, List::add, List::addAll);
(Note: this is closely related to how Collectors.toList() is currently implemented, but that’s an implementation detail, i.e. there is no guarantee that future implementations of the toList() collectors will still return an ArrayList)
An example from java.util.stream's documentation:
List<String>results =
stream.filter(s -> pattern.matcher(s).matches())
.collect(Collectors.toList());
Collectors has a toCollection() method, I'd suggest looking this way.
As an example that is more in line with Java 8 style of functional programming:
Collection<String> a = Collections.emptyList();
List<String> result = a.stream().
filter(s -> s.length() > 0).
collect(Collectors.toList());
You would possibly want to use toList or toSet or toMap methods from Collectors class.
However to get more control the toCollection method can be used. Here is a simple example:
Collection<String> c1 = new ArrayList<>();
c1.add("aa");
c1.add("ab");
c1.add("ca");
Collection<String> c2 = c1.stream().filter(s -> s.startsWith("a")).collect(Collectors.toCollection(ArrayList::new));
Collection<String> c3 = c1.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
c2.forEach(System.out::println); // prints-> aa ab
c3.forEach(System.out::println); // prints-> aa ab

Categories

Resources