Java List.contains (Objekt mit einem Feldwert von x)

199

Ich möchte überprüfen, ob a Listein Objekt enthält, das ein Feld mit einem bestimmten Wert hat. Jetzt könnte ich eine Schleife verwenden, um zu überprüfen, aber ich war neugierig, ob es etwas Code-effizienteres gibt.

Etwas wie;

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

Ich weiß, dass der obige Code nichts bewirkt, sondern nur grob demonstriert, was ich erreichen möchte.

Zur Verdeutlichung möchte ich keine einfache Schleife verwenden, da dieser Code derzeit in eine Schleife innerhalb einer Schleife innerhalb einer Schleife eingefügt wird. Aus Gründen der Lesbarkeit möchte ich diesen Schleifen nicht ständig Schleifen hinzufügen. Also fragte ich mich, ob es einfache (ish) Alternativen gab.

Rudi Kershaw
quelle
5
Da dies eine benutzerdefinierte Gleichheit ist, müssen Sie benutzerdefinierten Code schreiben.
Sotirios Delimanolis
Ihr angegebenes Ziel und Ihr Codebeispiel scheinen nicht übereinzustimmen. Möchten Sie die Objekte nur anhand eines Feldwerts vergleichen?
Duncan Jones
1
equals(Object)Methode Ihres benutzerdefinierten Objekts überschreiben ?
Josh M
1
for(Person p:list) if (p.getName().equals("John") return true; return false;Ich fürchte, Sie werden in Java keinen prägnanteren Weg finden.
MikeFHay
@ Rajdeep sorry, ich verstehe deine Frage nicht. p.equals(p) sollte immer wahr sein, also bin ich verwirrt, was Sie erreichen wollen. Wenn Sie eine neue Frage stellen , können Sie hoffentlich bessere Hilfe erhalten.
MikeFHay

Antworten:

266

Streams

Wenn Sie Java 8 verwenden, können Sie vielleicht Folgendes ausprobieren:

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

Alternativ können Sie auch Folgendes ausprobieren:

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

Diese Methode wird zurückgegeben, truewenn sie List<MyObject>ein MyObjectmit dem Namen enthält name. Wenn Sie eine Operation für jedes der MyObjects ausführen möchten getName().equals(name), können Sie Folgendes versuchen:

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

Wo orepräsentiert eine MyObjectInstanz.

Alternativ können Sie, wie aus den Kommentaren hervorgeht (Danke MK10), die folgende Stream#anyMatchMethode verwenden:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().equals(name));
}
Josh M.
quelle
1
Dann sollte das zweite Beispiel sein public boolean. Vielleicht möchten Sie auch verwenden, Objects.equals()falls o.getName()dies möglich ist null.
Eric Jablow
1
Ich bin nur ein paranoider Programmierer. Ich habe mich mit Projekten befasst, bei denen die Leute schnell und locker mit Nullen waren, daher neige ich dazu, defensiv zu sein. Es ist weitaus besser sicherzustellen, dass Elemente niemals null sind.
Eric Jablow
5
@EricJablow Vielleicht sollten Sie ALLE Antworten kommentieren , die keine nullÜberprüfungen durchführen, im Gegensatz zu nur einer (meiner).
Josh M
55
Noch kürzer und leichter zu verstehen: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10
3
Ich bin mit @ MK10 einverstanden, aber ich würde es java.util.Objectsfür einen nullsicheren Vergleich verwenden. return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Wenn Sie Objekte im Stream auf null prüfen möchten, können Sie .filter(Objects::nonNull)vor anyMatch ein hinzufügen.
Tobijdc
81

Sie haben zwei Möglichkeiten.

1. Die erste Wahl, die vorzuziehen ist, besteht darin, die Methode `equals ()` in Ihrer Object-Klasse zu überschreiben.

Angenommen, Sie haben diese Objektklasse:

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

