How to get a unique hashCode for an ArrayList? - java

var arr1 = new ArrayList<Integer>();
var arr2 = new ArrayList<Integer>();
System.out.println(arr1.hashCode());
System.out.println(arr2.hashCode());
System.out.println(arr1.equals(arr2));
Output:
1
1
true
After searching, I found this answer which explains why this is happening.
From the answer:
Here's the Java 8 implementation (it's actually implemented in
AbstractList) :
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
And since the two arrays are empty, it's obvious that the hashCode function would return 1 for both of the arrays.
My question is, given two ArrayLists, how to determine whether they are the same or not? How can I be sure that modifing one ArrayList won't affect the other? since I have no unique hash for each ArrayList, I can't compare their hashes to know whether they're equal or not. How to get a unique hash for each object?
Also why the equals function returns true? Does it compare objects by hashes? Are they actually the same object? If yes then why and how to make different objects then?
And what are the classes other than ArrayList that acts in this way?

Related

List.equals is false despite all objects in list being equal

I have a list of objects, say Article, which is quite a complex, nested object. I will omit the actual class as it shouldn't matter really; it is probably important to know that Article has equals() implemented (with AutoValue).
I have two lists:
List<Article> list1 = getSomeArticles();
List<Article> list2 = getOtherArticles();
Now I check if they are equal:
boolean listsAreEqual = list1.equals(list2);
this returns false.
But I check if a. both lists have the same size and b. each item at index i is equal in both lists:
if (list1.size() != list2.size()) {
return;
}
for (int i = 0; i < list1.size(); i++) {
Article article1 = list1.get(i);
Article article2 = list2.get(i);
if (!article1.equals(article2)) {
return;
}
}
// All items in the two lists are equals, but list2.equals(list2) is false
Both lists seem to contain exactly the same items, but list1.equals(list2) returns false.
How can that be?
They should be equal. Something is not right with your code. From the documentation:
Compares the specified object with this list for equality. Returns
true if and only if the specified object is also a list, both lists
have the same size, and all corresponding pairs of elements in the two
lists are equal. (Two elements e1 and e2 are equal if (e1==null ?
e2==null : e1.equals(e2)).) In other words, two lists are defined to
be equal if they contain the same elements in the same order. This
definition ensures that the equals method works properly across
different implementations of the List interface.
Unless, of course, the List implementation overrides the default from java.util.List.

Why does not Java HashSet.equals() check for object equality?

I've come to this phenomenon this morning, the equals method in Set does not check for value equality of the element while List does. This is not in accordance with the java doc.
Set<MyClass> s1 = new HashSet<>();
Set<MyClass> s2 = new HashSet<>();
Set<MyClass> s3 = new HashSet<>();
Set<MyClass> s4 = new HashSet<>();
List<MyClass> l1 = new ArrayList<>();
List<MyClass> l2 = new ArrayList<>();
MyClass o1 = new MyClass();
MyClass o2 = new MyClass();
// **this gives false, and does not call MyClass.equals().**
s1.add(o1);
s2.add(o2);
boolean setCompareWithDifferentObjects = s1.equals(s2);
// this gives true, and also does not call MyClass.equals().
s3.add(o1);
s4.add(o1);
boolean setCompareWithSaveObjects = s3.equals(s4);
// this give true, and MyClass.equals() is called.
l1.add(o1);
l2.add(o2);
boolean listCompare = l1.equals(l2)
I've done some research. According to this
Java doc for Set, HashSet equals , HashSet containsAll, HashSet contains, it will use (o==null ? e==null : o.equals(e)) to check whether the elements are equal. So why is this happen? Can anyone give me some hint on this?
Thanks!
----------Answer to this question can be found here -----------
What issues should be considered when overriding equals and hashCode in Java?
I overrided equals() but not hashCode()...
btw, the same set comparison worked in groovy, even if hashCode() is not overriden.
HashSet includes a number of optimizations that can explain all of this: first, if two objects are put into different buckets by their hash codes, or if they have different hash codes at all, they may skip the equals call. This is allowed by the contract of Object.hashCode; if two objects have different hash codes then they are not allowed to .equals to each other.
For the other case, HashSet takes advantage of the contract of .equals that specifies that if two objects are == to each other, then they must be .equals to each other. Part of HashSet's implementation here checks if the elements are ==, and if they are, it skips calling .equals.
If every method implements its contract correctly, this cannot change the semantics; HashSet will always behave exactly as if .equals were being called.

