The root cause of your problem is the semantics of equals(Object)
and hashCode()
for array objects as specified by the Java Language Specification, and the javadocs for java.lang.Object
.
Basically:
- The
equals(Object)
method for an array type is specified to have the same semantics as the ==
operator.
- The
hashCode()
for an array type is specified to return the identity hashcode value for the object.
This means that two distinct arrays are never equal according to the equals
object. It also means that assigning one of the elements of an array will never make the array equal to another.
The semantics of HashSet
are defined in terms of equals(Object)
. That means that the answer to your question:
HashSet is not sensitive to content of array?
... is, correct: HashSet
is not sensitive to the content of an array.
Your example
Now lets look at your example:
HashSet<int[][]> x = new HashSet<int[][]>();
int a[][] = {{1, 2}, {3, 4}};
x.add(a);
a[0][0] = 2;
a[0][1] = 1;
System.out.println(x.contains(a));
This returns true
because the array that you put into the HashSet
is the same array that you tested for. As explained above, the content of the array is irrelevant. HashSet relies on the equals(Object)
method, and for an array type that tests object identity; i.e. if the array objects are the same object.
"The contract" ...
But suppose that you did this:
HashSet<List<Integer>> x = new HashSet<List<Integer>>();
List<Integer> a = new ArrayList<>();
a.append(1);
a.append(2);
x.add(a);
a.set(0, 3);
System.out.println(x.contains(a));
what is going to happen now?
Answer: BAD THINGS!
The problem is that equals(Object)
and hashCode()
for ArrayList
are sensitive to the content of the array. But what we have done here is to "violate the contract" of how you are supposed to deal with objects in a HashSet
. You are not supposed to modify an object that is a member of a hash set in such a way that its hashCode
value changes.
If you violate the contract for equals / hashcode while an object is in a HashSet
(or is the key of a HashMap
or Hashtable
), then the object is liable to get lost in the data structure.
As a general rule, it is a bad idea to use mutable objects as hash keys.
This is the point that various comments have made been making. It is a very important point ... though it is not actually the fundamental problem with your example, as you wrote it.
Fixing your example
So how can we make your example work; i.e. do what (I think) you are really trying to do here?
This is for a simplified version with a 1-D array:
public List<Integer> makeSealedList(Integer ... values) {
return Collections.immutableList(Arrays.asList(values.clone()));
}
HashSet<List<Integer>> x = new HashSet<List<Integer>>();
List<Integer> a = makeSealedList(1, 2);
List<Integer> b = makeSealedList(1, 2);
List<Integer> c = makeSealedList(3, 2);
x.add(a);
System.out.println(x.contains(a)); // prints true
System.out.println(x.contains(b)); // prints true
System.out.println(x.contains(c)); // prints false
But note that this only works for "constant arrays", and I have deliberately encapsulated them to ensure that our lists are constant.
If you want to be able to be able to change an array while it is in the hashset and have the hashset automatically notice the change and rehash the array based on its new ... that is not supported by any of the Java SE collection types. And I don't think it is implementable without a whole bunch of extra infrastructure.
(The practical solution to that would be to remove the "array" from set, update it, and then add it back again.)