软件构造 Blog-3 Mutable类与Set的一些事
今天上课的时候,Mr.Wang提到这么一点(意思类似但是具体的表述可能有所不同):有些mutable的对象,加入Set之后,如果改变其值,会出现找不到该值的情况。并且在下课后,用了一段代码更加仔细的表述了一下这个问题。(伪)代码如下:
StringBuilder a = new StringBuilder("a");Set s = new HashSet();s.add(a); System.out.println(s.contains(a)); ===>truea.append("b");System.out.println(s.contains(a)); ===>trueList<String> list = new ArrayList<>();list.add("a");Set<List<String>> set = new HashSet<List<String>>();set.add(list);System.out.println(set.contains(list)); ==>truelist.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对象的fieldtestClass1.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
方法。
重写的方法如下:
@Overridepublic 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
方法。
@Overridepublic 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本来是周三开始写的,准备当晚就写完的,结果一直匆匆忙忙的拖到周六才写的差不多,而且也很水。感觉自己应该提高一下效率了。