Java List.contains (Alan değeri x'e eşit olan nesne)


201

Ben Listbelirli bir değeri olan bir alana sahip bir nesne içerip içermediğini kontrol etmek istiyorum . Şimdi, geçmek ve kontrol etmek için bir döngü kullanabilirsiniz, ancak daha kod verimli bir şey olup olmadığını merak ettim.

Gibi bir şey;

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

Yukarıdaki kod hiçbir şey yapmadığını biliyorum, sadece kabaca ne elde etmeye çalışıyorum göstermek için.

Ayrıca, sadece açıklığa kavuşturmak için, basit bir döngü kullanmak istemememin nedeni, bu kod şu anda bir döngü içinde bir döngü içinde bir döngü içine girecek olmasıdır. Okunabilirlik için bu döngülere döngü eklemeye devam etmek istemiyorum. Bu yüzden basit (ish) alternatifler olup olmadığını merak ettim.


5
Bu özel eşitlik olduğundan, özel kod yazmanız gerekir.
Sotirios Delimanolis

Belirttiğiniz hedef ve kod örneğiniz uyuşmuyor. Nesneleri yalnızca bir alan değeri temelinde karşılaştırmak ister misiniz?
Duncan Jones

1
equals(Object)Özel nesnenizin yöntemi geçersiz kılsın mı?
Josh M

1
for(Person p:list) if (p.getName().equals("John") return true; return false;Korkarım Java'da daha özlü bir yol bulamazsınız.
MikeFHay

@Rajdeep üzgünüm, sorunuzu anlamıyorum. p.equals(p) her zaman doğru olmalı , bu yüzden elde etmeye çalıştığınız şeyin kafası karıştı. Umarım yeni bir soru sorarsanız daha iyi yardım alabilirsiniz.
MikeFHay

Yanıtlar:


269

Canlı Yayınlar

Java 8 kullanıyorsanız, belki böyle bir şey deneyebilirsiniz:

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

Veya alternatif olarak, şöyle bir şey deneyebilirsiniz:

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

Bu yöntem döndürür trueeğer List<MyObject>bir içeren MyObjectadıyla name. Her MyObjects üzerinde bir işlem yapmak istiyorsanız getName().equals(name), böyle bir şey deneyebilirsiniz:

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

Burada obir MyObjectörneği temsil eder .

Alternatif olarak, yorumların önerdiği gibi (Teşekkürler MK10), Stream#anyMatchyöntemi kullanabilirsiniz :

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

1
Sonra, ikinci örnek olmalıdır public boolean. Ayrıca, kullanmak isteyebilirsiniz Objects.equals()durumda o.getName()olabilir null.
Eric Jablow

1
Ben sadece paranoyak bir programcıyım. İnsanların null'larla hızlı ve gevşek olduğu projelerle uğraştım, bu yüzden savunma eğilimindeyim. Eşyaların hiç boş kalmamasını sağlamak çok daha iyi.
Eric Jablow

5
@EricJablow Belki tek bir (benim) yerine, kontrol yapmayan TÜM cevaplar hakkında yorum yapmalısınız null.
Josh M

55
Daha da kısa ve anlaşılması daha kolay: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10

3
@ MK10 ile katılıyorum ama java.util.Objectsnullsafe karşılaştırma için kullanabilirsiniz return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);null akışındaki nesneleri kontrol etmek istiyorsanız, .filter(Objects::nonNull)anyMatch önce bir ekleyebilirsiniz.
tobijdc

81

İki seçeneğiniz var.

1. Tercih edilen ilk seçenek, Object sınıfınızdaki `equals ()` yöntemini geçersiz kılmaktır.

Diyelim ki, bu Object sınıfına sahipsiniz:

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

Şimdi diyelim ki MyObject ismini önemsiyorsunuz, benzersiz olmalı ki iki `` MyObject` 'aynı ada sahipse, eşit kabul edilmelidir. Bu durumda, eşitliği belirlemek için adları karşılaştırmak için `equals ()` yöntemini (ve ayrıca `hashcode ()` yöntemini) geçersiz kılmak istersiniz.

Bunu yaptıktan sonra, Koleksiyonda "foo" adında bir MyObject içerip içermediğini kontrol edebilirsiniz:

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

Ancak, aşağıdaki durumlarda bu sizin için bir seçenek olmayabilir:

  • Eşitliği kontrol etmek için hem adı hem de konumu kullanıyorsunuz, ancak yalnızca Koleksiyonda belirli bir konuma sahip herhangi bir `` MyObject` 'olup olmadığını kontrol etmek istiyorsunuz. Bu durumda, zaten `` equals () '' değerini geçersiz kıldınız.
  • "MyObject", değiştirme özgürlüğünüz olmayan bir API'nın parçasıdır.

Bunlardan herhangi biri söz konusuysa 2. seçenek istersiniz:

2. Kendi yardımcı programınızı yazın:

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

Alternatif olarak, ArrayList'i (veya başka bir koleksiyonu) genişletebilir ve ardından kendi yönteminizi ekleyebilirsiniz:

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

Ne yazık ki daha iyi bir yol yok.


Sanırım getter için parantezleri unuttuysanız (o! = Null && o.getLocation.equals (konum))
olszi

25

Google Guava

Guava kullanıyorsanız , fonksiyonel bir yaklaşım izleyebilir ve aşağıdakileri yapabilirsiniz

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

Bu biraz ayrıntılı görünüyor. Ancak yüklem bir nesnedir ve farklı aramalar için farklı varyantlar sağlayabilirsiniz. Kitaplığın kendisinin koleksiyonun yinelemesini ve uygulamak istediğiniz işlevi nasıl ayırdığını not edin. equals()Belirli bir davranış için geçersiz kılmanız gerekmez .

Aşağıda belirtildiği gibi, Java 8 ve daha sonra yerleşik java.util.Stream çerçevesi benzer bir şey sağlar.


1
Alternatif olarak Iterables.any(list, predicate), pratik olarak aynı olan kullanabilirsiniz ve stilistik olarak tercih edebilir veya etmeyebilirsiniz.
MikeFHay

@EricJablow Yup. :) Benim çözümüme bakabilirsin.
Josh M

