How can I collect a Java 8 stream into a Guava ImmutableCollection? - java-8

I would like to do the following:
List<Integer> list = IntStream.range(0, 7).collect(Collectors.toList());
but in a way that the resulting list is an implementation of Guava's ImmutableList.
I know I could do
List<Integer> list = IntStream.range(0, 7).collect(Collectors.toList());
List<Integer> immutableList = ImmutableList.copyOf(list);
but I would like to collect to it directly. I've tried
List<Integer> list = IntStream.range(0, 7)
.collect(Collectors.toCollection(ImmutableList::of));
but it threw an exception:
java.lang.UnsupportedOperationException
at com.google.common.collect.ImmutableCollection.add(ImmutableCollection.java:96)

The toImmutableList() method in the accepted answer of Alexis is now included in Guava 21 and can be used as:
ImmutableList<Integer> list = IntStream.range(0, 7)
.boxed()
.collect(ImmutableList.toImmutableList());
Edit: Removed #Beta from ImmutableList.toImmutableList along with other frequently used APIs in Release 27.1 (6242bdd).

This is where the collectingAndThen collector is useful:
List<Integer> list = IntStream.range(0, 7).boxed()
.collect(collectingAndThen(toList(), ImmutableList::copyOf));
It applies the transformation to the List you just built; resulting in an ImmutableList.
Or you could directly collect into the Builder and call build() at the end:
List<Integer> list = IntStream.range(0, 7)
.collect(Builder<Integer>::new, Builder<Integer>::add, (builder1, builder2) -> builder1.addAll(builder2.build()))
.build();
If this option is a bit-verbose to you and you want to use it in many places, you can create your own collector:
class ImmutableListCollector<T> implements Collector<T, Builder<T>, ImmutableList<T>> {
#Override
public Supplier<Builder<T>> supplier() {
return Builder::new;
}
#Override
public BiConsumer<Builder<T>, T> accumulator() {
return (b, e) -> b.add(e);
}
#Override
public BinaryOperator<Builder<T>> combiner() {
return (b1, b2) -> b1.addAll(b2.build());
}
#Override
public Function<Builder<T>, ImmutableList<T>> finisher() {
return Builder::build;
}
#Override
public Set<Characteristics> characteristics() {
return ImmutableSet.of();
}
}
and then:
List<Integer> list = IntStream.range(0, 7)
.boxed()
.collect(new ImmutableListCollector<>());
Just in case the link disappears in the comments; my second approach could be defined in a static utility method that simply uses Collector.of. It's simpler than creating your own Collector class.
public static <T> Collector<T, Builder<T>, ImmutableList<T>> toImmutableList() {
return Collector.of(Builder<T>::new, Builder<T>::add, (l, r) -> l.addAll(r.build()), Builder<T>::build);
}
and the usage:
List<Integer> list = IntStream.range(0, 7)
.boxed()
.collect(toImmutableList());

While not a direct answer to my question (it does not use collectors), this is a fairly elegant approach which doesn't use intermediate collections:
Stream<Integer> stream = IntStream.range(0, 7).boxed();
List<Integer> list = ImmutableList.copyOf(stream.iterator());
Source.

BTW: since JDK 10 it can be done in pure Java:
List<Integer> list = IntStream.range(0, 7)
.collect(Collectors.toUnmodifiableList());
Also toUnmodifiableSet and toUnmodifiableMap available.
Inside collector it was done via List.of(list.toArray())

FYI, there's a reasonable way to do this in Guava without Java 8:
ImmutableSortedSet<Integer> set = ContiguousSet.create(
Range.closedOpen(0, 7), DiscreteDomain.integers());
ImmutableList<Integer> list = set.asList();
If you don't actually need the List semantics and can just use a NavigableSet, that's even better since a ContiguousSet doesn't have to actually store all the elements in it (just the Range and DiscreteDomain).

Related

Iterate over Collected list in Java 8 GroupingBy

