Java List.contains(字段值等于x的对象)


199

我想检查是否List包含一个对象,该对象的字段具有特定值。现在,我可以使用循环进行检查,但是我很好奇是否还有任何更有效的代码。

就像是;

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

我知道上面的代码没有任何作用,只是为了大致演示我要实现的目标。

另外,为了澄清起见,我不想使用一个简单的循环的原因是因为此代码当前将进入一个循环,该循环位于一个循环内。为了提高可读性,我不想继续向这些循环添加循环。所以我想知道是否有任何简单的选择。


5
由于这是自定义相等,因此您必须编写自定义代码。
Sotirios Delimanolis 2013年

您声明的目标和代码示例似乎不匹配。您是否只想根据一个字段值比较对象?
邓肯·琼斯

1
覆盖equals(Object)自定义对象的方法?
乔什M

1
for(Person p:list) if (p.getName().equals("John") return true; return false;恐怕您在Java中找不到更简洁的方法。
MikeFHay 2013年

@Rajdeep对不起,我不明白你的问题。p.equals(p) 应该永远是对的,所以我很困惑您想要达到的目标。希望您提出新的问题可以得到更好的帮助。
MikeFHay

Answers:


266

如果您使用的是Java 8,则可以尝试执行以下操作:

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

或者,您可以尝试如下操作:

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

true如果中List<MyObject>包含MyObject名称为的,则此方法将返回name。如果你想在每一个执行操作MyObjects表示getName().equals(name),那么你可以尝试这样的事:

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

Where o代表一个MyObject实例。

或者,如注释所示(感谢MK10),可以使用以下Stream#anyMatch方法:

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

1
然后,第二个示例应该是public boolean。此外,您可能需要使用Objects.equals()的情况下o.getName()可以null
埃里克·贾布洛

1
我只是一个偏执的程序员。我所处理的项目中,人们在零零散散的情况下过得很快,所以我倾向于防守。最好确保项目永远不会为空。
Eric Jablow 2013年

5
@EricJablow也许您应该对所有不执行null检查的答案(而不是一个答案)进行评论。
乔什M

55
更短更容易理解: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10

3
我同意@ MK10,但我将java.util.Objects用于nullsafe比较return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);如果要检查流中的对象是否为null,则可以.filter(Objects::nonNull)在anyMatch之前添加一个。
tobijdc

81

您有两种选择。

1.最好的选择是重写Object类中的equals()方法。

举例来说,假设您拥有这个Object类:

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

现在,假设您只关心MyObject的名称,它应该是唯一的,因此,如果两个`MyObject`具有相同的名称,则应将它们视为相等。在这种情况下,您可能想覆盖`equals()`方法(以及`hashcode()`方法),以便它比较名称以确定相等性。

完成此操作后,您可以通过以下方式检查集合是否包含名称为“ foo”的MyObject:

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

但是,在以下情况下,这可能不是您的选择:

  • 您正在使用名称和位置来检查是否相等,但是您只想检查Collection是否具有位于特定位置的任何MyObject。在这种情况下,您已经覆盖了equals()。
  • MyObject是您无权更改的API的一部分。

如果出现以上两种情况,则需要选择2:

2.编写自己的实用程序方法:

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

另外,您可以扩展ArrayList(或其他集合),然后向其中添加自己的方法:

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

不幸的是,没有更好的方法来解决它。


我认为您忘记了if(o!= null && o.getLocation.equals(location))中的getter括号
olszi

25

谷歌番石榴

如果您使用的是Guava,则可以采用功能性方法并执行以下操作

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

看起来有点冗长。但是,谓词是一个对象,您可以为不同的搜索提供不同的变体。请注意,库本身如何将集合的迭代与您希望应用的函数分开。您不必equals()针对特定行为进行覆盖。

如下所述,Java 8和更高版本内置的java.util.Stream框架提供了类似的功能。


1
或者,您可以使用Iterables.any(list, predicate),实际上几乎是一样的,并且您可能在样式上也可能不喜欢它。
MikeFHay 2013年

@EricJablow是的。:)您可以看看我的解决方案。
乔什M

25

这是使用Java 8+的方法:

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

20

Collection.contains()通过调用equals()每个对象直到返回一个对象来实现true

因此,实现此目标的一种方法是覆盖,equals()但是您当然只能拥有一个等号。

因此,像Guava这样的框架为此使用谓词。使用Iterables.find(list, predicate),您可以通过将测试放入谓词中来搜索任意字段。

内置在VM之上的其他语言也已内置。例如,在Groovy中,您只需编写:

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

