Java List.contains (objet avec une valeur de champ égale à x)

199

Je veux vérifier si a Listcontient un objet qui a un champ avec une certaine valeur. Maintenant, je pouvais utiliser une boucle pour parcourir et vérifier, mais j'étais curieux de savoir s'il y avait quelque chose de plus efficace.

Quelque chose comme;

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

Je sais que le code ci-dessus ne fait rien, c'est juste pour montrer à peu près ce que j'essaie de réaliser.

Aussi, juste pour clarifier, la raison pour laquelle je ne veux pas utiliser une simple boucle est parce que ce code va actuellement à l'intérieur d'une boucle qui est à l'intérieur d'une boucle qui est à l'intérieur d'une boucle. Pour plus de lisibilité, je ne veux pas continuer à ajouter des boucles à ces boucles. Je me suis donc demandé s'il existait des alternatives simples (ish).

Rudi Kershaw
la source
5
Comme il s'agit d'une égalité personnalisée, vous devrez écrire du code personnalisé.
Sotirios Delimanolis
Votre objectif déclaré et votre exemple de code ne semblent pas correspondre. Voulez-vous comparer les objets uniquement sur la base d'une valeur de champ?
Duncan Jones
1
Remplacer la equals(Object)méthode de votre objet personnalisé?
Josh M
1
for(Person p:list) if (p.getName().equals("John") return true; return false;Vous ne trouverez pas un moyen plus concis en Java, je le crains.
MikeFHay
@Rajdeep désolé, je ne comprends pas votre question. p.equals(p) devrait toujours être vrai, donc je ne comprends pas ce que vous essayez de réaliser. J'espère que si vous posez une nouvelle question, vous pourrez obtenir une meilleure aide.
MikeFHay

Réponses:

267

Ruisseaux

Si vous utilisez Java 8, vous pouvez peut-être essayer quelque chose comme ceci:

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

Ou bien, vous pouvez essayer quelque chose comme ceci:

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

Cette méthode retournera truesi le List<MyObject>contient un MyObjectavec le nom name. Si vous souhaitez effectuer une opération sur chacun des MyObjects que getName().equals(name)vous pourriez essayer quelque chose comme ceci:

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

o représente une MyObjectinstance.

Alternativement, comme le suggèrent les commentaires (Merci MK10), vous pouvez utiliser la Stream#anyMatchméthode:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().equals(name));
}
Josh M
la source
1
Ensuite, le deuxième exemple devrait être public boolean. En outre, vous voudrez peut-être utiliser Objects.equals()au cas où cela o.getName()pourrait l'être null.
Eric Jablow
1
Je suis juste un programmeur paranoïaque. J'ai traité des projets où les gens étaient rapides et lâches avec des valeurs nulles, donc j'ai tendance à être défensif. Il vaut bien mieux s'assurer que les articles ne soient jamais nuls.
Eric Jablow
5
@EricJablow Vous devriez peut-être commenter TOUTES les réponses qui n'effectuent pas de nullvérification, par opposition à une seule (la mienne).
Josh M
55
Encore plus court et plus facile à comprendre: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10
3
Je suis d'accord avec @ MK10 mais j'utiliserais java.util.Objectspour la comparaison nullsafe return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Si vous voulez vérifier les objets dans le flux pour null, vous pouvez ajouter un .filter(Objects::nonNull)avant anyMatch.
tobijdc
81

Vous avez deux choix.

1. Le premier choix, qui est préférable, est de remplacer la méthode `equals ()` dans votre classe Object.

Disons, par exemple, que vous avez cette classe Object:

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

Supposons maintenant que vous vous souciez uniquement du nom de MyObject, qu'il doit être unique, donc si deux `MyObject`s ont le même nom, ils doivent être considérés comme égaux. Dans ce cas, vous voudrez remplacer la méthode `equals ()` (et aussi la méthode `hashcode ()`) afin qu'elle compare les noms pour déterminer l'égalité.

Une fois que vous avez fait cela, vous pouvez vérifier si une collection contient un MyObject avec le nom "foo" comme ceci:

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

