Using Streams filter Map based on a list of keys

I have a particular problem and was wondering whether the Java 8 Streams API could solve it. I know that this can be done outside of using Streams API but I don’t want to add all the boilerplate code associated with trying to achieve that, if it can be done using Streams. I have a map

Map<String, String> greetings = new HashMap<>();
greetings.put("abc", "Hello");
greetings.put("def", "Goodbye");
greetings.put("ghi", "Ciao");
greetings.put("xyz", "Bonsoir");

and a list of keys:

List<String> keys = Arrays.asList("def", "zxy");

and using the above with Streams API, is it possible to filter that down to:

Map<String, String> filteredGreetings = new HashMap<>();
filteredGreetings.put("def", "Goodbye");
filteredGreetings.put("xyz", "Bonsoir");

Hopefully this makes sense what I am trying to achieve.

So far I have got this to work only when specifying the exact key which to filter the map’s keySet on, but then this would only return a single entry set. I am interested in a completely filtered down map and I am struggling to achieve that.

Solution:

Try this:

Map<String, String> result = keys.stream()
        .filter(greetings::containsKey)
        .collect(Collectors.toMap(Function.identity(), greetings::get));

Or the other way round:

Map<String, String> result = greetings.entrySet().stream()
        .filter(e -> keys.contains(e.getKey()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

For the second approach I would recommend using a Set<String> for a larger list of keys because it has a O(1) time complexity for .contains() because it does not contain any duplicates:

Set<String> keys = new HashSet<>(Arrays.asList("def", "zxy"));

Using Java 8 Streams' Collectors to increment value based of existing key/value pair

Suppose there is a List<Object> and that Object contains two methods: getUserId and getPoints.

Consider that List<Object> contains three objects, and they contain the following data:

userId A; 3 points
userId A; 5 points
userId B; 1 point

After collecting this properly, I am expecting to have a Map<String, Integer> that would look like this:

A: 8,
B: 1

I am attempting this using Java’s 8 functional interfaces, the streams, because I only need the final result and I do not have nor need any of these mappings in between. I am willing to modify this if there is no other way.
I first came up with this:

this.service.getListObject()
    .stream()
    .collect(Collectors.toMap(Object::getUserId, Object::getPoints));

However, this has shown insufficient because it will always replace the key with the newest value, thus producing:

A: 5,
B: 1

How can I tweak the last bits from the Collectors.toMap() to automatically increment its value according to the value already stored in the map, so that I produce the result above?

Solution:

Use Collectors.groupingBy together with Collectors.summingInt as a downstream collector:

this.service.getListObject()
    .stream().collect(Collectors.groupingBy(
        Object::getUserId, Collectors.summingInt(Object::getPoints)));

Java Comparator with null fields

I have a list of entities Entity with the fields id and createdDate. I want to sort them as following:

  • higher id first
  • if id null, most recent createdDate first

I’ve tried the following unsuccessfuly, as it throwns a NullPointerException when id is null

Comparator comp = Comparator
                .nullsFirst(Comparator.comparing(e -> ((Entity) e).getId()))
                .thenComparing(e -> ((Entity e).getCreatedDate())
                .reversed();
entities.stream().sorted(comp).findFirst();

For what I see, Comparator.nullsFirst handles when the entity is null, not when the field to be compared is null. How can I handle this situation?

Solution:

I think you are looking for comparator like this :

Comparator<MyClass> comparator = Comparator.comparing(MyClass::getId, Comparator.nullsLast(Comparator.reverseOrder()))
                .thenComparing(MyClass::getCreateDate);

The code to test it :

List<MyClass> list = new ArrayList<>();

list.add(new MyClass(null, LocalDate.now()));
list.add(new MyClass(4L, LocalDate.now()));
list.add(new MyClass(2L, LocalDate.now()));
list.add(new MyClass(4L, LocalDate.now().plusDays(1)));
list.add(new MyClass(null, LocalDate.now().plusDays(1)));

Comparator<MyClass> comparator = Comparator.comparing(MyClass::getId, Comparator.nullsLast(Comparator.reverseOrder()))
                .thenComparing(MyClass::getCreateDate);

list.stream().sorted(comparator).forEach(myClass -> System.out.println(myClass.id + " " + myClass.createDate));

The output is :

4 2019-06-14
4 2019-06-15
2 2019-06-14
null 2019-06-14
null 2019-06-15

If you want nulls to be first just change nullsLast to nullsFirst.

Isn't it guaranteed that parallel stream derived from list will always behave like its sequential counterpart and give the same, predictable output?

The following code prints true for 100 times:

for(int i=0; i<100; i++) {
   String s2 = Arrays.asList("A", "E", "I", "O", "U").parallelStream().reduce("x", String::concat, String::concat);
   System.out.println("xAxExIxOxU".equals(s2));
}

Granted, 100 times is not a guarantee. But doesn’t it seem though that even if the identity used here does not meet the requirement “…for all u, combiner.apply(identity, u) is equal to u” per the doc, we can still say that a parallel stream derived from list or any other inherently ordered structure will behave just like a sequential stream in reduce() returning the same output?

Solution:

The Javadoc for the Stream.reduce function with an identity argument says:

The identity value must be an identity for the accumulator function.
This means that for all t, accumulator.apply(identity, t) is equal to
t.

Which is clearly not the case here – "x".concat(anything) doesn’t equal anything. The only valid identity value here is "".

If you had tested the premise of the title of your question – by seeing what a non-parallel operation returns – you’d see that the answer to your title is “no” – because a non-parallel stream returns "xAEIOU" for your reduce operation.

If you change the identity value from "x" to "", then the answer would be “yes, there is such a guarantee, as your reduce function is associative and the constraints on the identity value are also satisfied.”

Even with your modified title, the answer is clear:

You are breaking the contract of the reduce function by providing, as the identity value, a value that is not an identity value for your reduce function. As such, since you are breaking the contract of the reduce methods, all guarantees are off.

It’s easy to create a case where it doesn’t hold; just make your list larger like Holger already pointed out:

List<String> list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
    list.add("A");
}
String s2 = list.parallelStream().reduce("x", String::concat, String::concat);
System.out.println(s2);
if (s2.length() != list.size() * 2) {
    System.out.println("Bad s2 size");
}

Flattening a collection within collection to get a single List<>

I’m quite new to using Java and was trying to flatten collections within collections using map to try and get a single List. However I don’t seem to be able to get this working. In order to reproduce this I’ve created quite a simple example of what I’m trying to do. Here is what I have so far:

ClassA

import java.util.List;

public class ClassA {
    private List<ClassB> listOfClassB;

    public ClassA(List<ClassB> listOfClassB) {
        this.listOfClassB = listOfClassB;
    }

    public List<ClassB> getListOfClassB() {
        return this.listOfClassB;
    }
}

ClassB

    public class ClassB {
    private String item;

    public ClassB(String item) {
        this.item = item;
    }

    public String getItem() {
        return item;
    }
}

Main

public class Main {
    public static void main(String[] args) {
        ClassB firstClass = new ClassB("A");
        ClassB secondClass = new ClassB("B");
        ClassB thirdClass = new ClassB("C");
        ClassB fourthClass = new ClassB("D");
        ArrayList<ClassB> firstClassList = new ArrayList<>();
        ArrayList<ClassB> secondClassList = new ArrayList<>();

        firstClassList.add(firstClass);
        firstClassList.add(secondClass);
        secondClassList.add(thirdClass);
        secondClassList.add(fourthClass);

        ArrayList<ClassA> classes = new ArrayList<>();
        classes.add(new ClassA(firstClassList));
        classes.add(new ClassA(secondClassList));

        List<List<String>> collect = classes.stream().map(c -> c.getListOfClassB().stream().map(ClassB::getItem).collect(Collectors.toList())).collect(Collectors.toList());
    }
}

As you can see on the bottom I am able to get List<List<String>> but what I’m looking to get is a List<String> of the items within ClassB. I’ve tried using a flatmap for this but I couldn’t get it working and was looking for some guidance.

Thanks in advance.

Solution:

Here is the flatmap example which works fine:

classes.stream().flatMap(aclass -> aclass.getListOfClassB().stream())
    .forEach(b -> System.out.println("Class B Item Name : "+b.getItem()));

It gives the following output:

Class B Item Name : A
Class B Item Name : B
Class B Item Name : C
Class B Item Name : D

and to get the exact answer:

List<String> collect2 = classes.stream().flatMap(aclass -> aclass.getListOfClassB().stream())
    .map(b -> b.getItem())
    .collect(Collectors.toList());

it gives me a list as follows:

collect2 : [A, B, C, D]

Why is this my attempt of spawning endless Threads stops at 4?

I have this simple code in Java 8:

class ThreadTest {
    void threadTest() {
        new Thread(this::threadTest).start();
        System.out.println(Thread.activeCount());
    }

    public static void main(String[] args) {
        new ThreadTest().threadTest();
    }
}

and I was pretty much expecting to see very large numbers getting printed. All I see in the console is:

4
4
4
4
4
4
4
4
4

I said maybe I am not able to see others for whatever reason and modified the code as below:

class ThreadTest {
    void threadTest() {
        new Thread(this::threadTest).start();
        if (Thread.activeCount() > 4) {
            System.out.println(Thread.activeCount());
        }
    }

    public static void main(String[] args) {
        new ThreadTest().threadTest();
    }
}

and now nothing gets printed.

What am I missing here?

Solution:

Once your thread reaches the end of its execution (in your case, the end of the threadTest() method), it is no longer an active thread.

If you add an excessively long Thread.sleep in your method, you will see this active thread count increase further.

Creating an immutable list from an existing list using streams

There is a list of Person objects.

List<Person> persons = generatePersons();

An unmodifiableList is created with it.

List<Person> unmodifiableList = Collections.unmodifiableList(persons);

I understand that unmodifiableList doesn’t support add/remove/set operations. At the same time it is not immutable since it has a reference to an existing modifiable list persons and whenever changes are made to the persons list, the changes are reflected in unmodifiableList too.

An immutable list is created this way.

List<Person> immutableList = Collections.unmodifiableList(new ArrayList<>(persons));

This creates an immutable list since a conversion constructor is being used. No add/remove/set ops can be performed on immutableList nor any change in the original list persons would reflect in immutableList.
Let’s make an assumption that Person objects are immutable too.

Now, I want to create these two lists using streams.

The first one, I have created using:

List<Person> unmodifiablePersons = persons.stream()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));