I have a List of Objects say List<Type1> that I have grouped using type.(using groupingBy)
Now I want to convert that Map> into Type2 that has both the list and the Id of that group.
class Type1{
int id;
int type;
String name;
}
class Type2{
int type;
List<Type1> type1List;
}
This is what I have written to achieve this:
myCustomList
.stream()
.collect(groupingBy(Type1::getType))
.entrySet()
.stream()
.map(type1Item -> new Type2() {
{
setType(type1Item.getKey());
setType1List(type1Item.getValue());
}
})
.collect(Collectors.toList());
This works perfectly. But I am trying to make the code even cleaner. Is there a way to avoid streaming this thing all over again and use some kind of flatmap to achieve this.
You can pass a finisher function to the collectingAndThen to get the work done after the formation of the initial map.
List<Type2> result = myCustomList.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(Type1::getType),
m -> m.entrySet().stream()
.map(e -> new Type2(e.getKey(), e.getValue()))
.collect(Collectors.toList())));
You should give Type2 a constructor of the form
Type2(int type, List<Type1> type1List) {
this.type = type;
this.type1List = type1List;
}
Then, you can write .map(type1Item -> new Type2(type1Item.getKey(), type1Item.getValue())) instead of
.map(type1Item -> new Type2() {
{
setType(type1Item.getKey());
setType1List(type1Item.getValue());
}
})
See also What is Double Brace initialization in Java?
In short, this creates a memory leak, as it creates a subclass of Type2 which captures the type1Item its entire lifetime.
But you can perform the conversion as part of the downstream collector of the groupingBy. This implies that you have to make the toList explicit, to combine it via collectingAndThen with the subsequent mapping:
Collection<Type2> collect = myCustomList
.stream()
.collect(groupingBy(Type1::getType,
collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))))
.values();
If you really need a List, you can use
List<Type2> collect = myCustomList
.stream()
.collect(collectingAndThen(groupingBy(Type1::getType,
collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))),
m -> new ArrayList<>(m.values())));
You can do as mentioned below:
type1.map( type1Item -> new Type2(
type1Item.getKey(), type1Item
)).collect(Collectors.toList());

Efficient way to group by a given list based on a key and collect in same list java 8

I have the below class:
class A{
String property1;
String property2;
Double property3;
Double property4;
}
So the property1 and property2 is the key.
class Key{
String property1;
String property2;
}
I already have a list of A like below:
List<A> list=new ArrayList<>();
I want to group by using the key and add to another list of A in order to avoid having multiple items with same key in the list:
Function<A, Key> keyFunction= r-> Key.valueOf(r.getProperty1(), r.getProperty2());
But then while doing group by I have to take a sum of property3 and average of property4.
I need an efficient way to do it.
Note: I have skipped the methods of the given classes.
Collecting to a Map is unavoidable since you want to group things. A brute-force way to do that would be :
yourListOfA
.stream()
.collect(Collectors.groupingBy(
x -> new Key(x.getProperty1(), x.getProperty2()),
Collectors.collectingAndThen(Collectors.toList(),
list -> {
double first = list.stream().mapToDouble(A::getProperty3).sum();
// or any other default
double second = list.stream().mapToDouble(A::getProperty4).average().orElse(0D);
A a = list.get(0);
return new A(a.getProperty1(), a.getProperty2(), first, second);
})))
.values();
This could be slightly improved for example in the Collectors.collectingAndThen to only iterate the List once, for that a custom collector would be required. Not that complicated to write one...
Try like this:
Map<A,List<A>> map = aList
.stream()
.collect(Collectors
.groupingBy(item->new A(item.property1,item.property2)));
List<A> result= map.entrySet().stream()
.map(list->new A(list.getValue().get(0).property1,list.getValue().get(0).property1)
.avgProperty4(list.getValue())
.sumProperty3(list.getValue()))
.collect(Collectors.toList());
and create avgProperty4 and sumProperty3 methods like to this
public A sumProperty3(List<A> a){
this.property3 = a.stream().mapToDouble(A::getProperty3).sum();
return this;
}
public A avgProperty4(List<A> a){
this.property4 = a.stream().mapToDouble(A::getProperty4).average().getAsDouble();
return this;
}
result = aList.stream().collect(Collectors
.groupingBy(item -> new A(item.property1, item.property2),
Collectors.collectingAndThen(Collectors.toList(), list ->
new A(list.get(0).property1, list.get(0).property1)
.avgProperty4(list).sumProperty3(list))
)
);

Is there a way to print out the chain of all operations in a Flux?