25

Java 8+ kullanarak şu şekilde yapabilirsiniz:

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

20

Collection.contains()equals()biri dönünceye kadar her nesne çağrılarak uygulanır true.

Yani bunu uygulamanın bir yolu geçersiz kılmaktır equals()ama elbette, sadece bir tanesine eşit olabilirsiniz.

Bu nedenle Guava gibi çerçeveler bunun için tahminleri kullanır. İle Iterables.find(list, predicate)testi yüklemine girerek rastgele alanları arayabilirsiniz.

VM'nin üzerine inşa edilmiş diğer diller de buna sahiptir. Örneğin, Groovy'de şunları yazmanız yeterlidir:

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

Java 8 de tüm hayatımızı kolaylaştırdı:

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

Böyle şeyleri önemsiyorsanız, "Java'nın Ötesinde" kitabını öneriyorum. Java'nın sayısız eksiklikleri ve diğer dillerin nasıl daha iyi performans gösterdiğine dair birçok örnek içermektedir.


Bu nedenle, listeye eklenen özel Nesnelerin eşittir yöntemini geçersiz kılarsam ... belirli sınıf değişkenleri aynı ise sadece true döndürmesini sağlayabilir miyim?
Rudi Kershaw

1
Hayır, arkasındaki fikir equals()çok sınırlı. "Nesne kimliği" ni kontrol etmek anlamına gelir - bazı nesne sınıfları için ne anlama gelirse gelsin. Hangi alanların eklenmesi gerektiğini belirten bir bayrak eklediyseniz, bu her tür soruna neden olabilir. Buna şiddetle tavsiye ediyorum.
Aaron Digulla

Sevgiyi seviyorum, yani "yeni" Java 8 Lambda gerçekten bize bu serinliği hala vermiyor mu? def result = list.find{ it.name == 'John' }Oracle / JCP programcılara karşı savaş suçları için dava edilmelidir.
ken

19

Ikili arama

Listenizdeki bir öğeyi aramak için Collections.binarySearch komutunu kullanabilirsiniz (listenin sıralandığı varsayılarak):

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

nesne koleksiyonda yoksa negatif sayı döndürür, yoksa nesnenin değerini döndürür index. Bununla farklı arama stratejilerine sahip nesneleri arayabilirsiniz.


Bu, guava kullanmak istemeyen küçük projeler için harika bir çözümdür. Ancak bir soru, binarySearch üzerindeki dokümanlar şunu belirtiyor: "Liste, bu çağrıyı yapmadan önce belirtilen karşılaştırıcıya göre artan düzende sıralanmalıdır. Sıralanmamışsa, sonuçlar tanımsızdır". Ancak bazı durumlarda, karşılaştırılanlara bağlı olarak, durum böyle olmayabilir mi?
chrismarx