Angenommen, Sie interessieren sich nur für den Namen des MyObjects, der eindeutig sein sollte. Wenn also zwei MyObjects denselben Namen haben, sollten sie als gleich angesehen werden. In diesem Fall möchten Sie die Methode `equals ()` (und auch die Methode `hashcode ()` überschreiben, damit die Namen verglichen werden, um die Gleichheit zu bestimmen.

Sobald Sie dies getan haben, können Sie überprüfen, ob eine Sammlung ein MyObject mit dem Namen "foo" enthält.

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

Dies ist jedoch möglicherweise keine Option für Sie, wenn:

  • Sie verwenden sowohl den Namen als auch den Speicherort, um die Gleichheit zu überprüfen. Sie möchten jedoch nur überprüfen, ob eine Sammlung MyObjects mit einem bestimmten Speicherort enthält. In diesem Fall haben Sie `equals ()` bereits überschrieben.
  • `MyObject` ist Teil einer API, die Sie nicht ändern können.

Wenn einer dieser Fälle der Fall ist, möchten Sie Option 2:

2. Schreiben Sie Ihre eigene Dienstprogrammmethode:

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

Alternativ können Sie ArrayList (oder eine andere Sammlung) erweitern und dann Ihre eigene Methode hinzufügen:

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

Leider gibt es keinen besseren Weg, um es zu umgehen.

James Dunn
quelle
Ich denke, Sie haben Klammern für Getter in if (o! = Null && o.getLocation.equals (location))
vergessen
25

Google Guava

Wenn Sie Guava verwenden , können Sie einen funktionalen Ansatz wählen und Folgendes tun

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

das sieht ein wenig wortreich aus. Das Prädikat ist jedoch ein Objekt und Sie können verschiedene Varianten für verschiedene Suchvorgänge bereitstellen. Beachten Sie, wie die Bibliothek selbst die Iteration der Sammlung und die Funktion, die Sie anwenden möchten, trennt. Sie müssen equals()für ein bestimmtes Verhalten nicht überschreiben .

Wie unten erwähnt, bietet das in Java 8 und höher integrierte Framework java.util.Stream etwas Ähnliches.

Brian Agnew
quelle
1
Alternativ können Sie verwenden Iterables.any(list, predicate), was praktisch das gleiche ist, und Sie können es stilistisch bevorzugen oder nicht.
MikeFHay
@ EricJablow Yup. :) Du könntest dir meine Lösung ansehen.
Josh M
25

So geht's mit Java 8+:

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

Collection.contains()wird implementiert, indem equals()jedes Objekt aufgerufen wird, bis eines zurückkehrt true.

Eine Möglichkeit, dies zu implementieren, besteht darin, zu überschreiben, equals()aber natürlich können Sie nur eine Gleichheit haben.

Frameworks wie Guava verwenden daher Prädikate. Mit Iterables.find(list, predicate)können Sie nach beliebigen Feldern suchen, indem Sie den Test in das Prädikat einfügen.

In anderen Sprachen, die auf der VM erstellt wurden, ist dies integriert. In Groovy schreiben Sie beispielsweise einfach:

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

Java 8 hat uns auch das Leben leichter gemacht:

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

Wenn Sie sich für solche Dinge interessieren, empfehle ich das Buch "Beyond Java". Es enthält viele Beispiele für die zahlreichen Mängel von Java und wie andere Sprachen besser abschneiden.

Aaron Digulla
quelle
Wenn ich also die Methode equals der benutzerdefinierten Objekte überschreibe, die der Liste hinzugefügt werden ... kann ich sie dazu bringen, nur true zurückzugeben, wenn bestimmte Klassenvariablen identisch sind?
Rudi Kershaw
1
Nein, die Idee dahinter equals()ist sehr begrenzt. Es bedeutet, nach "Objektidentität" zu suchen - was auch immer das für eine Klasse von Objekten bedeuten mag. Wenn Sie ein Flag hinzugefügt haben, welche Felder enthalten sein sollen, kann dies zu Problemen aller Art führen. Ich rate dringend davon ab.
Aaron Digulla
Liebe Groovy, also gibt uns das "neue" Java 8 Lambda wirklich noch nicht diese Coolness? def result = list.find{ it.name == 'John' }Oracle / JCP sollte wegen Kriegsverbrechen gegen Programmierer verklagt werden.
Ken
19

Binäre Suche

Mit Collections.binarySearch können Sie ein Element in Ihrer Liste durchsuchen (vorausgesetzt, die Liste ist sortiert):

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

Dies gibt eine negative Zahl zurück, wenn das Objekt nicht in der Sammlung vorhanden ist, oder es gibt die indexdes Objekts zurück. Damit können Sie nach Objekten mit unterschiedlichen Suchstrategien suchen.

Debojit Saikia
quelle
Dies ist eine großartige Lösung für kleine Projekte, die keine Guave verwenden möchten. Eine Frage in den Dokumenten zu binarySearch lautet jedoch: "Die Liste muss vor diesem Aufruf in aufsteigender Reihenfolge gemäß dem angegebenen Komparator sortiert werden. Wenn sie nicht sortiert ist, sind die Ergebnisse undefiniert." Aber in einigen Fällen scheint dies, je nachdem, was verglichen wird, möglicherweise nicht der Fall zu sein?
Chrismarx
und die negative Zahl gibt an, wo das Objekt eingefügt wird, wenn Sie es hinzufügen und neu sortieren.
Fiorentinoing
8

