Java List.contains (Obiekt o wartości pola równej x)


199

Chcę sprawdzić, czy Listzawiera obiekt, który ma pole o określonej wartości. Teraz mogłem użyć pętli, aby przejść i sprawdzić, ale byłem ciekawy, czy jest coś bardziej wydajnego w kodzie.

Coś jak;

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

Wiem, że powyższy kod nic nie robi, to tylko przybliżenie tego, co próbuję osiągnąć.

Ponadto, aby wyjaśnić, powodem, dla którego nie chcę używać prostej pętli, jest to, że ten kod będzie teraz wchodził w pętlę, która znajduje się w pętli, która jest w pętli. Dla czytelności nie chcę ciągle dodawać pętli do tych pętli. Zastanawiałem się więc, czy istnieją jakieś proste (ish) alternatywy.


5
Ponieważ jest to niestandardowa równość, będziesz musiał napisać niestandardowy kod.
Sotirios Delimanolis,

Podany cel i przykład kodu wydają się nie pasować. Czy chcesz porównać obiekty tylko na podstawie jednej wartości pola?
Duncan Jones

1
Zastąpić equals(Object)metodę swojego obiektu niestandardowego?
Josh M

1
for(Person p:list) if (p.getName().equals("John") return true; return false;Obawiam się, że nie znajdziesz bardziej zwięzłego sposobu w Javie.
MikeFHay,

@Rajdeep przepraszam, nie rozumiem twojego pytania. p.equals(p) powinno zawsze być prawdą, więc nie rozumiem, co próbujesz osiągnąć. Mam nadzieję, że jeśli zadajesz nowe pytanie , możesz uzyskać lepszą pomoc.
MikeFHay

Odpowiedzi:


267

Strumienie

Jeśli używasz Java 8, być może możesz spróbować czegoś takiego:

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

Możesz też spróbować czegoś takiego:

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

Ta metoda zwróci, truejeśli List<MyObject>zawiera ona MyObjectz nazwą name. Jeśli chcesz wykonać operację na każdym MyObjectz którą getName().equals(name), to można spróbować czegoś takiego:

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

Gdzie oreprezentuje MyObjectinstancję.

Alternatywnie, jak sugerują komentarze (Dzięki MK10), możesz użyć Stream#anyMatchmetody:

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

1
Zatem drugim przykładem powinien być public boolean. Ponadto, warto użyć Objects.equals()w przypadku o.getName()może być null.
Eric Jablow,

1
Jestem tylko programistą paranoikiem. Zajmowałem się projektami, w których ludzie byli szybcy i luźni z zerami, więc mam skłonność do defensywy. O wiele lepiej, aby przedmioty nigdy nie były zerowe.
Eric Jabłoń

5
@EricJablow Być może powinieneś skomentować WSZYSTKIE odpowiedzi, które nie wykonują nullkontroli, a nie tylko jedną (moją).
Josh M

55
Jeszcze krótszy i łatwiejszy do zrozumienia: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10

3
Zgadzam się z @ MK10, ale chciałbym użyć java.util.Objectsdo porównania nullsafe return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Jeśli chcesz sprawdzić obiekty w strumieniu pod kątem null, możesz dodać znak .filter(Objects::nonNull)przed anyMatch.
tobijdc

81

Masz dwie możliwości.

1. Pierwszym wyborem, który jest lepszy, jest zastąpienie metody `equals ()` w klasie Object.

Załóżmy na przykład, że masz tę klasę Object:

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

Powiedzmy teraz, że zależy ci tylko na nazwie MyObject, aby była unikalna, więc jeśli dwa `MyObject`s mają taką samą nazwę, powinny być uważane za równe. W takim przypadku należy zastąpić metodę `equals ()` (a także metodę `hashcode ()`), aby porównała nazwy w celu ustalenia równości.

Po wykonaniu tej czynności możesz sprawdzić, czy kolekcja zawiera obiekt MyObject o nazwie „foo”, w ten sposób:

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

Jednak może to nie być dla Ciebie opcja, jeśli:

  • Używasz zarówno nazwy, jak i lokalizacji, aby sprawdzić równość, ale chcesz tylko sprawdzić, czy w kolekcji znajduje się jakikolwiek obiekt „MyObject” o określonej lokalizacji. W tym przypadku już zastąpiłeś `equals ()`.
  • „MyObject” jest częścią interfejsu API, którego zmiany nie można zmieniać.

Jeśli tak jest w obu przypadkach, będziesz potrzebować opcji 2:

2. Napisz własną metodę użyteczności:

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

Alternatywnie możesz rozszerzyć ArrayList (lub inną kolekcję), a następnie dodać do niej własną metodę:

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

Niestety nie ma lepszego sposobu na obejście tego.


Myślę, że zapomniałeś nawiasów dla gettera w if (o! = Null && o.getLocation.equals (lokalizacja))
olszi

25

Google Guava

Jeśli używasz Guawy , możesz zastosować funkcjonalne podejście i wykonać następujące czynności

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

co wygląda na trochę gadatliwe. Jednak predykat jest obiektem i możesz podać różne warianty dla różnych wyszukiwań. Zwróć uwagę, jak sama biblioteka oddziela iterację kolekcji i funkcję, którą chcesz zastosować. Nie musisz przesłonićequals() określonego zachowania.

Jak zauważono poniżej, framework java.util.Stream wbudowany w Javę 8 i później zapewnia coś podobnego.


1
Alternatywnie możesz użyć Iterables.any(list, predicate), który jest praktycznie taki sam, i możesz preferować stylistycznie.
MikeFHay,

@EricJablow Yup. :) Możesz spojrzeć na moje rozwiązanie.
Josh M

