251

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

就像是;

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

我知道上面的代码没有做任何事情,它只是为了大致展示我想要实现的目标。

另外,为了澄清,我不想使用简单循环的原因是因为此代码当前将进入循环内部的循环内部循环内部。为了可读性,我不想继续向这些循环添加循环。所以我想知道是否有任何简单的(ish)替代品。

4

13 回答 13

349

如果您使用的是 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带有名称的 a,则此方法将返回name。如果您想对每个MyObjects that执行操作getName().equals(name),那么您可以尝试这样的操作:

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

whereo代表一个MyObject实例。

或者,正如评论所建议的(感谢 MK10),您可以使用以下Stream#anyMatch方法:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().equals(name));
}
于 2013-09-17T14:15:18.040 回答
83

你有两个选择。

1. 第一个选择是可取的,是覆盖 Object 类中的 `equals()` 方法。

例如,假设您有这个 Object 类:

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

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

完成此操作后,您可以检查 Collection 是否包含名为“foo”的 MyObject,如下所示:

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

但是,如果出现以下情况,这可能不适合您:

  • 您同时使用名称和位置来检查是否相等,但您只想检查集合是否有任何具有特定位置的“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;
    }

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

于 2013-09-17T14:44:28.270 回答
39

这是使用 Java 8+ 的方法:

boolean isJohnAlive = list.stream().anyMatch(o -> o.getName().equals("John"));
于 2018-10-22T17:45:09.203 回答
26

谷歌番石榴

如果您使用的是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框架提供了类似的功能。

于 2013-09-17T14:07:18.690 回答
22

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

如果您关心这样的事情,我建议您阅读“Beyond Java”一书。它包含许多示例,说明 Java 的众多缺点以及其他语言如何做得更好。

于 2013-09-17T14:11:00.757 回答
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对象的。有了这个,您可以使用不同的搜索策略搜索对象。

于 2013-09-17T14:15:48.673 回答
8

地图

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

于 2017-02-10T11:19:58.633 回答
6

日食系列

如果您使用的是Eclipse Collections,则可以使用该anySatisfy()方法。如果可能的话,要么调整你List的,ListAdapter要么改变你ListListIterable

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 ollections 的提交者。

于 2013-09-17T18:53:02.303 回答
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 类。

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

如果你想使用它,你需要做的就是调用方法并定义你的谓词

CollectionUtil.find(list, new Predicate<MyObject>{
    public boolean contains(T item){
        return "John".equals(item.getName());
     }
});
于 2015-09-16T12:21:04.687 回答
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);
        }
    });
}
于 2017-11-21T02:55:33.220 回答
3

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

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

new Object().setName("John")
于 2013-09-17T14:06:27.253 回答
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);

于 2015-09-16T12:06:39.873 回答
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 );
于 2017-08-22T05:34:27.850 回答