Loop through array and display only one item

I have an array which looks like this:
array = { Person1, Person2, Person3, Person1, Person2, Person3};
Now how can I get an int with the total count of unique Persons? That means a Person which was already counted, shouldn't be counted again.
In the give example above should return the int value of 3
int uniq = 3;
How can I do this task?
If all you want is the size, this is a correct version of one of the other answers:
Set<Person> set = new HashSet<Person>(Arrays.asList(array));
int count = set.size();
This assumes that either your Person type has an equals method that will tell you whether two persons are the same, or if there's no equals, the default equals is OK (the default returns true if the references are equal).
P.S.: if you've defined your own Person in equals, you must also define hashCode or else this solution won't work. hashCode must be defined so that if two Persons are equal then they also have the same hash code. See What issues should be considered when overriding equals and hashCode in Java?.
the easiest way is to avoid doublon when you add the person in your array.
To do that, when you add a Person in the array, you go through all the array and verify that you don't already have it :)
If you need to have the doublon in the list, then you should create a tempArray and only add the different person in this new array like described in the other method, so in the end you will have your good number in the tempArray.size()
Try:
List<Person> uniqueList = new ArrayList<Person>(new LinkedHashSet<Person>( array.asList() ).sort() );
uniqueList.size(); //Number of unique objects

How to insert array into hashtable w/o initializing in Java?

I have a hashmap initialized as follows:
Hashmap<String[][], Boolean> tests = new Hashmap<String [][], Boolean>();
I would like to insert into tests without having to initialize the key:
tests.put({{"a"}, {"a"}}, true);
However, Java doesn't seem to let me do this. It works if I do it like this:
String[][] hi = {{"a"}, {"a"}};
tests.put(hi, true);
Is there any way to avoid the latter and get the former working?
Can someone also explain the reasoning behind this error?
Thanks
Yes, you can write like this:
tests.put(new String[][] {{"a"}, {"a"}}, true);
This is often referred to as an anonymous array or a just-in-time array.
In your case you would have to use
tests.put(new String[][]{{"a"}, {"a"}}, true);
because as you noticed {{"a"}, {"a"}}
String[][] hi = {{"a"}, {"a"}};
can be used only while creating reference to array.
You can use
tests.put(new String[][]{{"hello", "goodbye"},{"hi", "bye"}}, true);
This is almost definitely not what you want.
Arrays in Java get their equality and hash code from Object -- which is to say, based on their reference identity. So:
String[] a = { "hello" }; // create one array
String[] b = { "hello" }; // create a different array with the same contents
assert a != b; // the two references are to different objects
assert ! a.equals(b); // they're not equal
assert a.hashCode() != b.hashCode(); // neither are their hashes (probably)
a and b will not be equal, and their hash codes will almost certainly not be equal, since they are different objects. This means that if you use an array as the key to a hash map, you won't be able to retrieve the value using an key but the exact one that you created it with: any other array will have a different hash code and will be non-equal, and therefore won't be considered an equivalent key.
The solution is to replace the String[][] with a List<List<String>>. Lists define equality and hash codes based on their contents, so a list containing [ "hello" ] is equal to any other list containing [ "hello" ]:
List<String> x = Arrays.asList("hello");
List<String> y = Arrays.asList("hello");
assert x != y; // the two lists are different objects
assert x.equals(y); // but they're equal
assert x.hashCode() == y.hashCode(); // and so are their hash codes
Now you can use the lists as keys. Keep in mind that once a list is a key to the map, it's not allowed to change values. Doing so will probably break the hash map, because the list's hash code will have changed, but the map won't know about it, so the map will look for it in the wrong hash bucket.
The easiest options here are:
be sure that nobody else has a reference to that same List object and might change it
create a deep copy of the List before you put it into the map (that is, copy the "inner" lists as well as the "outer" one)
For the second option, it'd be something like:
// copy the outer list
List<List<String>> outerCopy = new ArrayList<List<String>>( originalList );
ListIterator<List<String>> listIterator = outerCopy.listIterator();
while (listIterator.hasNext()) {
// make a copy of the inner list
List<String> innerCopy = new ArrayList<String>( listIterator.next() );
listIterator.set(innerCopy);
}

Cross compare ArrayList elements and remove duplicates