Cependant, cela pourrait ne pas être une option pour vous si:

  • Vous utilisez à la fois le nom et l'emplacement pour vérifier l'égalité, mais vous voulez uniquement vérifier si une collection a des `MyObject`s avec un certain emplacement. Dans ce cas, vous avez déjà remplacé `equals ()`.
  • `MyObject` fait partie d'une API que vous n'avez pas la liberté de modifier.

Si l'un ou l'autre est le cas, vous aurez besoin de l'option 2:

2. Écrivez votre propre méthode utilitaire:

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

Alternativement, vous pouvez étendre ArrayList (ou une autre collection), puis y ajouter votre propre méthode:

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

Malheureusement, il n'y a pas de meilleure solution.

James Dunn
la source
Je pense que vous avez oublié les crochets pour getter dans if (o! = Null && o.getLocation.equals (location))
olszi
25

Google Guava

Si vous utilisez Guava , vous pouvez adopter une approche fonctionnelle et effectuer les opérations suivantes

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

qui a l'air un peu bavard. Cependant, le prédicat est un objet et vous pouvez fournir différentes variantes pour différentes recherches. Notez comment la bibliothèque elle-même sépare l'itération de la collection et la fonction que vous souhaitez appliquer. Vous n'avez pas à remplacer equals()un comportement particulier.

Comme indiqué ci-dessous, le cadre java.util.Stream intégré à Java 8 et versions ultérieures fournit quelque chose de similaire.

Brian Agnew
la source
1
Alternativement, vous pouvez utiliser Iterables.any(list, predicate), qui est pratiquement le même, et vous pouvez ou non le préférer stylistiquement.
MikeFHay
@EricJablow Yup. :) Vous pouvez regarder ma solution.
Josh M
25

Voici comment le faire en utilisant Java 8+:

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

Collection.contains()est implémenté en appelant equals()chaque objet jusqu'à ce qu'un retourne true.

Donc, une façon de l'implémenter est de remplacer, equals()mais bien sûr, vous ne pouvez avoir qu'un seul égal.

Les cadres comme Guava utilisent donc des prédicats pour cela. AvecIterables.find(list, predicate) , vous pouvez rechercher des champs arbitraires en plaçant le test dans le prédicat.

D'autres langages construits au-dessus de la VM ont ceci intégré. Dans Groovy , par exemple, vous écrivez simplement:

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

Java 8 nous a aussi facilité la vie:

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

Si vous vous souciez de choses comme ça, je suggère le livre "Beyond Java". Il contient de nombreux exemples des nombreuses lacunes de Java et de la façon dont d'autres langages fonctionnent mieux.

Aaron Digulla
la source
Donc, si je remplace la méthode equals des objets personnalisés ajoutés à la liste ... est-ce que je peux la faire retourner juste si certaines variables de classe sont les mêmes?
Rudi Kershaw
1
Non, l'idée derrière equals()est très limitée. Cela signifie de vérifier "l'identité de l'objet" - quoi que cela puisse signifier pour une classe d'objets. Si vous ajoutez un indicateur indiquant les champs à inclure, cela peut provoquer toutes sortes de problèmes. Je le déconseille fortement.
Aaron Digulla
Vous adorez groovy, donc la "nouvelle" Lambda Java 8 ne nous donne pas encore cette fraîcheur? def result = list.find{ it.name == 'John' }Oracle / JCP devrait être poursuivi pour crimes de guerre contre des programmeurs.
ken
19

Recherche binaire

Vous pouvez utiliser Collections.binarySearch pour rechercher un élément dans votre liste (en supposant que la liste est triée):

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

qui renverra un nombre négatif si l'objet n'est pas présent dans la collection ou bien il renverra le indexde l'objet. Avec cela, vous pouvez rechercher des objets avec différentes stratégies de recherche.

Debojit Saikia
la source
C'est une excellente solution pour les petits projets qui ne veulent pas utiliser de goyave. Une question cependant, les documents sur binarySearch déclarent que: "La liste doit être triée par ordre croissant selon le comparateur spécifié, avant d'effectuer cet appel. Si elle n'est pas triée, les résultats ne sont pas définis". Mais dans certains cas, selon ce qui est comparé, cela pourrait ne pas être le cas?
chrismarx
et le nombre négatif représente l'endroit où l'objet sera inséré si vous l'ajoutez et le triez à nouveau.
fiorentinoing
8