ve negatif sayı, nesne ekleyip yeniden sıralarsanız nesnenin nereye ekleneceğini gösterir.
fiorentinoing

8

Harita

Hashmap<String, Object>Değerlerden birini anahtar olarak kullanarak oluşturabilir ve sonra yourHashMap.keySet().contains(yourValue)doğru döndürüp döndürmediğini görebilirsiniz.


+1 Her şeyden önce ayrıntıları haritaya koymak için biraz ek yük anlamına gelse de, sürekli zaman araması elde edersiniz. Şimdiye kadar kimsenin bunu önermediğine şaşırdım.
Rudi Kershaw

6

Eclipse Koleksiyonları

Eğer kullanıyorsanız Eclipse Koleksiyonlar kullanabileceğiniz anySatisfy()yöntemi. Ya uyum Listbir de ListAdapterulaşabilir veya değiştirmek Listbir içine ListIterablemümkünse.

ListIterable<MyObject> list = ...;

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

Bunun gibi işlemleri sık sık yaparsanız, türün özelliğe sahip olup olmadığını yanıtlayan bir yöntem ayıklamak daha iyidir.

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

Alternatif formu anySatisfyWith()bir yöntem başvurusuyla birlikte kullanabilirsiniz .

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

Para üstünü yapamıyorsanız Listbir içine ListIterable, burada bütçeyi nasıl ListAdapter.

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

Not: Eclipse ödemeleri için bir komisyoncuyum.


5

Predicate

Koleksiyonlarla uğraşmak için size daha fazla işlevsellik kazandıran Java 8 veya kitaplığı kullanmazsanız, çözümünüzden daha fazla kullanılabilecek bir şey uygulayabilirsiniz.

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    
    }

Ben böyle bir şey kullanıyorum, yüklem arayüzü var, ve benim util sınıfına uygulama geçiriyorum.

Bunu kendi tarzımda yapmanın avantajı nedir? herhangi bir tür koleksiyonunda arama ile ilgilenen bir yönteminiz var. ve farklı bir alana göre arama yapmak istiyorsanız ayrı yöntemler oluşturmanız gerekmez. Yapmanız gereken tek şey, artık yararlı olmadığı anda yok edilebilecek farklı yüklem sağlamaktır /

kullanmak istiyorsanız, yapmanız gereken tek şey yöntemi çağırmak ve yükleminizi tanımlamaktır.

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

4

İşte Guava kullanan bir çözüm

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

containsyöntem equalsdahili olarak kullanır . Bu yüzden equalssınıfınıza göre ihtiyaca göre yöntemi geçersiz kılmanız gerekir.

Ancak bu sözdizimsel olarak doğru görünmüyor:

new Object().setName("John")

3
Ayarlayıcı geri dönmedikçe this.
Sotirios Delimanolis

Bu nedenle, listeye eklenen özel Nesnelerin eşittir yöntemini geçersiz kılarsam ... belirli sınıf değişkenleri aynı ise sadece true döndürmesini sağlayabilir miyim?
Rudi Kershaw

@RudiKershaw Evet bu fikir, bir / iki / tüm alanlara dayalı olarak doğru dönebilirsiniz. Dolayısıyla, aynı ada sahip iki nesnenin eşit nesneler olduğunu söylemenin mantıklı bir şekilde doğru olduğuna inanıyorsanız, sadece adları eşleştirerek true değerini döndürün.
Juned Ahsan

1
equalsGenel olarak sınıf için mantıklı olmadıkça, tek kullanımlık bir durum için geçersiz kılmanız gerçekten tavsiye edilmez . Sadece döngüyü yazarken çok daha iyi olursunuz.
MikeFHay

@MikeFHay kabul etti, bu yüzden yorumda bahsettiğimde "Bu nedenle, aynı ada sahip iki nesnenin eşit nesneler olduğunu söylemek mantıklı bir şekilde doğru olduğuna inanıyorsanız"
Juned Ahsan

1

Bunu List.contains(Object with field value equal to x)tekrar tekrar yapmanız gerekirse , basit ve etkili bir çözüm şöyle olacaktır:

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

O zaman List.contains(Object with field value equal to x)aynı sonuç olurdufieldOfInterestValues.contains(x);


1

ayrıca şunları anyMatch()kullanabilirsiniz:

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

0

JAVA 8 SDK'ya rağmen, kütüphanelerin çalışmanıza yardımcı olabilecek birçok toplama aracı vardır, örneğin: 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 );
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.