web-dev-qa-db-fra.com

Le moyen le plus efficace de voir si ArrayList contient un objet dans Java

J'ai une ArrayList d'objets en Java. Les objets ont quatre champs, deux desquels je considérerais l'objet comme égal à un autre. Je cherche le moyen le plus efficace, vu ces deux champs, de voir si le tableau contient cet objet.

La clé est que ces classes sont générées à partir d'objets XSD, je ne peux donc pas modifier les classes elles-mêmes pour écraser le .equals.

Existe-t-il un meilleur moyen que de simplement parcourir en boucle et comparer manuellement les deux champs pour chaque objet, puis de les casser lorsqu'ils sont détectés? Cela semble tellement désordonné, à la recherche d'une meilleure solution.

Edit: la liste de tableaux provient d'une réponse SOAP) qui n'est pas masquée dans les objets.

71
Parrots

Vous pouvez utiliser un comparateur avec les méthodes intégrées de Java pour le tri et la recherche binaire. Supposons que vous ayez une classe comme celle-ci, où a et b sont les champs que vous voulez utiliser pour le tri:

class Thing { String a, b, c, d; }

Vous définiriez votre comparateur:

Comparator<Thing> comparator = new Comparator<Thing>() {
  public int compare(Thing o1, Thing o2) {
    if (o1.a.equals(o2.a)) {
      return o1.b.compareTo(o2.b);
    }
    return o1.a.compareTo(o2.a);
  }
};

Puis triez votre liste:

Collections.sort(list, comparator);

Et enfin faire la recherche binaire:

int i = Collections.binarySearch(list, thingToFind, comparator);
37
Fabian Steeg

Compte tenu de vos contraintes, vous êtes coincé avec la recherche par force brute (ou la création d’un index si la recherche doit être répétée). Pouvez-vous préciser la façon dont le ArrayList est généré - peut-être y a-t-il une marge de manœuvre.

Si vous recherchez uniquement un code plus joli, envisagez d'utiliser les classes Apache Commons Collections, en particulier CollectionUtils.find () , pour le sucre syntaxique prêt à l'emploi:

ArrayList haystack = // ...
final Object needleField1 = // ...
final Object needleField2 = // ...

Object found = CollectionUtils.find(haystack, new Predicate() {
   public boolean evaluate(Object input) {
      return needleField1.equals(input.field1) && 
             needleField2.equals(input.field2);
   }
});
10
Michael Brewer-Davis

Si la liste est trié , vous pouvez utiliser un recherche binaire . Si non, alors il n'y a pas de meilleur moyen.

Si vous faites cela souvent, cela vaudrait certainement la peine de trier la liste la première fois. Comme vous ne pouvez pas modifier les classes, vous devrez utiliser un Comparator pour effectuer le tri et la recherche.

6
Michael Myers

Si vous êtes un utilisateur de my ForEach DSL , vous pouvez le faire avec une requête Detect.

Foo foo = ...
Detect<Foo> query = Detect.from(list);
for (Detect<Foo> each: query) 
    each.yield = each.element.a == foo.a && each.element.b == foo.b;
return query.result();
4
akuhn

Même si la méthode equals étais comparant ces deux champs, logiquement, ce serait exactement le même code que vous le feriez manuellement. OK, ça pourrait être "en désordre", mais c'est toujours la bonne réponse

4
oxbow_lakes

Existe-t-il un meilleur moyen que de simplement parcourir en boucle et comparer manuellement les deux champs pour chaque objet, puis de les rompre lorsqu'ils sont détectés? Cela semble tellement désordonné, à la recherche d’une meilleure solution.

Si votre préoccupation est la maintenabilité, vous pouvez faire ce que Fabian Steeg suggère (c'est ce que je ferais) bien que ce ne soit probablement pas le "plus efficace" (car vous devez d'abord trier le tableau, puis effectuer la recherche binaire) mais certainement l'option la plus propre et la meilleure.

Si l'efficacité vous préoccupe vraiment, vous pouvez créer une implémentation de liste personnalisée qui utilise le champ de votre objet comme hachage et utiliser une table de hachage comme stockage. Mais ce serait probablement trop.

Ensuite, vous devez changer le lieu où vous remplissez les données de ArrayList à YourCustomList.

Comme:

 List list = new ArrayList();

 fillFromSoap( list );

À:

 List list = new MyCustomSpecialList();

 fillFromSoap( list );

L'implémentation ressemblerait à quelque chose comme ceci:

class MyCustomSpecialList extends AbstractList  { 
    private Map<Integer, YourObject> internalMap;

    public boolean add( YourObject o ) { 
         internalMap.put( o.getThatFieldYouKnow(), o );
    }

    public boolean contains( YourObject o ) { 
        return internalMap.containsKey( o.getThatFieldYouKnow() );
    }

}

