289

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.

Rudi Kershaw
  • 12,332
  • 7
  • 52
  • 77
  • Your stated goal and your code example don't seem to match. Do you want to compare the objects only on the basis of one field value? – Duncan Jones Sep 17 '13 at 14:06
  • 1
    Override the `equals(Object)` method of your custom object? – Josh M Sep 17 '13 at 14:06
  • 1
    `for(Person p:list) if (p.getName().equals("John") return true; return false;` You won't find a more concise way in Java I'm afraid. – MikeFHay Sep 17 '13 at 15:20
  • @Rajdeep sorry, I don't understand your question. `p.equals(p)` _should_ always be true, so I'm confused what you're trying to achieve. Hopefully if you [ask a new question](http://stackoverflow.com/questions/ask) you can get better help. – MikeFHay Feb 24 '15 at 16:03

14 Answers14

391

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 -> name.equals(o.getName()));
}
oikonomopo
  • 4,025
  • 7
  • 44
  • 73
Josh M
  • 11,611
  • 7
  • 39
  • 49
  • 2
    Then, the second example should be `public boolean`. Also, you might want to use `Objects.equals()` in case `o.getName()` can be `null`. – Eric Jablow Sep 17 '13 at 14:28
  • @EricJablow Right, I just copied and pasted after writing the `perform` method. Also, there's a fairly large chance that it will never be `null`. Have you noticed that no other answers perform a `null` check? Or are you just pointing it out to me? – Josh M Sep 17 '13 at 14:34
  • 1
    I'm just a paranoid programmer. I've dealt with projects where people were fast and loose with nulls, so I tend to be defensive. Far better to ensure items not be null, ever. – Eric Jablow Sep 17 '13 at 16:03
  • 5
    @EricJablow Perhaps you should comment on **ALL** of the answers that don't perform `null` checks, as opposed to just one (mine). – Josh M Sep 17 '13 at 20:53
  • The first one is more flexible. You can return the found object with the name if you replace `isPresent()` with `.orElse(null)` – Mark Jeronimus Apr 26 '16 at 18:03
  • 79
    Even shorter and easier to understand: ```return list.stream().anyMatch(o -> o.getName().equals(name));``` – MK10 Sep 22 '16 at 16:10
  • 4
    I aggree with @MK10 but i would use `java.util.Objects` for nullsafe comparison `return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);` If you want to check objects in stream for null, you can add a `.filter(Objects::nonNull)` before anyMatch. – tobijdc Sep 26 '16 at 12:57
  • Can you pls explain me what type of magic in that :) ? How can the void method "perfom" return something? – karlihnos Dec 06 '17 at 15:35
  • 1
    I can't believe this answer has so many points. Stream.anyMatch method does the work – GabrielBB Oct 22 '18 at 17:47
  • I copied this answer and even my static linter realized I should just replace it with `anyMatch`. I wonder if the correct answer will ever reach the top, given it has just 20 votes in 2020. – Noumenon Feb 17 '20 at 22:58
  • If you wanted to do "contains()" to see if an object is in a list, but wanted to skip checking one particular field (like the date modified of a file), how would you do that? – Collin Feb 16 '22 at 22:31
  • @MK10 how check match for 2 conditions for objects stream, im just want to validate if stream contains 2 object tha each fill one of the 2 confitions, actually Im doing 2 stream validation, but its posible to do in just 1 – qleoz12 Dec 26 '22 at 21:44
85

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.

James Dunn
  • 8,064
  • 13
  • 53
  • 87
  • I think you forgot brackets for getter in if(o != null && o.getLocation.equals(location)) – op_ Feb 06 '20 at 13:33
49

This is how to do it using Java 8+ :