Karte

Sie können einen Hashmap<String, Object>mit einem der Werte als Schlüssel erstellen und dann prüfen, ob yourHashMap.keySet().contains(yourValue)true zurückgegeben wird.


quelle
+1 Obwohl es ein wenig Aufwand bedeutet, die Details zuerst in die Karte einzufügen, erhalten Sie eine konstante Zeitsuche. Ich bin überrascht, dass dies bisher noch niemand vorgeschlagen hat.
Rudi Kershaw
6

Eclipse-Sammlungen

Wenn Sie Eclipse-Sammlungen verwenden , können Sie die anySatisfy()Methode verwenden. Passen Sie Ihre Listin a an ListAdapteroder ändern Sie Ihre Listin eine, ListIterablewenn möglich.

ListIterable<MyObject> list = ...;

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

Wenn Sie solche Vorgänge häufig ausführen, ist es besser, eine Methode zu extrahieren, die antwortet, ob der Typ das Attribut hat.

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

Sie können das alternative Formular anySatisfyWith()zusammen mit einer Methodenreferenz verwenden.

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

Wenn Sie Ihre nicht Listin eine ändern können ListIterable, gehen Sie wie folgt vor ListAdapter.

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

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.

Craig P. Motlin
quelle
5

Predicate

Wenn Sie kein Java 8 oder keine Bibliothek verwenden, die Ihnen mehr Funktionen für den Umgang mit Sammlungen bietet, können Sie etwas implementieren, das wiederverwendbarer ist als Ihre Lösung.

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    
    }

Ich benutze so etwas, ich habe eine Prädikatschnittstelle und ich übergebe die Implementierung an meine util-Klasse.

Was ist der Vorteil, wenn ich das auf meine Weise mache? Sie haben eine Methode, die sich mit der Suche in einer beliebigen Typensammlung befasst. und Sie müssen keine separaten Methoden erstellen, wenn Sie nach verschiedenen Feldern suchen möchten. Alles, was Sie tun müssen, ist, ein anderes Prädikat bereitzustellen, das zerstört werden kann, sobald es nicht mehr nützlich ist.

Wenn Sie es verwenden möchten, müssen Sie lediglich die Methode aufrufen und Ihr Prädikat definieren

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

Hier ist eine Lösung mit 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);
        }
    });
}
Phan Van Linh
quelle
3

containsMethode verwendet equalsintern. Sie müssen also die equalsMethode für Ihre Klasse gemäß Ihren Anforderungen überschreiben .

Übrigens sieht das syntaktisch nicht korrekt aus:

new Object().setName("John")
Juned Ahsan
quelle
3
Es sei denn, der Setter kehrt zurück this.
Sotirios Delimanolis
Wenn ich also die Methode equals der benutzerdefinierten Objekte überschreibe, die der Liste hinzugefügt werden ... kann ich sie dazu bringen, nur true zurückzugeben, wenn bestimmte Klassenvariablen identisch sind?
Rudi Kershaw
@RudiKershaw Ja, das ist die Idee, Sie können true auf der Basis von einem / zwei / allen Feldern zurückgeben. Wenn Sie also der Meinung sind, dass es logisch korrekt ist zu sagen, dass zwei Objekte mit demselben Namen gleiche Objekte sind, geben Sie true zurück, indem Sie einfach die Namen zusammenfassen.
Juned Ahsan
1
Es ist wirklich nicht ratsam, equalsfür einen einzelnen Anwendungsfall zu überschreiben , es sei denn, dies ist für die Klasse im Allgemeinen sinnvoll. Sie wären viel besser dran, wenn Sie nur die Schleife schreiben würden.
MikeFHay
@ MikeFHay stimmte zu, deshalb erwähnte ich im Kommentar "Wenn Sie also glauben, dass es logisch korrekt ist zu sagen, dass zwei Objekte mit demselben Namen gleiche Objekte sind, dann geben Sie true zurück"
Juned Ahsan
1

Wenn Sie dies List.contains(Object with field value equal to x)wiederholt ausführen müssen , wäre eine einfache und effiziente Problemumgehung:

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

Dann List.contains(Object with field value equal to x)hätte das das gleiche Ergebnis wiefieldOfInterestValues.contains(x);

Magda
quelle
1

Sie können auch anyMatch()verwenden:

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

Trotz des JAVA 8 SDK gibt es viele Sammlungstools, mit denen Bibliotheken arbeiten können, zum Beispiel: 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 );
Pascha GR
quelle