I have an ArrayList<MyObject> that may (or may not) contain duplicates of MyObject I need to remove from the List. How can I do this in a way that I don't have to check duplication twice as I would do if I were to iterate the list in two for-loops and cross checking every item with every other item.
I just need to check every item once, so comparing A:B is enough - I don't want to compare B:A again, as I already did that.
Furthermore; can I just remove duplicates from the list while looping? Or will that somehow break the list and my loop?
Edit: Okay, I forgot an important part looking through the first answers: A duplicate of MyObject is not just meant in the Java way meaning Object.equals(Object), but I need to be able to compare objects using my own algorithm, as the equality of MyObjects is calculated using an algorithm that checks the Object's fields in a special way that I need to implement!
Furthermore, I can't just override euqals in MyObject as there are several, different Algorithms that implement different strategies for checking the equality of two MyObjects - e.g. there is a simple HashComparer and a more complex EuclidDistanceComparer, both being AbstractComparers implementing different algorithms for the public abstract boolean isEqual(MyObject obj1, MyObject obj2);
Sort the list, and the duplicates will be adjacent to each other, making them easy to identify and remove. Just go through the list remembering the value of the previous item so you can compare it with the current one. If they are the same, remove the current item.
And if you use an ordinary for-loop to go through the list, you control the current position. That means that when you remove an item, you can decrement the position (n--) so that the next time around the loop will visit the same position (which will now be the next item).
You need to provide a custom comparison in your sort? That's not so hard:
Collections.sort(myArrayList, new Comparator<MyObject>() {
public int compare(MyObject o1, MyObject o2) {
return o1.getThing().compareTo(o2.getThing());
}
});
I've written this example so that getThing().compareTo() stands in for whatever you want to do to compare the two objects. You must return an integer that is zero if they are the same, greater than 1 if o1 is greater than o2 and -1 if o1 is less than o2. If getThing() returned a String or a Date, you'd be all set because those classes have a compareTo method already. But you can put whatever code you need to in your custom Comparator.
Create a set and it will remove the duplicates automatically for you if the ordering is not important.
Set<MyObject> mySet = new HashSet<MyObject>(yourList);
Instantiate a new set-based collection HashSet. Don't forget to implement equals and hashcode for MyObject.
Good Luck!
If object order is insignificant
If the order is not important, you can put the elements of the list into a Set:
Set<MyObject> mySet = new HashSet<MyObject>(yourList);
The duplicates will be removed automatically.
If object order is significant
If ordering is significant, then you can manually check for duplicates, e.g. using this snippet:
// Copy the list.
ArrayList<String> newList = (ArrayList<String>) list.clone();
// Iterate
for (int i = 0; i < list.size(); i++) {
for (int j = list.size() - 1; j >= i; j--) {
// If i is j, then it's the same object and don't need to be compared.
if (i == j) {
continue;
}
// If the compared objects are equal, remove them from the copy and break
// to the next loop
if (list.get(i).equals(list.get(j))) {
newList.remove(list.get(i));
break;
}
System.out.println("" + i + "," + j + ": " + list.get(i) + "-" + list.get(j));
}
}
This will remove all duplicates, leaving the last duplicate value as original entry. In addition, it will check each combination only once.
Using Java 8
Java Streams makes it even more elegant:
List<Integer> newList = oldList.stream()
.distinct()
.collect(Collectors.toList());
If you need to consider two of your objects equal based on your own definition, you could do the following:
public static <T, U> Predicate<T> distinctByProperty(Function<? super T, ?> propertyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(propertyExtractor.apply(t));
}
(by Stuart Marks)
And then you could do this:
List<MyObject> newList = oldList.stream()
.filter(distinctByProperty(t -> {
// Your custom property to use when determining whether two objects
// are equal. For example, consider two object equal if their name
// starts with the same character.
return t.getName().charAt(0);
}))
.collect(Collectors.toList());
Futhermore
You cannot modify a list while an Iterator (which is usually used in a for-each loop) is looping through an array. This will throw a ConcurrentModificationException. You can modify the array if you are looping it using a for loop. Then you must control the iterator position (decrementing it while removing an entry).
Or http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html if you need sort-order..
EDIT: What about deriving from http://docs.oracle.com/javase/6/docs/api/java/util/TreeSet.html, it will allow you to pass in a Comparator at construction time. You override add() to use your Comparator instead of equals() - this will give you the flexibility of creating different sets that are ordered according to your Comparator and they will implement your "Equality"-Strategy.
Dont forget about equals() and hashCode() though...

Categories