Java List.contains(Object with field value equal to x)

I want to check whether a List contains an object that has a field with a certain value. Now, I could use a loop to go through and check, but I was curious if there was anything more code efficient.

Something like;

if(list.contains(new Object().setName("John"))){
    //Do some stuff
}

I know the above code doesn't do anything, it's just to demonstrate roughly what I am trying to achieve.

Also, just to clarify, the reason I don't want to use a simple loop is because this code will currently go inside a loop that is inside a loop which is inside a loop. For readability I don't want to keep adding loops to these loops. So I wondered if there were any simple(ish) alternatives.


Solution 1:

Streams

If you are using Java 8, perhaps you could try something like this:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().filter(o -> o.getName().equals(name)).findFirst().isPresent();
}

Or alternatively, you could try something like this:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().map(MyObject::getName).filter(name::equals).findFirst().isPresent();
}

This method will return true if the List<MyObject> contains a MyObject with the name name. If you want to perform an operation on each of the MyObjects that getName().equals(name), then you could try something like this:

public void perform(final List<MyObject> list, final String name){
    list.stream().filter(o -> o.getName().equals(name)).forEach(
            o -> {
                //...
            }
    );
}

Where o represents a MyObject instance.

Alternatively, as the comments suggest (Thanks MK10), you could use the Stream#anyMatch method:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().equals(name));
}

Solution 2:

You have two choices.

1. The first choice, which is preferable, is to override the `equals()` method in your Object class.

Let's say, for example, you have this Object class:

public class MyObject {
    private String name;
    private String location;
    //getters and setters
}

Now let's say you only care about the MyObject's name, that it should be unique so if two `MyObject`s have the same name they should be considered equal. In that case, you would want to override the `equals()` method (and also the `hashcode()` method) so that it compares the names to determine equality.

Once you've done this, you can check to see if a Collection contains a MyObject with the name "foo" by like so:

MyObject object = new MyObject();
object.setName("foo");
collection.contains(object);

However, this might not be an option for you if:

  • You are using both the name and location to check for equality, but you only want to check if a Collection has any `MyObject`s with a certain location. In this case, you've already overridden `equals()`.
  • `MyObject` is part of an API that you don't have liberty to change.

If either of these are the case, you'll want option 2:

2. Write your own utility method:

public static boolean containsLocation(Collection<MyObject> c, String location) {
    for(MyObject o : c) {
        if(o != null && o.getLocation.equals(location)) {
            return true;
        }
    }
    return false;
}

Alternatively, you could extend ArrayList (or some other collection) and then add your own method to it:

public boolean containsLocation(String location) {
    for(MyObject o : this) {
        if(o != null && o.getLocation.equals(location)) {
                return true;
            }
        }
        return false;
    }

Unfortunately there's not a better way around it.

Solution 3:

This is how to do it using Java 8+ :

boolean isJohnAlive = list.stream().anyMatch(o -> o.getName().equals("John"));

Solution 4:

Google Guava

If you're using Guava, you can take a functional approach and do the following

FluentIterable.from(list).find(new Predicate<MyObject>() {
   public boolean apply(MyObject input) {
      return "John".equals(input.getName());
   }
}).Any();

which looks a little verbose. However the predicate is an object and you can provide different variants for different searches. Note how the library itself separates the iteration of the collection and the function you wish to apply. You don't have to override equals() for a particular behaviour.

As noted below, the java.util.Stream framework built into Java 8 and later provides something similar.