Java List.contains (oggetto con valore di campo uguale a x)


199

Voglio verificare se a List contiene un oggetto che ha un campo con un certo valore. Ora, potevo usare un loop per scorrere e controllare, ma ero curioso di sapere se c'era qualcosa di più efficiente nel codice.

Qualcosa di simile a;

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

So che il codice sopra non fa nulla, è solo per dimostrare all'incirca ciò che sto cercando di ottenere.

Inoltre, solo per chiarire, il motivo per cui non voglio usare un semplice ciclo è perché questo codice attualmente andrà all'interno di un ciclo che si trova all'interno di un ciclo che è all'interno di un ciclo. Per leggibilità non voglio continuare ad aggiungere loop a questi loop. Quindi mi chiedevo se ci fossero alternative semplici (ish).


5
Poiché si tratta di uguaglianza personalizzata, dovrai scrivere un codice personalizzato.
Sotirios Delimanolis,

L'obiettivo dichiarato e l'esempio di codice non sembrano corrispondere. Vuoi confrontare gli oggetti solo sulla base di un valore di campo?
Duncan Jones,

1
Sostituire il equals(Object)metodo dell'oggetto personalizzato?
Josh M,

1
for(Person p:list) if (p.getName().equals("John") return true; return false;Temo che non troverai un modo più conciso in Java.
MikeFHay,

@Rajdeep scusa, non capisco la tua domanda. p.equals(p) dovrebbe essere sempre vero, quindi sono confuso su ciò che stai cercando di ottenere. Spero che se fai una nuova domanda puoi ottenere un aiuto migliore.
MikeFHay,

Risposte:


267

Flussi

Se stai usando Java 8, forse potresti provare qualcosa del genere:

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

O in alternativa, potresti provare qualcosa del genere:

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

Questo metodo restituirà truese List<MyObject>contiene a MyObjectcon il nome name. Se vuoi eseguire un'operazione su ognuna delle MyObjects getName().equals(name), allora puoi provare qualcosa del genere:

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

Dove orappresenta aMyObject un'istanza.

In alternativa, come suggeriscono i commenti (grazie MK10), è possibile utilizzare il Stream#anyMatchmetodo:

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

1
Quindi, il secondo esempio dovrebbe essere public boolean. Inoltre, potresti voler usare Objects.equals()nel caso in cui o.getName()sia possibile null.
Eric Jablow,

1
Sono solo un programmatore paranoico. Ho affrontato progetti in cui le persone erano veloci e libere di nullità, quindi tendo ad essere difensivo. Molto meglio assicurarsi che gli articoli non siano mai nulli.
Eric Jablow,

5
@EricJablow Forse dovresti commentare TUTTE le risposte che non eseguono i nullcontrolli, al contrario di una sola (la mia).
Josh M,

55
Ancora più breve e più facile da capire: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10

3
Sono d'accordo con @ MK10 ma lo java.util.Objectsuserei per il confronto nullsafe return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Se vuoi controllare gli oggetti nel flusso per null, puoi aggiungere un .filter(Objects::nonNull)prima di anyMatch.
tobijdc,

81

Hai due scelte.

1. La prima scelta, che è preferibile, è quella di sovrascrivere il metodo `equals ()` nella tua classe Object.

Diciamo, ad esempio, che hai questa classe Object:

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

Ora diciamo che ti interessa solo il nome di MyObject, che dovrebbe essere univoco, quindi se due `MyObject`s hanno lo stesso nome dovrebbero essere considerati uguali. In tal caso, si vorrebbe sovrascrivere il metodo `equals ()` (e anche il metodo `hashcode ()`) in modo da confrontare i nomi per determinare l'uguaglianza.

Una volta fatto questo, puoi verificare se una raccolta contiene un oggetto MyObject con il nome "pippo" in questo modo:

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

Tuttavia, questa potrebbe non essere un'opzione per te se:

  • Stai usando sia il nome che la posizione per verificare l'uguaglianza, ma vuoi solo verificare se una Collezione ha qualche `MyObject` con una certa posizione. In questo caso, hai già ignorato `equals ()`.
  • `MyObject` fa parte di un'API che non hai la libertà di cambiare.

Se uno di questi è il caso, ti consigliamo l'opzione 2:

2. Scrivi il tuo metodo di utilità:

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

In alternativa, puoi estendere ArrayList (o qualche altra raccolta) e quindi aggiungere il tuo metodo ad esso:

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

Sfortunatamente non esiste un modo migliore per aggirarlo.


Penso che hai dimenticato le parentesi per getter in if (o! = Null && o.getLocation.equals (location))
olszi

25

Google Guava

Se stai usando Guava , puoi adottare un approccio funzionale e procedere come segue

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

che sembra un po 'prolisso. Tuttavia, il predicato è un oggetto e puoi fornire diverse varianti per ricerche diverse. Nota come la libreria stessa separa l'iterazione della raccolta e la funzione che desideri applicare. Non devi scavalcareequals() per un comportamento particolare.

Come notato di seguito, il framework java.util.Stream integrato in Java 8 e versioni successive offre qualcosa di simile.


1
In alternativa puoi usare Iterables.any(list, predicate), che è praticamente lo stesso, e potresti preferirlo o meno stilisticamente.
MikeFHay,

@EricJablow Yup. :) Potresti guardare la mia soluzione.
Josh M,

25

Ecco come farlo usando Java 8+:

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

20

Collection.contains()viene implementato chiamando equals()ogni oggetto fino a quando uno non ritornatrue .

Quindi un modo per implementarlo è quello di sovrascrivere equals() ma, ovviamente, puoi avere solo uno uguale.

Framework come Guava quindi usano predicati per questo. ConIterables.find(list, predicate) , è possibile cercare campi arbitrari inserendo il test nel predicato.