I am lost at creating equivalent of immutableList through streams.

How can I do that?

Edit:

I added a new Person object to the original list persons and printed the size of persons list and unmodifiablePersons. Both give me the same size.
So, changes are being reflected to unmodifiablePersonsand hence it is not immutable yet. Am I missing something here?

Edit 2

Silly. Should have gone through the docs. unmodifiablePersons is indeed an immutable list. Also, the new Person object was added before the unmodifiablePersons was created and hence the above observation. Super silly.

Solution:

Well in your first case someone has access to List<Person> unmodifiableList and can edit it, but when you collect no one has access to that List generated by Collectors.toList – so you are good.

What you are probably missing is that Collectors::toList will create a new List – which should really be obvious; and you wrap it into an unmodifiable one, thus the result of that is truly unmodifiable.

Also in java-10 there is special collector for that:

List<Integer> result = Arrays.asList(1, 2, 3, 4)
        .stream()
        .collect(Collectors.toUnmodifiableList());

This collector uses List::of internally – immutable collections added in java-9, thus for example they don’t support nulls.

Why can I collect a parallel stream to an arbitrarily large array but not a sequential stream?

From answering this question, I ran into a peculiar feature. The following code works as I assumed it would (the first two values within the existing array would be overridden):

Integer[] newArray = Stream.of(7, 8)
                           .parallel()
                           .toArray(i -> new Integer[] {1, 2, 3, 4, 5, 6});

