当前位置: 代码迷 >> 综合 >> 软件构造 Blog-3 Mutable Class与Set的一些事
  详细解决方案

软件构造 Blog-3 Mutable Class与Set的一些事

热度:26   发布时间:2023-11-17 09:07:07.0

软件构造 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本来是周三开始写的,准备当晚就写完的,结果一直匆匆忙忙的拖到周六才写的差不多,而且也很水。感觉自己应该提高一下效率了。

  相关解决方案