Altre lingue integrate nella VM hanno questo integrato. In Groovy , ad esempio, scrivi semplicemente:

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

Java 8 ha anche semplificato tutte le nostre vite:

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

Se ti interessano cose del genere, ti suggerisco il libro "Oltre Java". Contiene molti esempi delle numerose carenze di Java e di come le altre lingue facciano meglio.


Quindi, se eseguo l'override del metodo equals degli oggetti personalizzati che vengono aggiunti all'elenco ... posso farlo restituire true se determinate variabili di classe sono uguali?
Rudi Kershaw,

1
No, l'idea alla base equals()è molto limitata. Significa verificare la "identità dell'oggetto", qualunque cosa ciò possa significare per una classe di oggetti. Se hai aggiunto una bandiera quali campi devono essere inclusi, ciò potrebbe causare problemi di ogni tipo. Lo sconsiglio vivamente.
Aaron Digulla,

Ti piacciono i film, quindi il "nuovo" Java 8 Lambda non ci dà ancora questo fresco? def result = list.find{ it.name == 'John' }Oracle / JCP dovrebbero essere citati in giudizio per crimini di guerra contro programmatori.
Ken,

19

Ricerca binaria

Puoi utilizzare Collections.binarySearch per cercare un elemento nell'elenco (supponendo che l'elenco sia ordinato):

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

che restituirà un numero negativo se l'oggetto non è presente nella raccolta oppure restituirà indexl'oggetto. Con questo puoi cercare oggetti con diverse strategie di ricerca.


Questa è un'ottima soluzione per i piccoli progetti che non vogliono usare la guava. Una domanda però, i documenti su binarySearch affermano che: "L'elenco deve essere ordinato in ordine crescente in base al comparatore specificato, prima di effettuare questa chiamata. Se non è ordinato, i risultati non sono definiti". Ma sembra in alcuni casi, a seconda di cosa viene confrontato, questo potrebbe non essere il caso?
chrismarx,

e il numero negativo rappresenta il punto in cui l'oggetto verrà inserito se lo si aggiunge e si riordina.
Fiorentino il

8

Carta geografica

È possibile creare uno Hashmap<String, Object>utilizzando uno dei valori come chiave e quindi vedere se yourHashMap.keySet().contains(yourValue)restituisce true.


+1 Sebbene significhi un po 'di spese generali nel mettere i dettagli nella mappa in primo luogo, si ottiene una ricerca del tempo costante. Sono sorpreso che nessuno l'abbia suggerito fino ad ora.
Rudi Kershaw,

6

Collezioni Eclipse

Se si utilizzano le raccolte Eclipse , è possibile utilizzare il anySatisfy()metodo Adatta il tuo Listin a ListAdaptero cambia il tuo Listin a ListIterablese possibile.

ListIterable<MyObject> list = ...;

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

Se eseguirai operazioni come questa frequentemente, è meglio estrarre un metodo che risponda se il tipo ha l'attributo.

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

È possibile utilizzare il modulo alternativo anySatisfyWith()insieme a un riferimento al metodo.

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

Se non riesci a cambiarlo Listin a ListIterable, ecco come lo useresti ListAdapter.

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

Nota: sono un committer per ollections Eclipse.


5

Predicate

Se non si utilizza Java 8 o una libreria che offre più funzionalità per gestire le raccolte, è possibile implementare qualcosa che può essere più riutilizzabile della soluzione.

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    
    }

sto usando qualcosa del genere, ho un'interfaccia predicata e sto passando l'implementazione alla mia classe util.

Qual è il vantaggio di fare questo a modo mio? hai un metodo che si occupa della ricerca in qualsiasi raccolta di tipi. e non è necessario creare metodi separati se si desidera cercare in campi diversi. tutto quello che devi fare è fornire un predicato diverso che può essere distrutto non appena non è più utile /

se vuoi usarlo, tutto ciò che devi fare è chiamare il metodo e definire il tuo predicato

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

4

Ecco una soluzione che utilizza 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);
        }
    });
}

3

containsil metodo utilizza equalsinternamente. Quindi devi sovrascrivere il equalsmetodo per la tua classe secondo le tue necessità.

A proposito, questo non sembra sintatticamente corretto:

new Object().setName("John")

3
A meno che il setter non ritorni this.
Sotirios Delimanolis,

Quindi, se eseguo l'override del metodo equals degli oggetti personalizzati che vengono aggiunti all'elenco ... posso farlo restituire true se determinate variabili di classe sono uguali?
Rudi Kershaw,

@RudiKershaw Sì, questa è l'idea, puoi restituire true sulla base di uno / due / tutti i campi. Quindi, se ritieni che sia logicamente corretto dire che due oggetti con lo stesso nome sono oggetti uguali, allora restituiscono true semplicemente combinando i nomi.
Juned Ahsan,

1
Non è davvero consigliabile eseguire l'override equalsper un singolo caso d'uso, a meno che non abbia senso per la classe in generale. Staresti molto meglio scrivendo il ciclo.
MikeFHay,

@MikeFHay concordato, ecco perché ho menzionato nel commento "Quindi, se credi che sia logicamente corretto dire che due oggetti con lo stesso nome sono oggetti uguali, allora ritorna vero"
Juned Ahsan,

1

Se è necessario eseguire questa operazione List.contains(Object with field value equal to x)ripetutamente, una soluzione semplice ed efficace sarebbe:

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

Quindi il risultato List.contains(Object with field value equal to x)sarebbe lo stesso difieldOfInterestValues.contains(x);


1

puoi anche anyMatch()usare:

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

0

Nonostante JAVA 8 SDK ci sono molte librerie di strumenti di raccolta che possono aiutarti a lavorare con, ad esempio: 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 );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.