25

Oto jak to zrobić za pomocą Java 8+:

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

20

Collection.contains()jest implementowany przez wywołanie equals()każdego obiektu, dopóki jeden nie powróci true.

Tak więc jednym ze sposobów na wdrożenie tego jest zastąpienie, equals()ale oczywiście można mieć tylko jeden równy.

Dlatego też struktury takie jak Guava używają do tego predykatów. ZIterables.find(list, predicate) można wyszukiwać dowolne pola, umieszczając test w predykacie.

Inne języki zbudowane na maszynie wirtualnej mają tę wbudowaną funkcję. Na przykład w Groovy po prostu piszesz:

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

Java 8 także ułatwiła nam życie:

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

Jeśli zależy Ci na takich rzeczach, sugeruję książkę „Beyond Java”. Zawiera wiele przykładów licznych wad Java i lepszego radzenia sobie z innymi językami.


Tak więc, jeśli zastąpię metodę równości niestandardowych obiektów dodawanych do listy ... czy mogę sprawić, aby zwracała wartość true, jeśli niektóre zmienne klas są takie same?
Rudi Kershaw,

1
Nie, pomysł equals()jest bardzo ograniczony. Oznacza to sprawdzenie „tożsamości obiektu” - cokolwiek to może oznaczać dla niektórych klas obiektów. Jeśli dodasz flagę, które pola powinny zostać uwzględnione, może to powodować różnego rodzaju problemy. Zdecydowanie odradzam to.
Aaron Digulla,

Uwielbiasz groovy, więc „nowa” Java 8 Lambda naprawdę nie daje nam jeszcze takiego spokoju? def result = list.find{ it.name == 'John' }Oracle / JCP powinny być pozwane za zbrodnie wojenne przeciwko programistom.
ken

19

Wyszukiwanie binarne

Możesz użyć Collections.binarySearch, aby wyszukać element na liście (zakładając, że lista jest posortowana):

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

który zwróci liczbę ujemną, jeśli obiekt nie będzie obecny w kolekcji, lub zwróci indexobiekt. Dzięki temu możesz wyszukiwać obiekty o różnych strategiach wyszukiwania.


To świetne rozwiązanie dla małych projektów, które nie chcą używać guawy. Jedno pytanie, dokumentacja binarySearch stwierdza, że: „Przed wykonaniem tego wywołania lista musi zostać posortowana w porządku rosnącym zgodnie z określonym komparatorem. Jeśli nie zostanie posortowana, wyniki są niezdefiniowane”. Ale w niektórych przypadkach, w zależności od tego, co jest porównywane, może tak nie być?
chrismarx

