1

How can I perform a dirty check on an ArrayList in Java?

I have an object

Animal
{
    String animalName;
    String animalType;
    String animalPlace
}

I have two lists:

List<Animal> animalList1 = new ArrayList<Animal>();
List<Animal> animalList2 = new ArrayList<Animal>();

In the first list, I have the values:

[0][lion,Carnivorous,Africa]
[1][tiger,Carnivorous,Zambia]
[2][Goat,Herbivorous,Zimbabwe]

In the second list, I have the values:

[0][lion,Carnivorous,Africa]
[1][tiger,Carnivorous,Zambia]
[2][Goat,Herbivorous,Norway]

In the second list, the animalPlace for the element at index 2 has changed from Zimbabwe to Norway. How do I compare the two list values and the values which have changed put that object in a separate list?

List<Animal> animalList3 = new ArrayList<Animal>();

[0] [Goat,Herbivorous,Norway]

I know the traditional way of doing it by comparing the list using a for loop:

for (int i = 0; i < list1.size(); i++) 
{
  for (int j = i+1; j < list2.size(); j++) 
  {
    // compare list1 and list2 values
  }
}

Is there any faster and/or better way?

DwB
  • 37,124
  • 11
  • 56
  • 82
sTg
  • 4,313
  • 16
  • 68
  • 115
  • 1
    Implement `equals()` method and keep the ones that are unique – azro Nov 16 '17 at 17:16
  • 2
    Well, it seems you want to compare list1[i] with list2[i]. That's not what your nested loops do. Doing it correctly would already be much faster. Start by finding a correct solution. Then optimize if it's not fast enough. – JB Nizet Nov 16 '17 at 17:16
  • as @azro said, implement equals. then you can use the following: `list1.stream().filter(a -> list2.stream().anyMatch(a2 -> a2.equals(a))).collect(Collectors.toList());` – Lino Nov 16 '17 at 17:18
  • @JBNizet Yes i need to compare two list – sTg Nov 16 '17 at 17:18

2 Answers2

5

The List#removeAll() method is your friend.

Implement equals() and hashCode() for Animal, and then:

List<Animal> animalList3 = new ArrayList<Animal>(animalList2);
animalList3.removeAll(animalList1);
AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • @azro An animal that has been updated in `animalList2` should not `equals()` an animal in `animalList1`, and so `removeAll()` will not remove the updated animal from the newly created `animalList3`. Thus, `animalList3` will contain all new **and** modified entries. The OP has not indicated whether new entries will exist, and what to do if they occur. Likewise, the OP has not specified what to do if an entry has been deleted. But given only modifications to entries in the list, all modified entries will appear in `animalList3`. – AJNeufeld Nov 16 '17 at 20:24
2

You can do it this way : iterate over second list and keep only the ones that are also in first list BUT which are not equals : they have been modified (type or place, name is same)

List<Animal> res = animalList2.stream()
            .filter(a -> animalList1.stream()
                        .anyMatch(b -> !b.equals(a) && b.animalName.equals(a.animalName)))
            .collect(Collectors.toList());

With :

List<Animal> animalList1 = new ArrayList<Animal>();
animalList1.add(new Animal("lion", "Carnivorous", "Africa"));
animalList1.add(new Animal("tiger", "Carnivorous", "Zambia"));
animalList1.add(new Animal("Goat", "Herbivorous", "Zimbabwe"));
animalList1.add(new Animal("Donkey", "Herbivorous", "Zimbabwe"));

List<Animal> animalList2 = new ArrayList<Animal>();
animalList2.add(new Animal("lion", "Carnivorous", "Africa"));  //not new but no modif   X
animalList2.add(new Animal("tiger", "Carnivorous", "Zambia")); //not new but no modif   X
animalList2.add(new Animal("Goat", "Herbivorous", "Norway"));  //not new and modif      V
animalList2.add(new Animal("Horse", "Herbivorous", "Norway")); //new                    X

You'll get only [Goat,Herbivorous,Norway] at the end, the only one which is an update from first list to second


And you need an equals() like this :

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Animal animal = (Animal) o;
    return animalName.equals(animal.animalName) && animalType.equals(animal.animalType) && animalPlace.equals(animal.animalPlace);
}
azro
  • 53,056
  • 7
  • 34
  • 70
  • Will this be comparing only the new objects or the properties that from the object as well? – sTg Nov 16 '17 at 17:31
  • Print only those which are in **both** list but not equals in both, so those which have been updated – azro Nov 16 '17 at 20:07
  • You are assuming the `animalName` is a unique key. If the both lists contain [Goat,Herbivorous,Zimbabwe] and [Goat,Herbivorous,Norway], then both entries will pass your `anyMatch` filter (a non-equal animal with that name exists), and both entries will appear in the modified list, despite no modifications existing. – AJNeufeld Nov 16 '17 at 20:41
  • Also, if you downvote my answer and add a comment explaining your downvote, and then you delete your comment based on my reply, it would be polite to a) remove the downvote, or b) post a different comment explaining why you are still downvoting my answer. – AJNeufeld Nov 16 '17 at 20:44