Java 8也使我们的生活更加轻松:

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

如果您关心这样的事情,我建议您读《超越Java》一书。它包含许多有关Java众多缺点以及其他语言如何做得更好的示例。


因此,如果我重写添加到列表中的自定义对象的equals方法...如果某些类变量相同,我能否让它返回true?
鲁迪·克肖

1
不,背后的想法equals()非常有限。这意味着要检查“对象身份”-对某些类的对象可能意味着什么。如果添加了一个标志,应包括哪些字段,则可能会引起各种麻烦。我强烈建议不要这样做。
亚伦·迪古拉

喜欢时髦,所以“新” Java 8 Lambda真的不给我们带来这种凉爽吗? def result = list.find{ it.name == 'John' }应该对Oracle / JCP提起针对程序员的战争罪起诉。
2016年

19

二元搜寻

您可以使用Collections.binarySearch来搜索列表中的元素(假设列表已排序):

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());
            }
        });

如果对象不存在于集合中,则它将返回负数,否则将返回index对象的。这样,您可以使用不同的搜索策略搜索对象。


对于不想使用番石榴的小型项目,这是一个很好的解决方案。但是,一个问题是,binarySearch上的文档指出:“在进行此调用之前,必须根据指定的比较器将列表按升序排序。如果不对列表进行排序,则结果是不确定的”。但是在某些情况下,取决于所比较的内容,情况可能并非如此?
chrismarx 2014年

负数表示添加和重新排序对象后将插入的位置。
fiorentinoing

8

地图

您可以Hashmap<String, Object>使用其中一个值作为键来创建一个,然后查看是否yourHashMap.keySet().contains(yourValue)返回true。


+1尽管这意味着将细节首先放入地图中会产生一些开销,但随后您将获得固定时间的查找。令我惊讶的是,到目前为止还没有人建议这样做。
鲁迪·克肖

6

Eclipse集合

如果您正在使用Eclipse Collections,则可以使用该anySatisfy()方法。如果可能,请改编List为a ListAdapterList改成a ListIterable

ListIterable<MyObject> list = ...;

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

如果您经常进行此类操作,则最好提取一个可以回答类型是否具有属性的方法。

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);
    }
}

您可以将替代形式anySatisfyWith()与方法引用一起使用。

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

如果您无法将其更改ListListIterable,则使用方法如下ListAdapter

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

注意:我是Eclipse推荐的提交者。


5

Predicate

如果您不使用Java 8或提供更多功能来处理集合的库,则可以实现比解决方案更可重用的功能。

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    
    }

我正在使用类似的东西,我有谓词接口,并将其实现传递给util类。

用我的方式这样做的好处是什么?您有一种方法可以处理任何类型集合中的搜索。如果您要按不同的字段进行搜索,则不必创建单独的方法。alll您需要做的是提供不同的谓词,一旦谓词不再有用就可以将其销毁。

如果您想使用它,那么您所要做的就是调用方法并定义您的谓词

CollectionUtil.find(list, new Predicate<MyObject>{
    public boolean contains(T item){
        return "John".equals(item.getName());
     }
});

4

这是使用番石榴的解决方案

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);
        }
    });
}

3

contains方法在equals内部使用。因此,您需要根据需要重写equals类的方法。

顺便说一下,这在语法上看起来不正确:

new Object().setName("John")

3
除非二传手回来this
Sotirios Delimanolis 2013年

因此,如果我重写添加到列表中的自定义对象的equals方法...如果某些类变量相同,我能否让它返回true?
鲁迪·克肖

@RudiKershaw是的,您可以在一个/两个/所有字段的基础上返回true。因此,如果您认为两个具有相同名称的对象是相等的对象在逻辑上是正确的,则只需将名称相互映射即可返回true。
Juned Ahsan 2013年

1
实际上,不建议equals为单个用例进行覆盖,除非通常对于该类而言是有意义的。编写循环可能会更好。
MikeFHay 2013年

@MikeFHay同意,这就是为什么我在评论中提到“因此,如果您认为两个具有相同名称的对象是相等的对象在逻辑上是正确的,然后返回true”
Juned Ahsan

1

如果您需要List.contains(Object with field value equal to x)重复执行此操作,则简单有效的解决方法是:

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

List.contains(Object with field value equal to x)结果将与fieldOfInterestValues.contains(x);


1

您还可以anyMatch()使用:

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

0

尽管有JAVA 8 SDK,还是有很多收集工具库可以帮助您使用,例如: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 );
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.