a liczba ujemna oznacza, gdzie obiekt zostanie wstawiony, jeśli dodasz go i ponownie posortujesz.
fiorentinoing,

8

Mapa

Możesz utworzyć Hashmap<String, Object>jedną z wartości jako klucz, a następnie sprawdzić, czy yourHashMap.keySet().contains(yourValue)zwraca true.


+1 Mimo, że oznacza to trochę narzutu przy umieszczaniu szczegółów na mapie w pierwszej kolejności, wówczas uzyskuje się ciągłe wyszukiwanie czasu. Dziwi mnie, że nikt dotąd tego nie sugerował.
Rudi Kershaw,

6

Kolekcje Eclipse

Jeśli korzystasz z kolekcji Eclipse , możesz użyć tej anySatisfy()metody. Dostosuj swój Listw ListAdapterlub zmień Listw na, ListIterablejeśli to możliwe.

ListIterable<MyObject> list = ...;

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

Jeśli często będziesz wykonywać takie operacje, lepiej wyodrębnić metodę, która odpowiada, czy typ ma atrybut.

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

Możesz użyć alternatywnego formularza anySatisfyWith()wraz z odwołaniem do metody.

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

Jeśli nie możesz zmienić swojego Listna ListIterable, oto sposób, w jaki chcesz go użyć ListAdapter.

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

Uwaga: jestem zleceniodawcą kolekcji Eclipse.


5

Predicate

Jeśli nie używasz Java 8 lub biblioteki, która daje więcej funkcji do obsługi kolekcji, możesz zaimplementować coś, co może być bardziej przydatne niż twoje rozwiązanie.

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    
    }

używam czegoś takiego, mam interfejs predykatu i przekazuję jego implementację do mojej klasy util.

Jaka jest zaleta robienia tego na mój sposób? masz jedną metodę, która zajmuje się wyszukiwaniem w kolekcji dowolnego typu. i nie musisz tworzyć osobnych metod, jeśli chcesz wyszukiwać według innego pola. wszystko, co musisz zrobić, to podać inny predykat, który może zostać zniszczony, gdy tylko przestanie być przydatny /

jeśli chcesz go użyć, wszystko, co musisz zrobić, to wywołać metodę i zdefiniować predykat

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

4

Oto rozwiązanie wykorzystujące 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

containsmetoda wykorzystuje equalswewnętrznie. Musisz więc zastąpić equalsmetodę dla swojej klasy zgodnie z potrzebami.

Przy okazji nie wygląda to na poprawną składniowo:

new Object().setName("John")

3
Chyba że Seter powróci this.
Sotirios Delimanolis,

Tak więc, jeśli zastąpię metodę równości niestandardowych obiektów dodawanych do listy ... czy mogę sprawić, aby zwracała wartość true, jeśli niektóre zmienne klas są takie same?
Rudi Kershaw,

@RudiKershaw Tak, to jest pomysł, możesz zwrócić wartość true na podstawie jednego / dwóch / wszystkich pól. Jeśli więc uważasz, że logicznie poprawne jest stwierdzenie, że dwa obiekty o tej samej nazwie są obiektami równymi, zwróć wartość true, po prostu łącząc nazwy.
Juned Ahsan,

1
Naprawdę nie zaleca się zastępowania equalsdla pojedynczego przypadku użycia, chyba że ma to sens dla klasy w ogóle. Lepiej byłoby po prostu napisać pętlę.
MikeFHay

@MikeFHay zgodził się, dlatego wspomniałem w komentarzu „Więc jeśli uważasz, że logicznie poprawne jest twierdzenie, że dwa obiekty o tej samej nazwie są jednakowymi obiektami, to zwróć prawdę”
Juned Ahsan,

1

Jeśli musisz to List.contains(Object with field value equal to x)powtarzać wielokrotnie, prostym i wydajnym obejściem byłoby:

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

Wówczas List.contains(Object with field value equal to x)otrzymalibyśmy taki sam wynik jakfieldOfInterestValues.contains(x);


1

możesz również anyMatch()użyć:

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

0

Pomimo JAVA 8 SDK istnieje wiele bibliotek narzędzi do zbierania, które mogą pomóc w pracy, na przykład: 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 );
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.