在前面的博文中,小编主要简单介绍了java集合中的总体框架,以及list接口中典型的集合ArrayList和LinkedList,接着,我们来看set的部分集合,set集合和数学意义上的集合没有差别,作为集合,可以容纳多个元素,而且,集合里面没有重复的元素,Set集合是Collection的子集,Set集合与Collection基本相同,没有提供任何额外的方法,只是Set不允许包含重复的元素,今天这篇博文小编主要介绍Set集合中的HashSet,小编会通过简单的demo来介绍set集合的特点,以及equals和hashCode方法,最后看一下HashSet的底层源码以及与hashMap的对比`(*∩_∩*)′,请小伙伴们多多指教`(*∩_∩*)′。我们知道Set的集合是无序、不可重复的集合,首先,我们来看一下HashSet,HashSet是set集合中用的最多的,so,我们来看下面的一个小例子:
package j2se.demo; import java.util.HashSet; public class SetTest1 { public static void main(String[] args) { HashSet set = new HashSet(); set.add("a"); set.add("b"); set.add("c"); set.add("d"); System.out.println(set); } }运行如下所示:
通过这个例子我们可以看出来集合的第一个特点,无序,我们添加的时候,顺序是abcd,出来的时候是dbca,这是集合的第一个特点;我们再向集合里面添加一个a,如下所示:
package j2se.demo; import java.util.HashSet; public class SetTest1 { public static void main(String[] args) { HashSet set = new HashSet(); set.add("a"); set.add("b"); set.add("c"); set.add("d"); set.add("a"); System.out.println(set); } }运行效果如下所示:
我们来看一下,到底是谁没有添加进去呢?编写相关代码,如下所示:
package j2se.demo; import java.util.HashSet; public class SetTest1 { public static void main(String[] args) { HashSet set = new HashSet(); System.out.println(set.add("a")); set.add("b"); set.add("c"); set.add("d"); System.out.println(set.add("a")); System.out.println(set); } }运行如下所示:
通过上面的截图,我们知道,第一个元素加进去了,第二个元素没有加进去,通过上面的小例子,我们知道,集合有两个特点,一个是无序,一个是无重复,接着,我们再来新建一个class,命名为SetTest2,编写代码如下所示:
package jihe; import java.util.HashSet; public class SetTest2 { public static void main(String[] args) { HashSet set = new HashSet(); set.add(new People("zhangsan")); set.add(new People("lisi")); System.out.println(set); } } class People{ String name; public People(String name){ this.name= name; } }运行如下所示:
通过结果我们知道,打印出来两个对象,但是我们无法确定第一个就是zhangsan这个对象,第二个就是lisi这个对象。我们再来添加一个,如下所示:
package jihe; import java.util.HashSet; public class SetTest2 { public static void main(String[] args) { HashSet set = new HashSet(); set.add(new People("zhangsan")); set.add(new People("lisi")); set.add(new People("zhangsan")); System.out.println(set); } } class People{ String name; public People(String name){ this.name= name; } }运行如下所示:
三个对象的地址不一样,所以可以添加进来,接着,我们来修改代码,如下所示:
Package jihe; import java.util.HashSet; public class SetTest2 { public static void main(String[] args) { HashSet set = new HashSet(); // set.add(new People("zhangsan")); // set.add(new People("lisi")); // set.add(new People("zhangsan")); People p1 = new People("zhangsan"); set.add(p1); set.add(p1); System.out.println(set); } } class People{ String name; public People(String name){ this.name= name; } }运行效果如下所示:
虽然引用了两个对象,但是实际上是一个对象,接着,我们来修改代码部分,如下所示:
package jihe; import java.util.HashSet; public class SetTest2 { public static void main(String[] args) { HashSet set = new HashSet(); // set.add(new People("zhangsan")); // set.add(new People("lisi")); // set.add(new People("zhangsan")); // People p1 = new People("zhangsan"); // // set.add(p1); // set.add(p1); String s1 = new String("a"); String s2 = new String("a"); set.add(s1); set.add(s2); System.out.println(set); } } class People{ String name; public People(String name){ this.name= name; } }运行如下所示:
对象是两个,但是内容是一样的;上面的demo,小编主要介绍了set集合中的添加一个元素的相关操作,通过查看api文档,我们发现出现了一个关键字equals,接着,我们来看,关于Object类的equals方法的特点,ps:这里的无序是指x和y两个对象是否equals。
关于Object类的equals方法的特点:
a、自反性:x.equals(x)应该返回true。
b、对称性:x.equals(y)为true。
c、传递性:x.equals(y)为true并且y.equals(z)为true,那么x.equals(z)也应该为true。
d、一致性:x.equals(y)的第一次调用true,那么x.equals(y)的第二次、第三次、第n次调用也应该为true,前提田间是在比较之间没有修改x也没有修改y。
e、对于非空引用x,x.equals(null)返回false。
当我们override equals方法的时候,同时我们也要override HashCode方法,so我们来看HashCode方法的特点:
a、在Java应用的一次执行过程当中,对于同一个对象的hashCode方法的多次调用,他们应该返回同样的值,前提是该对象的信息没有发生变化。
b、对于两个对象来说,如果使用equals方法比较返回true,那么这两个对象的hashCode值一定是相同的。
c、对于两个对象来说,如果使用equals方法比较返回false,那么这两个对象的hashCode值不要求一定不同,可以相同,可以不同,但是如果不同则可以提高应用的性能。
d、对于Object类来说,不同的Object对象的hashCode值是不同的,Object类的hashCode值表示的是对象的地址。
当我们使用HashSet的时候,hashCode方法就会得到调用,判断已经存储在集合中的对象的hashCode值是否一致,如果不一致,直接加进去,如果一致,再进行equals方法的比较,equals方法如果返回true,表示对象已经添加进去了,就不会增加新的对象,反之,加进去,特别需要注意的是,如果我们重写了equals方法,那么也要重写hashCode方法,反之亦然。通过分析,我们知道,判断一个对象能否放入到集合里面,是通过hashCode以及equals方法来共同完成的,接着,我们再来看一个demo,如何将我们自定义的类,放到集合当中,相同的名字就不能放入到集合中,我们需要做的就是重写equals方法和hashCode方法,新建class,取名为SetTest3,编写相关代码,如下所示:
package jihe; import java.util.HashSet; public class SetTest3 { public static void main(String[] args) { HashSet set = new HashSet(); } } class Student{ String name; public Student(String name){ this.name=name; } public int hashCode(){ return this.name.hashCode(); } public boolean equals(Object obj){ if(this==obj){ return true; } if(null !=obj && obj instanceof Student){ Student s = (Student)obj; if(name.equals(s.name)) { return true; } } return false; } }接着,编写SetTest里面的代码,如下所示:
import java.util.HashSet; public class SetTest { public static void main(String[] args) { HashSet set = new HashSet(); Student s1 = new Student("dingguohua"); Student s2 = new Student("dingguohua"); set.add(s1); set.add(s2); System.out.println(set); } }运行效果如下所示:
通过这个demo,我们知道,对象不同,内容相同,所以添加不进去,这是在实际应用中,很常用的一种方式,我们不使用object提供的hashcode和equals方法,转而,使用自己的实现,在实际应用中,我们向集合中添加元素的时候,我们都是根据内容而不是根据地址决定的。
HashSet的底层部分源码
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { // 使用 HashMap 的 key 保存 HashSet 中所有元素 private transient HashMap<E,Object> map; // 定义一个虚拟的 Object 对象作为 HashMap 的 value private static final Object PRESENT = new Object(); ... // 初始化 HashSet,底层会初始化一个 HashMap public HashSet() { map = new HashMap<E,Object>(); } // 以指定的 initialCapacity、loadFactor 创建 HashSet // 其实就是以相应的参数创建 HashMap public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<E,Object>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(initialCapacity , loadFactor); } // 调用 map 的 keySet 来返回所有的 key public Iterator<E> iterator() { return map.keySet().iterator(); } // 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数 public int size() { return map.size(); } // 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空, // 当 HashMap 为空时,对应的 HashSet 也为空 public boolean isEmpty() { return map.isEmpty(); } // 调用 HashMap 的 containsKey 判断是否包含指定 key //HashSet 的所有元素就是通过 HashMap 的 key 来保存的 public boolean contains(Object o) { return map.containsKey(o); } // 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap public boolean add(E e) { return map.put(e, PRESENT) == null; } // 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素 public boolean remove(Object o) { return map.remove(o)==PRESENT; } // 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素 public void clear() { map.clear(); } ... }
对于Hash来说,她是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素,因此HashSet的实现比较简单,通过源码我们可以看出来,HashSet的实现只是封装了一个HashMap对象来存储所有的集合元素,如果放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个 Object 对象。HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。
HashSet && HashMap
在比较她们两个之前,我们来分别看一下什么是HashSet以及HashMap:
什么是HashSet?
通过前面的介绍,我们知道HashSet实现的是Set接口,她有两个特点,无序和无重复,在存储HashSet的时候,要先确保对象重写equals和hashCode方法,这样才能比较对象的值是否相等,所以确保set中没有存储相等的对象,如果我们没有重写过这两个方法, 即public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会立即返回false,如果成功添加返回true。
什么是HashMap?
通过java集合框架这篇博文,我们知道HashMap实现了Map这个接口,Map接口是对键值对进行映射,Map中不允许有重复的键,Map接口又有个基本的实现HashMap和TreeMap,TreeMap保存了对象的排列次序,而HashMap则不能,HashMap允许键和值为null,HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap的时,能保证只有一个线程更改Map;
public Object put(Object Key,Object value)方法用来将元素添加到map中。接着,我们来对比一下HashSet和HashMap,看看她们之间的区别。
小编寄语:该博文,小编主要简单介绍了Set集合中的HashSet,从Set集合的基本特点开始入手。通过简单的demo来介绍set集合的特点,以及equals和hashCode方法,最后看一下HashSet的底层源码以及与hashMap的对比,,她是基于HashMap实现的,HashSet底层采用HashMap来保存所有元素,因此HashSet的实现比较简单,通过源码我们可以看出来,HashSet的实现只是封装了一个HashMap对象来存储所有的集合元素,java集合,未完待续......