软件构造 Blog-4 Mutable类与Set的一些事
今天上课的时候,Mr.Wang提到这么一点(意思类似但是具体的表述可能有所不同):有些mutable的对象,加入Set之后,如果改变其值,会出现找不到该值的情况。并且在下课后,用了一段代码更加仔细的表述了一下这个问题。(伪)代码如下:
StringBuilder a = new StringBuilder("a"); Set s = new HashSet(); s.add(a); System.out.println(s.contains(a)); ===>true a.append("b"); System.out.println(s.contains(a)); ===>true List<String> list = new ArrayList<>(); list.add("a"); Set<List<String>> set = new HashSet<List<String>>(); set.add(list); System.out.println(set.contains(list)); ==>true list.add("b"); System.out.println(set.contains(list)); ==>false
在刚下课的时候其实就和室友讨论了一下这个问题,认为就是hashCode的问题,但是没有进行深入的实验,后来Mr.Wang的说法证实了我们的猜想。然而在给出的例子里使用的都是已经定义好的mutable的类型,不好直接说明情况,然后出于一种好奇,我又忙(bu)里(xie)偷(shi)闲(yan)的用代码说明一下,于是我先写了下面的代码:
public class testMutable {
public static void main(String [] args){
String label = "name";
Set<TestClass> setInstance = new HashSet<>();
TestClass testClass1 = new TestClass(label);
//先加入mutable的对象
setInstance.add(testClass1);
System.out.println(setInstance.contains(testClass1));
String something = "something";
//更改mutable对象的field
testClass1.add(something);
System.out.println(setInstance.contains(testClass1));
}
}
class TestClass{
private final String label;
public Set<String> set;
//此处是一个mutable的类
public TestClass(String label){
this.label = label;
set = new HashSet<>();
}
public String getLabel(){
return this.label;
}
public void add(String something){
set.add(something);
}
}
测试的结果为
true true
出现这个结果是跟StringBuilder
的结果是一致的,但和Lisr
的结果不一样。由于猜测是因为hashCode
的问题,因此我重写了TestClass
类的hashCode
方法。
重写的方法如下:
@Override
public int hashCode(){
int result = 17;
result = result * 31 + label.hashCode();
result = result * 31 + set.hashCode();
return result;
}
然后的测试结果就变成了
true false
可以看到这样的结果就可以说明hashCode
方法对于HashSet
判断是否出现有着重要的关系。但是如果我不重写hashCode
方法,而是重写equals
方法会不会有相同的效果呢。
注释掉已写的hashCode
方法,又写了下面的一个equals
方法。
@Override
public boolean equals(Object obj) {
if(obj == this)
return true;
if(obj instanceof TestClass) {
TestClass testClass = (TestClass) obj;
if(testClass.getLabel().equals(this.getLabel())) {
if(testClass.set == this.set)
return true;
if(testClass.set.size() != this.set.size())
return false;
else {
return this.set.containsAll(testClass.set);
}
}
return false;
}
return false;
}
测试结果变成了
true true
于是,我们可以说hashCode确实在HashSet
的判断重复和是否存在有着很大的作用。但是,我又发现了下面一个问题(这表明不想让我写实验的事实)我将测试的主函数改成了下面的样子:
public class testMutable {
public static void main(String [] args){
String label = "name";
Set<TestClass> setInstance = new HashSet<>();
TestClass testClass1 = new TestClass(label);
setInstance.add(testClass1);
System.out.println("Contains testClass1: " + setInstance.contains(testClass1));
String something = "something";
testClass1.add(something);
System.out.println("After add something: "+setInstance.contains(testClass1));
System.out.println("set has " + setInstance.size());
TestClass testClass2 = new TestClass(label);
testClass2.set = testClass1.set;
System.out.println("Add testClass2: " + setInstance.add(testClass2));
System.out.println("Contains testClass2: " + setInstance.contains(testClass2));
System.out.println("set has " + setInstance.size());
}
}
测试的结果就变成了
Contains testClass1: true
After add something: false
set has 1
Add testClass2: true
Contains testClass2: true
set has 2
在最开始的时候,我以为是一个错误。其实是因为我在增加testClass1
的时候由于已经更改过所以导致testClass1
的hashCode就改变了。这样我在第二次加入一个相同的哈希值的testClass2
的时候,才可以加进去。
其实HashSet
的底部是一个HashMap
来具体实现的,所以我们很多对于集合的理解都可以借用HashMap
的理解。在判断是不是相等的时候,其实先使用的是判HashCode是不是相同的然后再使用equals
的方法来进行。
更多详细的介绍,希望大家去Java Set集合的详解看一下,确实感觉看完之后会明白更多。
后记
这篇blog本来是周三开始写的,准备当晚就写完的,结果一直匆匆忙忙的拖到周六才写的差不多,而且也很水。感觉自己应该提高一下效率了。