Carte

Vous pouvez créer un en Hashmap<String, Object>utilisant l'une des valeurs comme clé, puis voir si yourHashMap.keySet().contains(yourValue)renvoie true.


la source
+1 Bien que cela signifie un peu de temps supplémentaire pour mettre les détails sur la carte en premier lieu, vous obtenez ensuite une recherche constante. Je suis surpris que personne ne l'ait suggéré jusqu'à présent.
Rudi Kershaw
6

Collections Eclipse

Si vous utilisez des collections Eclipse , vous pouvez utiliser la anySatisfy()méthode. Soit adapter votre Listen a ListAdapterou changer votre Listen ListIterablesi possible.

ListIterable<MyObject> list = ...;

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

Si vous effectuez fréquemment des opérations comme celle-ci, il est préférable d'extraire une méthode qui indique si le type possède l'attribut.

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

Vous pouvez utiliser le formulaire alternatif anySatisfyWith()avec une référence de méthode.

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

Si vous ne pouvez pas changer votre Listen un ListIterable, voici comment vous l'utiliseriez ListAdapter.

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

Remarque: je suis un committer pour les ollections Eclipse.

Craig P. Motlin
la source
5

Predicate

Si vous n'utilisez pas Java 8, ou une bibliothèque qui vous offre plus de fonctionnalités pour gérer les collections, vous pouvez implémenter quelque chose qui peut être plus réutilisable que votre solution.

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    
    }

j'utilise quelque chose comme ça, j'ai une interface de prédicat, et je passe l'implémentation à ma classe util.

Quel est l'avantage de faire cela à ma façon? vous avez une méthode qui traite de la recherche dans n'importe quelle collection de types. et vous n'avez pas à créer de méthodes distinctes si vous souhaitez effectuer une recherche par champ différent. tout ce que vous devez faire est de fournir un prédicat différent qui peut être détruit dès qu'il ne sert plus /

si vous voulez l'utiliser, il vous suffit d'appeler la méthode et de définir votre prédicat

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

Voici une solution utilisant 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
la source
3

containsutilise en equalsinterne. Vous devez donc remplacer leequals méthode de votre classe selon vos besoins.

Btw cela ne semble pas correct sur le plan statistique:

new Object().setName("John")
Juned Ahsan
la source
3
A moins que le passeur ne revienne this.
Sotirios Delimanolis
Donc, si je remplace la méthode equals des objets personnalisés ajoutés à la liste ... est-ce que je peux la faire retourner juste si certaines variables de classe sont les mêmes?
Rudi Kershaw
@RudiKershaw Oui, c'est l'idée, vous pouvez retourner vrai sur la base d'un / deux / tous les champs. Donc, si vous pensez qu'il est logiquement correct de dire que deux objets de même nom sont des objets égaux, retournez true en comaprant simplement les noms.
Juned Ahsan
1
Il n'est vraiment pas conseillé de remplacer equalspour un cas d'utilisation unique, sauf si cela a du sens pour la classe en général. Vous feriez mieux d’écrire la boucle.
MikeFHay
@MikeFHay a accepté, c'est pourquoi j'ai mentionné dans le commentaire "Donc, si vous croyez qu'il est logiquement correct de dire que deux objets du même nom sont des objets égaux, alors retournez vrai"
Juned Ahsan
1

Si vous devez effectuer cette opération à List.contains(Object with field value equal to x)plusieurs reprises, une solution de contournement simple et efficace serait:

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

Ensuite, le List.contains(Object with field value equal to x)serait le même résultat quefieldOfInterestValues.contains(x);

Magda
la source
1

vous pouvez également anyMatch()utiliser:

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

Malgré JAVA 8 SDK, de nombreuses bibliothèques d'outils de collecte peuvent vous aider à travailler, par exemple: 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 );
Pasha GR
la source