boolean isJohnAlive = list.stream().anyMatch(o -> "John".equals(o.getName());
GabrielBB
  • 2,479
  • 1
  • 35
  • 49
  • 4
    Tried the filter based answer by Josh above, but Intellij also suggested your anyMatch answer. Great! – Thyag Sep 09 '20 at 16:54
  • 4
    Please flip the string equality check to avoid a potential null pointer exception `"John".equals(o.getName())` – B Thuy Nov 25 '21 at 10:03
26

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • 1
    Alternatively you could use `Iterables.any(list, predicate)`, which is practically the same, and you may or may not prefer it stylistically. – MikeFHay Sep 17 '13 at 14:12
  • @EricJablow Yup. :) You could look at my solution. – Josh M Sep 17 '13 at 14:16
22

Collection.contains() is implemented by calling equals() on each object until one returns true.

So one way to implement this is to override equals() but of course, you can only have one equals.

Frameworks like Guava therefore use predicates for this. With Iterables.find(list, predicate), you can search for arbitrary fields by putting the test into the predicate.

Other languages built on top of the VM have this built in. In Groovy, for example, you simply write:

def result = list.find{ it.name == 'John' }

Java 8 made all our lives easier, too:

List<Foo> result = list.stream()
    .filter(it -> "John".equals(it.getName())
    .collect(Collectors.toList());

If you care about things like this, I suggest the book "Beyond Java". It contains many examples for the numerous shortcomings of Java and how other languages do better.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • So, if I override the equals method of the custom Objects being added to the list... can I get it to just return true if certain class variables are the same? – Rudi Kershaw Sep 17 '13 at 14:12
  • 1
    No, the idea behind `equals()` is very limited. It means to check for "object identity" - whatever that might mean for some class of objects. If you added a flag which fields should be included, that could cause all kinds of trouble. I strongly advise against it. – Aaron Digulla Sep 17 '13 at 14:20
  • Love groovy, so the "new" Java 8 Lambda really don't give us this coolness still? `def result = list.find{ it.name == 'John' }` Oracle/JCP should be sued for war crimes against programmers. – ken Jul 08 '16 at 12:58
19

Binary Search

You can use Collections.binarySearch to search an element in your list (assuming the list is sorted):

Collections.binarySearch(list, new YourObject("a1", "b",
                "c"), new Comparator<YourObject>() {

            @Override
            public int compare(YourObject o1, YourObject o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

which will return a negative number if the object is not present in the collection or else it will return the index of the object. With this you can search for objects with different searching strategies.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Debojit Saikia
  • 10,532
  • 3
  • 35
  • 46
  • This is a great solution for small projects that don't want to use guava. One question though, the docs on binarySearch state that: "The list must be sorted into ascending order according to the specified comparator, prior to making this call. If it is not sorted, the results are undefined". But in seems in some cases, depending on what's being compared, this might not be the case? – chrismarx Sep 04 '14 at 14:29
  • and the negative number represents where the object will be inserted if you add it and re-sort. – fiorentinoing Nov 17 '16 at 13:35
8

Map

You could create a Hashmap<String, Object> using one of the values as a key, and then seeing if yourHashMap.keySet().contains(yourValue) returns true.

  • +1 Although it means a little overhead in putting the details into the map in the first place you then get constant time lookup. I'm surprised no one has suggested this until now. – Rudi Kershaw Feb 10 '17 at 13:19
6

Eclipse Collections

If you're using Eclipse Collections, you can use the anySatisfy() method. Either adapt your List in a ListAdapter or change your List into a ListIterable if possible.

ListIterable<MyObject> list = ...;

boolean result =
    list.anySatisfy(myObject -> myObject.getName().equals("John"));

If you'll do operations like this frequently, it's better to extract a method which answers whether the type has the attribute.

public class MyObject
{
    private final String name;

    public MyObject(String name)
    {
        this.name = name;
    }

    public boolean named(String name)
    {
        return Objects.equals(this.name, name);
    }
}

You can use the alternate form anySatisfyWith() together with a method reference.

boolean result = list.anySatisfyWith(MyObject::named, "John");

If you cannot change your List into a ListIterable, here's how you'd use ListAdapter.

boolean result = 
    ListAdapter.adapt(list).anySatisfyWith(MyObject::named, "John");

Note: I am a committer for Eclipse ollections.

Craig P. Motlin
  • 26,452
  • 17
  • 99
  • 126
5

Predicate

If you dont use Java 8, or library which gives you more functionality for dealing with collections, you could implement something which can be more reusable than your solution.

interface Predicate<T>{
        boolean contains(T item);
    }

    static class CollectionUtil{

        public static <T> T find(final Collection<T> collection,final  Predicate<T> predicate){
            for (T item : collection){
                if (predicate.contains(item)){
                    return item;
                }
            }
            return null;
        }
    // and many more methods to deal with collection    
    }

i'm using something like that, i have predicate interface, and i'm passing it implementation to my util class.

What is advantage of doing this in my way? you have one method which deals with searching in any type collection. and you dont have to create separate methods if you want to search by different field. alll what you need to do is provide different predicate which can be destroyed as soon as it no longer usefull/

if you want to use it, all what you need to do is call method and define tyour predicate

CollectionUtil.find(list, new Predicate<MyObject>{
    public boolean contains(T item){
        return "John".equals(item.getName());
     }
});
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
user902383
  • 8,420
  • 8
  • 43
  • 63
4

Here is a solution using Guava

private boolean checkUserListContainName(List<User> userList, final String targetName){

    return FluentIterable.from(userList).anyMatch(new Predicate<User>() {
        @Override
        public boolean apply(@Nullable User input) {
            return input.getName().equals(targetName);
        }
    });
}
Linh
  • 57,942
  • 23
  • 262
  • 279
3

contains method uses equals internally. So you need to override the equals method for your class as per your need.

Btw this does not look syntatically correct:

new Object().setName("John")
Juned Ahsan
  • 67,789
  • 12
  • 98
  • 136
  • 3
    Unless the setter returns `this`. – Sotirios Delimanolis Sep 17 '13 at 14:09
  • So, if I override the equals method of the custom Objects being added to the list... can I get it to just return true if certain class variables are the same? – Rudi Kershaw Sep 17 '13 at 14:16
  • @RudiKershaw Yes that is the idea, you can return true on the basis of one/two/all fields. So if you believe it is logically correct to say that two objects with same name are equal objects then return true by just comapring the names. – Juned Ahsan Sep 17 '13 at 14:18
  • 2
    It's really not advisable to override `equals` for a single use case, unless it makes sense for the class in general. You'd be much better off just writing the loop. – MikeFHay Sep 17 '13 at 15:05
  • @MikeFHay agreed, thats why i mentioned in the comment "So if you believe it is logically correct to say that two objects with same name are equal objects then return true" – Juned Ahsan Sep 17 '13 at 15:15
1

If you need to perform this List.contains(Object with field value equal to x) repeatedly, a simple and efficient workaround would be:

List<field obj type> fieldOfInterestValues = new ArrayList<field obj type>;
for(Object obj : List) {
    fieldOfInterestValues.add(obj.getFieldOfInterest());
}

Then the List.contains(Object with field value equal to x) would be have the same result as fieldOfInterestValues.contains(x);

durron597
  • 31,968
  • 17
  • 99
  • 158
Magda
  • 211
  • 3
  • 5
0

Despite JAVA 8 SDK there is a lot of collection tools libraries can help you to work with, for instance: http://commons.apache.org/proper/commons-collections/

Predicate condition = new Predicate() {
   boolean evaluate(Object obj) {
        return ((Sample)obj).myField.equals("myVal");
   }
};
List result = CollectionUtils.select( list, condition );
Pasha
  • 1,534
  • 15
  • 27
-1

Try this:

list.stream().filter(req -> 
req.getName().equals("John")).findFirst().isPresent()
Jan Rozenbajgier
  • 534
  • 4
  • 13