Update Set<Person> if Person exists, or add them if they don't

Viewed 213

Let's say I have a very simple class, Person, which contains a name and address:

public class Person {
    String name;
    String address;

    public Person(final String name,
                  final String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

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

    public String address() {
        return address;
    }

    public void setAddress(final String newAddress) {
        this.address = newAddress;
    }
}

I now have a Set containing all of the known Person objects, and I want to update the address of one of the persons based only on their name. For that I have the following:

persons.stream()
       .filter(person -> personToUpdate.getName().equalsIgnoreCase(person.getName()))
       .forEach(person -> {
                person.setAddress(personToUpdate.getAddress());
        });

However, my problem is when I have a new person altogether who is not in the Set. How do I check the Set to see if the person exists, and if so, update their address. But if they don't exist, add them to the list. I know this is simple, but for whatever reason I just cannot think how to achieve this right now. I do not want to go down the road of creating a List of all the names, comparing the new name, if they're in the list etc etc etc. I'd rather keep it as concise as possible.

EDIT: Names will be unique!

5 Answers

Unless you need a one-liner, you can simply take advantage of the of the Optionals:

Optional<Person> person = persons.stream()
        .filter(p -> personToUpdate.getName().equalsIgnoreCase(p.getName()))
        .findFirst();

if (person.isPresent()) {
    person.get().setAddress(personToUpdate.getAddress());
} else {
    persons.add(personToUpdate);
}

If you're using Java 9+, this can look even better:

persons.stream()
        .filter(p -> personToUpdate.getName().equalsIgnoreCase(p.getName()))
        .findFirst()
        .ifPresentOrElse(
            p -> p.setAddress(personToUpdate.getAddress()),
            () -> persons.add(personToUpdate)
        );

This of course assumes that names are unique (as you expect to update one entry).

You may do it like so by using a Map.

Map<String, Person> personMap = persons.stream()
    .collect(Collectors.toMap(Person::getName, Function.identity()));
personMap.merge(personToUpdate.getName(), personToUpdate, 
    (p1, p2) -> new Person(p1.getName(), p2.address()));

There are a few easy ways to do this (I'm assuming that name uniquely identifies a Person, and two Person objects with the same name are considered equal)

  1. Implement equals and hashcode, and do a contains call before your stream - if it returns false, it's not in the set
  2. Use a Map instead of a Set, where the key can be easily used to lookup a person by an attribute (like name, which seems to be your primary key here).

Side note - you should implement equals and hashcode anyways - especially if you're putting these objects into a Set or Map. By implementing equals and hashcode to only consider name, you will guarantee uniqueness of name within the set (right now it's not guaranteed)

Assuming that (1) your Person objects are intended to only be compared using reference equality (and thus that object identity needs to be preserved), and (2) multiple Person objects might match a given name, you'll need to handle these cases manually:

public static void updatePersonAddress(String name, String address, Set<Person> persons) {
    // Find all the matching persons, if any.
    List<Person> matched = new ArrayList<>();
    for (Person person : persons) {
        if (person.getName().equalsIgnoreCase(name)) {
            matched.append(person);
        }
    }

    // If zero persons matched, create a new Person to match,
    // and add the new person to the set. Then we're done!
    if (matched.isEmpty()) {
        persons.add(new Person(name, address));
        return;
    }

    // Otherwise, update the Person objects (which are still in the
    // persons Set) to include the new address.
    for (Person person : matched) {
        person.setAddress(address);
    }
}

If names are unique, this simplifies to:

public static void updatePersonAddress(String name, String address, Set<Person> persons) {
    for (Person person : persons) {
        if (person.getName().equalsIgnoreCase(name)) {
            person.setAddress(address);
            // If we've found one that matches, then we know there
            // won't be any more, so we can be done now.
            return;
        }
    }
    // If we made it through the loop without returning, then it means
    // that no Person in the Set matched. So add one that does.
    persons.add(new Person(name, address));
}

You can use persons.contains(person). However you will have to override hashcode() and equals() methods

Related