Un peu comme un HashSet, le problème ici est que le HashSet repose sur la bonne implémentation de la méthode hashCode, que vous n'avez probablement pas. Au lieu de cela, vous utilisez comme hachage "le champ que vous connaissez", qui permet de faire en sorte qu'un objet soit égal à l'autre.

Bien sûr, implémenter une liste à partir de zéro beaucoup plus délicate que mon extrait ci-dessus, c’est pourquoi je dis que la suggestion Fabian Steeg serait meilleure et plus facile à mettre en œuvre (bien que quelque chose comme cela serait plus efficace)

Dites-nous ce que vous avez fait à la fin.

2
OscarRyz

Peut-être qu'une liste n'est pas ce dont vous avez besoin.

Peut-être qu'un TreeSet serait un meilleur conteneur. Vous obtenez l'insertion et la récupération de O (journal N) et l'itération ordonnée (mais ne permettent pas les doublons).

LinkedHashMap pourrait être encore mieux pour votre cas d'utilisation, vérifiez aussi.

2
Ben Hardy

Il y a trois options de base:

1) Si les performances d'extraction sont primordiales et qu'il est pratique de le faire, utilisez une forme de table de hachage construite une fois (et modifiée si/la liste change).

2) Si la liste est triée de manière pratique ou s'il est pratique de la trier et que la récupération de O (log n) est suffisante, triez et recherchez.

3) Si la récupération de O(n)) est assez rapide ou s'il est peu pratique de manipuler/maintenir la structure de données ou une autre, parcourez la liste.

Avant d'écrire du code plus complexe qu'une simple itération sur la liste, il convient de réfléchir à quelques questions.

  • Pourquoi quelque chose de différent est-il nécessaire? (Temps) performance? Élégance? Maintenabilité? Réutilisation? Toutes ces raisons sont acceptables, séparément ou ensemble, mais elles influencent la solution.

  • Quel contrôle avez-vous sur la structure de données en question? Pouvez-vous influencer la construction? Géré plus tard?

  • Quel est le cycle de vie de la structure de données (et des objets sous-jacents)? Est-ce construit en une seule fois et jamais changé, ou très dynamique? Votre code peut-il surveiller (ou même modifier) ​​son cycle de vie?

  • Existe-t-il d'autres contraintes importantes, telles que l'empreinte mémoire? Est-ce que l'information sur les doublons est importante? Etc.

1
Jeremy Rishel

Construire une carte de hachage de ces objets en fonction de la valeur du champ en tant que clé pourrait être intéressant du point de vue des performances, par exemple. peupler les cartes une fois et trouver des objets très efficacement

1
Rocket Surgeon

Si vous devez effectuer plusieurs recherches dans la même liste, il peut être rentable de créer un index.

Itérez une fois et construisez une carte de hachage avec la valeur égale que vous recherchez en tant que clé et le nœud approprié en tant que valeur. Si vous avez besoin de tous au lieu de tous les éléments d'une valeur égale, laissez la carte avoir un type de liste de valeur et construisez la liste complète lors de l'itération initiale.

Veuillez noter que vous devez effectuer une mesure avant de procéder car la surcharge liée à la construction de l'index peut masquer le fait de traverser jusqu'à ce que le nœud attendu soit trouvé.

1

Je dirais que la solution la plus simple serait d'encapsuler l'objet et de déléguer l'appel de contenu à une collection de la classe encapsulée. Ceci est similaire au comparateur mais ne vous oblige pas à trier la collection résultante, vous pouvez simplement utiliser ArrayList.contains ().

public class Widget {
        private String name;
        private String desc;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }



    public abstract class EqualsHashcodeEnforcer<T> {

        protected T wrapped;

        public T getWrappedObject() {
            return wrapped;
        }

        @Override
        public boolean equals(Object obj) {
            return equalsDelegate(obj);
        }

        @Override
        public int hashCode() {
            return hashCodeDelegate();
        }

        protected abstract boolean equalsDelegate(Object obj);

        protected abstract int hashCodeDelegate();
    }


    public class WrappedWidget extends EqualsHashcodeEnforcer<Widget> {

        @Override
        protected boolean equalsDelegate(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj == getWrappedObject()) {
                return true;
            }
            if (obj.getClass() != getWrappedObject().getClass()) {
                return false;
            }
            Widget rhs = (Widget) obj;

            return new EqualsBuilder().append(getWrappedObject().getName(),
                    rhs.getName()).append(getWrappedObject().getDesc(),
                    rhs.getDesc()).isEquals();
        }

        @Override
        protected int hashCodeDelegate() {

            return new HashCodeBuilder(121, 991).append(
                    getWrappedObject().getName()).append(
                    getWrappedObject().getDesc()).toHashCode();
        }

    }
0
jonathan.cone