Most efficient way to see if an ArrayList contains an object in Java

后端 未结 12 1087
孤城傲影
孤城傲影 2020-11-30 18:25

I have an ArrayList of objects in Java. The objects have four fields, two of which I\'d use to consider the object equal to another. I\'m looking for the most efficient wa

相关标签:
12条回答
  • 2020-11-30 18:58

    You could use a Comparator with Java's built-in methods for sorting and binary search. Suppose you have a class like this, where a and b are the fields you want to use for sorting:

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

    You would define your Comparator:

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

    Then sort your list:

    Collections.sort(list, comparator);
    

    And finally do the binary search:

    int i = Collections.binarySearch(list, thingToFind, comparator);
    
    0 讨论(0)
  • 2020-11-30 18:58

    Given your constraints, you're stuck with brute force search (or creating an index if the search will be repeated). Can you elaborate any on how the ArrayList is generated--perhaps there is some wiggle room there.

    If all you're looking for is prettier code, consider using the Apache Commons Collections classes, in particular CollectionUtils.find(), for ready-made syntactic sugar:

    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);
       }
    });
    
    0 讨论(0)
  • 2020-11-30 18:58

    Maybe a List isn't what you need.

    Maybe a TreeSet would be a better container. You get O(log N) insertion and retrieval, and ordered iteration (but won't allow duplicates).

    LinkedHashMap might be even better for your use case, check that out too.

    0 讨论(0)
  • 2020-11-30 18:59

    It depends on how efficient you need things to be. Simply iterating over the list looking for the element which satisfies a certain condition is O(n), but so is ArrayList.Contains if you could implement the Equals method. If you're not doing this in loops or inner loops this approach is probably just fine.

    If you really need very efficient look-up speeds at all cost, you'll need to do two things:

    1. Work around the fact that the class is generated: Write an adapter class which can wrap the generated class and which implement equals() based on those two fields (assuming they are public). Don't forget to also implement hashCode() (*)
    2. Wrap each object with that adapter and put it in a HashSet. HashSet.contains() has constant access time, i.e. O(1) instead of O(n).

    Of course, building this HashSet still has a O(n) cost. You are only going to gain anything if the cost of building the HashSet is negligible compared to the total cost of all the contains() checks that you need to do. Trying to build a list without duplicates is such a case.


    * () Implementing hashCode() is best done by XOR'ing (^ operator) the hashCodes of the same fields you are using for the equals implementation (but multiply by 31 to reduce the chance of the XOR yielding 0)

    0 讨论(0)
  • 2020-11-30 19:01

    Building a HashMap of these objects based on the field value as a key could be worthwhile from the performance perspective, e.g. populate Maps once and find objects very efficiently

    0 讨论(0)
  • 2020-11-30 19:03

    Is there any better way than just looping through and manually comparing the two fields for each object and then breaking when found? That just seems so messy, looking for a better way.

    If your concern is maintainability you could do what Fabian Steeg suggest ( that's what I would do ) although it probably isn't the "most efficient" ( because you have to sort the array first and then perform the binary search ) but certainly the cleanest and better option.

    If you're really concerned with efficiency, you can create a custom List implementation that uses the field in your object as the hash and use a HashMap as storage. But probably this would be too much.

    Then you have to change the place where you fill the data from ArrayList to YourCustomList.

    Like:

     List list = new ArrayList();
    
     fillFromSoap( list );
    

    To:

     List list = new MyCustomSpecialList();
    
     fillFromSoap( list );
    

    The implementation would be something like the following:

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

    }

    Pretty much like a HashSet, the problem here is the HashSet relies on the good implementation of the hashCode method, which probably you don't have. Instead you use as the hash "that field you know" which is the one that makes one object equals to the other.

    Of course implementing a List from the scratch lot more tricky than my snippet above, that's why I say the Fabian Steeg suggestion would be better and easier to implement ( although something like this would be more efficient )

    Tell us what you did at the end.

    0 讨论(0)
提交回复
热议问题