1

I have a HashMap where the key is an Id and the value is and object. I have a separate list of Contacts. If the contacts list has an id that matches a key value in the HashMap, I want to remove the item from the HashMap. Whats the most efficent way to do this?

I tried the follwing but it didn't do anything:

List<C> contacts;

    Map<Integer, SurveyAnswer<?>> qaMapInit = qaList.stream()
            .collect(
                Collectors.toMap(SurveyAnswer::getSurveyAnswerPk, (qa) -> qa)
            );

qaMapInit.keySet().removeAll(contacts);

I'm using Java 8

2 Answers2

4
qaMapInit.keySet().removeAll(contacts);

isn't quite right.

The best you can do is

for (C contact: contacts) {
   qaMapInit.remove(contact.getId());
}
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
0

Caveat: Do not make actual use of my Answer here. In real work, I would go with the simple solution shown in correct Answer by Louis Wasserman.


Given that you were attempting to use streams, I did the same. Below are my results. Note: Usually streams can simplify long or convoluted conventional code. But this here is an example of the opposite. My stream-based code is less clear than Wasserman’s.

First, we establish an example class similar to yours. I use a Java 16+ record for brevity, but that is besides the point here.

record Person( Integer id , String name ) { }

For example data, I make an unmodifiable list of people having ID values of 1, 2, and 3.

List < Person > people = List.of(
        new Person( 1 , "Alice" ) ,
        new Person( 2 , "Bob" ) ,
        new Person( 3 , "Carol" )
);

And an example map of person’s ID to their survey answer.

Map < Integer, String > mapOfPersonIdToAnswer =
        new HashMap <>(
                Map.of(
                        1 , "Poodle" ,
                        3 , "Cockatiel" ,
                        4 , "Akita"
                ) );

Now comes our logic.

The main problem in the Question’s code is that you were asking the removeAll method to compare the C objects in your list of contacts. But you defined your map as having not the C objects themselves as keys but rather their nested Integer ID field numbers as keys. We need to extract the ID field value from all your contacts (your C objects, here, our Person objects).

So we establish a set of all the persons’ ID field value, a Set< Integer >. Generally best to default to immutability, so I return an unmodifiable set as provided by Collectors.toUnmodifiableSet.

Set < Integer > idsOfPeople = people.stream().map( person -> person.id() ).collect( Collectors.toUnmodifiableSet() );

Same line of code, adding commentary.

Set < Integer > idsOfPeople =
        people
                .stream()                                 // Make a stream of our `List` of `Person` objects.
                .map( person -> person.id() )             // For each `Person` object in our stream, produce the ID field value to produce a new stream of `Integer` objects.
                .collect(                                 // Gather the `Integer` objects from our second stream into a collection.
                        Collectors.toUnmodifiableSet()    // Produce an unmodifiable set into which we collect our `Integer` objects.
                );                                        // Return a `Set< Integer >`, a set of integer objects.

By the way… At first I thought to add a .distinct() call after the map call, in case we had duplicate Person objects in the list. But calling .distinct() is superfluous because a Set by definition eliminates duplicates.

Now on to removing entries from our map where the key of the entry is found to match a Person ID in our set idsOfPeople. The trick is that the Set interface has no method for removeAllEntriesWhereKeyIsFoundInSet. But there is an indirect route to accomplish that goal.

The Map#keySet method produces as special kind of set, a set that is a view onto the keys in the map, not a separate independent set. If we delete a key from that key-set, we also delete its entry from the map.

Bonus: The Set interface includes a removeAll method. If we call removeAll, while passing our idsOfPeople set, on our special key-set view, we effectively remove all the entries where entry key is found in idsOfPeople.

For more discussion, see Remove multiple keys from Map in efficient way?.

mapOfPersonIdToAnswer.keySet().removeAll( idsOfPeople );

Result when run. As expected, we are left with only a single entry in our map, the last entry, the one for ID 4 for which we have no such person in our people list.

mapOfPersonIdToAnswer = {4=Akita}

Not that I recommend it, but you could turn those two lines into a single-line.

mapOfPersonIdToAnswer.keySet().removeAll( people.stream().map( person -> person.id() ).collect( Collectors.toUnmodifiableSet() ) );

As I said above, this is not better than the Wasserman solution, but it was fun to explore.

Here is the entire code example.

package work.basil.mapping;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class App {
    public static void main ( String[] args ) {
        App app = new App();
        app.demo();
    }

    private void demo () {
        record Person( Integer id , String name ) { }
        List < Person > people = List.of(
                new Person( 1 , "Alice" ) ,
                new Person( 2 , "Bob" ) ,
                new Person( 3 , "Carol" )
        );


        Map < Integer, String > mapOfPersonIdToAnswer =
                new HashMap <>(
                        Map.of(
                                1 , "Poodle" ,
                                3 , "Cockatiel" ,
                                4 , "Akita"
                        ) );

        Set < Integer > idsOfPeople = people.stream().map( person -> person.id() ).collect( Collectors.toUnmodifiableSet() );
        mapOfPersonIdToAnswer.keySet().removeAll( idsOfPeople );

        System.out.println( "mapOfPersonIdToAnswer = " + mapOfPersonIdToAnswer );

    }
}
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154