System.out.println(Arrays.toString(newArray));

Output:

[7, 8, 3, 4, 5, 6]

However, attempting this with a sequential stream throws an IllegalStateException:

Integer[] newArray = Stream.of(7, 8)
                           .toArray(i -> new Integer[] {1, 2, 3, 4, 5, 6});

System.out.println(Arrays.toString(newArray));

Output:

Exception in thread "main" java.lang.IllegalStateException: Begin size 2 is not equal to fixed size 6
    at java.base/java.util.stream.Nodes$FixedNodeBuilder.begin(Nodes.java:1222)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:483)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:550)
    at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:517)
    at test/test.Test.main(Test.java:30)

I’m curious as to why the sequential stream does not overwrite elements of the array as the parallel stream does. I searched around a bit and was not able to find documentation regarding this, but I assume it exists somewhere.

Solution:

The generator function is required to produce “a new array of the desired type and the provided length.” If you don’t comply with the spec, behavior is undefined.

Java8 group a list of lists to map

I have a Model and a Property class with the following signatures:

public class Property {

    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Model {

    private List<Property> properties = new ArrayList<>();

    public List<Property> getProperties() {
        return properties;
    }
}

I want a Map<String, Set<Model>> from a List<Model> where the key would be the name from the Property class. How can I can I use java8 streams to group that list by its Properyes’ name? All Propertyes are unique by name.

It is possible to solve in a single stream or should I split it somehow or go for the classical solution?

Solution:

yourModels.stream()
          .flatMap(model -> model.getProperties().stream()
                  .map(property -> new AbstractMap.SimpleEntry<>(model, property.getName())))
          .collect(Collectors.groupingBy(
                Entry::getValue, 
                Collectors.mapping(
                    Entry::getKey, 
                    Collectors.toSet())));

How do I get my EC2 instance from EB to stop re-installing java 7?

So, basically I have an EC2 instance that was created by Elastic Beanstalk and about every week I have been SSHing into it to uninstall java 7 and install java 8. The elastic beanstalk environment is set up for Node.js, but I have a separate java program that needs to be instantiated. I tried putting commands to install java 8 in rc.local but that didn’t work at all. The file is now back to its default state. Is there somewhere where I can set up the instance to default to Java 8? Is there a way to just keep the installation that I install via SSH? Please help, my site keeps going down because of this and I’m not really sure what to do about it and google was not much help.

Solution:

.ebextensions is how I would do this: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions.html

.ebextensions run anytime an ec2 instance is created; so script the uninstall of java 7 and install of java 8, and you’re good to go.

More info…

It sounds like you understand that every now and again, EB automatically destroys your ec2 instance and replaces it. Often this is done on a weekly basis; take a look at the EB console -> Configuration -> Managed updates. You’ll probably find that it says

Managed updates: enabled

Weekly update window: [some date/time here]

This is a good thing. EB is automatically installing important security and other updates for you. When it does this, it may replace your ec2 instances. Which means: the ec2 instance that you manually installed java 8 on is destroyed, and a brand new machine is created. This brand new machine has the default java 7 installed on it…and now you again have to SSH manually into this new instance and install 8.

What you need is a script that will run on all new ec2 instances that EB decides to create for you; this script will follow the steps you are taking each time you manually SSH into your ec2 instance. Note that even if you turned off managed updates, it’s still possible (even likely) that ec2 instances get destroyed. This is a tenant of good infrastructure design; if an ec2 instance has a problem for any reason, your infrastructure should automatically in the background:

1.) Start a new ec2 instance

2.) Provision the new ec2 instance

3.) Reroute your website traffic to the new instance

4.) Decomission rid of the old/damaged ec2 instance

So, in an .ebextensions file, create a script that will uninstall java 7 and install java 8 and you’ll be all set.