Given a Flux or a Mono from project reactor is a there a way to get the Flux or Mono to print out what the operator chain looks like. For example given the code below.
Fulx flux = Flux.just("a","b","c")
.map( v -> v.toUpperCase())
.log();
Is there some way to get the flux to print out a list of all the operators that are chained inside in the processing pipeline? Some nice ascii formatted text or a marble diagram?
printTheFlux(flux) should make a nice printout that show the structure of all the operators from the example above. I am not expecting to produce the code in the lambda's just a way to see what operators are chained together.
There is partial building blocks for doing this with the Scannable interface:
public String textRepresentation(Flux<?> flux) {
Scannable sc = Scannable.from(flux);
//scan the last operator in the chain and ask if it knows its parents
List<String> names = sc.parents().map(Scannable::operatorName)
.collect(Collectors.toList());
//as it traverses the chain from bottom to top, we need to reverse the order
Collections.reverse(names);
//need to also add the last operator
names.add(sc.operatorName());
return names.toString();
}
#Test
public void textRepresentationTest() {
Flux flux = Flux.just("a","b","c")
.map( v -> v.toUpperCase())
.log();
System.out.println(textRepresentation(flux));
}
Prints
[map, log]
Not all operators fully support it though (as you can see, the just source doesn't for instance).
Nice suggestion!
However, waiting for it, we can just have something like :
Disposable flux = Flux.just("a", "b", "c")
.map(String::toUpperCase)
.doOnNext(FluxUtil::print)
.subscribe();
Where FluxUtil::print is just a static method that you can write with different ways.
Here is the complete code works for me:
public class FluxUtil {
private static String s = "";
public static void main(String[] args) {
Disposable flux = Flux.just("a", "b", "c")
.map(String::toUpperCase)
.doOnNext(FluxUtil::print)
.subscribe();
}
private static Object print(Object o) {
s = !s.isEmpty() ? s.concat("->") : s;
s = s.concat(o.toString());
System.out.println(s);
return o;
}
}

java 8 list grouping with value mapping function producing list

I have a following Person class
public class Person {
public String name;
public List<Brand> brands;
//Getters
}
and a List<Person> persons(possibly with same names). I need to group in a map of <String, List<Brand>> with Person's name as Keys and lists of accumulated Brands as values.
Something like this
Map<String, List<List<String>>> collect = list.stream().collect(
groupingBy(Person::getName, mapping(Person::getBrands, toList()))
);
produces undesired result and I know why. If the values could be somehow flatten during grouping? Is there a way to do it right there with Streams api?
java 9 will add the flatMapping collector specifically for this type of task:
list.stream().collect(
groupingBy(
Person::getName,
flatMapping(
p -> p.getBrands().stream(),
toList()
)
)
Guessing what is the desired result, you can achieve it with just toMap collector:
Map<String, List<String>> collect = persons.stream().collect(
toMap(
Person::getName,
Person::getBrands,
(l1, l2) -> ImmutableList.<String /*Brand*/>builder().addAll(l1).addAll(l2).build())
);
You will need to merge brands into a single List:
list.stream().collect(Collectors.toMap(
Person::getName,
Person::getBrands,
(left, right) -> {
left.addAll(right);
return left;
},
HashMap::new));
You can create a custom collector for the downstream to your groupBy:
Collector.of(LinkedList::new,
(list, person) -> list.addAll(person.brands),
(lhs, rhs) -> { lhs.addAll(rhs); return rhs; })
There is MoreCollectors provided in open source library: StreamEx
list.stream().collect(
groupingBy(Person::getName, MoreCollectors.flatMapping(p -> p.getBrands().stream()));

Tranversing and filtering a Set comparing its objects' getters to an Array using Stream

I've got some working, inelegant code here:
The custom object is:
public class Person {
private int id;
public getId() { return this.id }
}
And I have a Class containing a Set<Person> allPersons containing all available subjects. I want to extract a new Set<Person> based upon one or more ID's of my choosing. I've written something which works using a nested enhanced for loop, but it strikes me as inefficient and will make a lot of unnecessary comparisons. I am getting used to working with Java 8, but can't quite figure out how to compare the Set against an Array. Here is my working, but verbose code:
public class MyProgram {
private Set<Person> allPersons; // contains 100 people with Ids 1-100
public Set<Person> getPersonById(int[] ids) {
Set<Person> personSet = new HashSet<>() //or any type of set
for (int i : ids) {
for (Person p : allPersons) {
if (p.getId() == i) {
personSet.add(p);
}
}
}
return personSet;
}
}
And to get my result, I'd call something along the lines of:
Set<Person> resultSet = getPersonById(int[] intArray = {2, 56, 66});
//resultSet would then contain 3 people with the corresponding ID
My question is how would i convert the getPersonById method to something using which streams allPersons and finds the ID match of any one of the ints in its parameter array? I thought of some filter operation, but since the parameter is an array, I can't get it to take just the one I want only.
The working answer to this is:
return allPersons.stream()
.filter(p -> (Arrays.stream(ids).anyMatch(i -> i == p.getId())) )
.collect(Collectors.toSet());
However, using the bottom half of #Flown's suggestion and if the program was designed to have a Map - it would also work (and work much more efficiently)
As you said, you can introduce a Stream::filter step using a Stream::anyMatch operation.
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
if (ids.length == 0) {
return Collections.emptySet();
}
return allPersons.stream()
.filter(p -> IntStream.of(ids).anyMatch(i -> i == p.getId()))
.collect(Collectors.toSet());
}
If the method is called more often, then it would be a good idea to map each Person to its id having a Map<Integer, Person>. The advantage is, that the lookup is much faster than iterating over the whole set of Person.Then your algorithm may look like this:
private Map<Integer, Person> idMapping;
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
return IntStream.of(ids)
.filter(idMapping::containsKey)
.mapToObj(idMapping::get)
.collect(Collectors.toSet());
}

Resources