下面我们来一一讲讲,当然,重点会讲一下HashSet去重。
1、首先,还是让大家看看一个简洁的Java集合框架结构体系图:
2、看看这些接口和类有什么特征。
Collection(接口)
特征(单列集合): 不唯一,无序
List(子接口)
特征(单列集合): 不唯一,有序
Set(子接口)
特征(单列集合): 唯一,无序
Map(接口)
特征(双列集合): 键值对
key--->valuemap.put("userName",uName);key是拿的Set的特性,而value是拿的Collection的特性。
HashSet
特征:底层由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。无索引,唯一,无序。最大的优势就是去重。
* 无参构造方法
*/publicNews(){}/** * 有参构造方法 *@paramid *@paramtitle *@paramauthor */publicNews(intid, String title, String author){super();this.id = id;this.title = title;this.author = author;}}
4、测试代码:
package com.javaxyz.test;import java.util.HashSet;import java.util.Iterator;import java.util.Set;import org.junit.Test;import com.javaxyz.equals.News;/**
* @ClassName:HashSetDemo.java
* @Description:Set的运用
* @Author:DongGaoYun
* @AuthorEnglishName:Andy
* @QQ:1050968899
* @WeiXin:QingYunJiao
* @WeiXinGongZhongHao: JavaForum
* @Date:2019-10-25
* @Version:1.0 HashSet 优势: 去重 特有功能:
*
*/publicclassHashSetDemo1 {// 需求:// 增加新闻元素// 获取新闻总数// 操作HashSet容器,移除元素// 判断是否包含此元素// 打印输出四种方式//忽略警告@SuppressWarnings("all")// public static void main(String[] args) {@Testpublic void testSet() {// 创建对象// 创建多态的形式,要注意的点:只能调用父类与子类重写的方法,子类特有方法无法调用Setlist= new HashSet();// 多态的形式。//1.父子关系 2.重写方法 3.父类的引用指向子类对象Newsnews1 = newNews(1,"张卓1","green1");Newsnews2 = newNews(2,"张卓2","green2");Newsnews11 = newNews(2,"张卓2","green2");Newsnews3 = newNews(3,"张卓3","green3");Newsnews4 = newNews(4,"张卓4","green4");Newsnews5 = newNews(5,"张卓5","green5");Newsnews6 = newNews(6,"张卓6","green6");Newsnews7 = newNews(7,"张卓7","green7");Newsnews8 = newNews(8,"张卓8","green8");// 插入数据list.add(news1);list.add(news2);list.add(news3);// 注意set是无序的,没有索引,所以报错// list.add(1,news4);// 插入数据list.add(news6);list.add(news11);// 总条数System.out.println(list.size());// 操作HashSet容器,移除元素/*
* list.remove(0); list.remove(news1);
*/// 判断是否包含此元素System.out.println(list.contains(news7));// listSystem.out.println("-------第一种打印输出方法---start----");System.out.print(list);System.out.println("-------第一种打印输出方法---end----");System.out.println();//System.out.println("-------第二种打印输出方法-普通for--start----");// 普通for不能用/*
* for (int i = 0; i < list.size(); i++) { News newss = (News)
* list.get(i); System.out.println("News [id=" + newss.getId() + "," +
* " title=" + newss.getTitle() + "," + " author=" + newss.getAuthor() +
* "]"); }
*///System.out.println("-------第二种打印输出方法---end----");System.out.println();System.out.println("-------第二种打印输出方法-增强for--start----");for(Object object :list) {Newsnewss = (News) object;System.out.println("News [id="+ newss.getId() +","+" title="+ newss.getTitle() +","+" author="+ newss.getAuthor()+"]");}System.out.println("-------第二种打印输出方法---end----");System.out.println();System.out.println("-------第三种打印输出方法-转换成数组--start----");/**
* toArray Object[] toArray() 返回包含此 collection 中所有元素的数组。如果 collection
* 对其迭代器返回的元素顺序做出了某些保证,那么此方法必须以相同的顺序返回这些元素。 返回的数组将是“安全的”,因为此 collection
* 并不维护对返回数组的任何引用。(换句话说,即使 collection
* 受到数组的支持,此方法也必须分配一个新的数组)。因此,调用者可以随意修改返回的数组。
*
* 此方法充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。
*
* 返回: 包含此 collection 中所有元素的数组
*/Object[] obj =list.toArray();for(Object object : obj) {Newsnewss = (News) object;System.out.println("News [id="+ newss.getId() +","+" title="+ newss.getTitle() +","+" author="+ newss.getAuthor()+"]");}System.out.println("-------第三种打印输出方法---end----");System.out.println();System.out.println("-------第四种打印输出方法-迭代方式--start----");/**
* iterator Iterator<E> iterator()
*
* boolean hasNext() 如果仍有元素可以迭代,则返回 true。
*
* E next() 返回迭代的下一个元素。
*
* void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
*/Iterator it =list.iterator();while(it.hasNext()) {Newsnewss = (News) it.next();System.out.println("News [id="+ newss.getId() +","+" title="+ newss.getTitle() +","+" author="+ newss.getAuthor()+"]");}System.out.println("-------第四种打印输出方法---end----");}}
5、输出结果:
5false-------第一种打印输出方法---start----[News [id=1,title=张卓1,author=green1], News [id=2,title=张卓2,author=green2], News [id=3,title=张卓3,author=green3], News [id=2,title=张卓2,author=green2], News [id=6,title=张卓6,author=green6]]-------第一种打印输出方法---end-----------第二种打印输出方法-增强for--start----News [id=1,title=张卓1,author=green1]News [id=2,title=张卓2,author=green2]News [id=3,title=张卓3,author=green3]News [id=2,title=张卓2,author=green2]News [id=6,title=张卓6,author=green6]-------第二种打印输出方法---end-----------第三种打印输出方法-转换成数组--start----News [id=1,title=张卓1,author=green1]News [id=2,title=张卓2,author=green2]News [id=3,title=张卓3,author=green3]News [id=2,title=张卓2,author=green2]News [id=6,title=张卓6,author=green6]-------第三种打印输出方法---end-----------第四种打印输出方法-迭代方式--start----News [id=1,title=张卓1,author=green1]News [id=2,title=张卓2,author=green2]News [id=3,title=张卓3,author=green3]News [id=2,title=张卓2,author=green2]News [id=6,title=张卓6,author=green6]-------第四种打印输出方法---end----
6、从上面的打印中可以看出:
Set的输出是无序的;
Set虽然说具有去重的功能,但是自定义对象增加到Set集合或HashSet集合中,还是有重复的对象,怎么办呢?有什么玄机呢?为什么没有去重呢?我们还是看看java的系统类String,我们在String类中把相同的对象增加进去会去重吗?我们来试试。具体代码如下:
publicclass HashSetDemo2_String {// 需求:// 增加新闻元素// 获取新闻总数// String类,如果在HashSet容器增加元素,如果增加的元素是相同的,就会去重。@SuppressWarnings("all")// public static void main(String[] args) {@TestpublicvoidtestSet() {// 创建对象// 创建多态的形式,要注意的点:只能调用父类与子类重写的方法,子类特有方法无法调用// Set list=new HashSet();//多态的形式。//1.父子关系 2.重写方法 3.父类的引用指向子类对象HashSet list =newHashSet();Strings1 =newString("abc");Strings2 =newString("abc");Strings3 =newString("abc");// 增加元素list.add(s1);// 注意:当向set集合中存储相同元素时,add(obj)方法返回的是false.list.add(s2); list.add(s3);// 获取元素总数System.out.println(list.size());System.out.println();//打印输出System.out.println(list);}}
7、输出结果:
1[abc]
8、很显然,java系统类在向Set或HashSet集合里增加元素时,已经完美的去重了。那我们就看看String类里重写了哪些方法,我们来借鉴一下!惊奇的发现了String重写Object超类中的两个方法:一个是equals方法(前面介绍过:Java入门-Java学习路线扩展课程:equals的使用),另一个是hashCode方法。源码如下:
重写equals方法
publicbooleanequals(ObjectanObject) {if(this== anObject) {returntrue; }if(anObjectinstanceofString) {StringanotherString = (String) anObject;intn = value.length;if(n == anotherString.value.length) {charv1[] = value;charv2[] = anotherString.value;inti =0;while(n-- !=0) {if(v1[i] != v2[i])returnfalse; i++; }returntrue; } }returnfalse;}
重写hashCode方法
publicinthashCode(){inth = hash;if(h ==0&&value.length >0) {charval[] =value;for(inti =0; i
9、原来如此,String类用属性value值作为底层数据来计算hashCode的,即相同的value就一定会有相同的哈希值。如果value值相同,那么调用equals方法比较也是相等的;反过来不一定成立。它不保证相同的哈希值一定就是相同的对象。
10、在Java中,String类有个别对象的属性值不同,但是哈希值相同,比如字符串"gdejicbegh"与字符串"hgebcijedg"具有相同的哈希值-801038016。代码如下:
HashSet list = new HashSet();Strings1= new String("gdejicbegh");Strings2= new String("hgebcijedg");System.out.println(s1.hashCode()==s2.hashCode());System.out.println(s1.hashCode());System.out.println(s2.hashCode());
11、输出结果:
true-801038016-801038016
12、结论如下:
如果equals比较后相等,则hashCode一定会相等。 更多免费资料请加微信公众号:javaforum
如果hashCode的哈希值相等,equals就不一定相等,注意:它不保证相同的哈希值一定就是相同的对象。
13、小伙伴们是不是有种豁然开朗的感觉?我们来改造一下News.java类,改造代码如下:
* 无参构造方法
*/publicNews() {}/** * 有参构造方法 * *@paramid *@paramtitle *@paramauthor */publicNews(int id, String title, String author) {super();this.id = id;this.title = title;this.author = author;}/*
* 为什么把prime变量值初始化为31,有以下几个原因:更多免费资料请加微信公众号:javaforum
* 1.31这个数字不大也不太小
* 2.31是一个奇数,也是一个质数,即只能被1和本身整除的数。
* 3.如果选择偶数,乘2相当于移位运算可能导致溢出,数据会丢失
* 4.31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的JVM可以自动完成这种优化。 *
*/@Overridepublicint hashCode() {finalint prime =31;int result =1;result = prime * result + ((author ==null) ?0: author.hashCode());result = prime * result + id;result = prime * result + ((title ==null) ?0: title.hashCode());System.out.println("执行的hashCode是:"+result);returnresult;}/*
* 在我的博文中已经讲过: Java入门-Java学习路线扩展课程:equals的使用 更多免费资料请加微信公众号:javaforum
*/@Overridepublicboolean equals(Object obj) {//提升效率 判断传入的对象与本对象是否是同一个对象if(this== obj)returntrue;if(obj ==null)returnfalse;if(getClass() != obj.getClass())//比较两个对象的字节码文件是否是同一个字节码returnfalse;News other = (News) obj;//向下转型if(author ==null) {if(other.author !=null)returnfalse;}elseif(!author.equals(other.author))returnfalse;if(id != other.id)returnfalse;if(title ==null) {if(other.title !=null)returnfalse;}elseif(!title.equals(other.title))returnfalse;returntrue;}}
14、测试代码:
package com.javaxyz.test;import java.util.HashSet;import java.util.Iterator;import java.util.Set;import org.junit.Test;import com.javaxyz.equals.News;/**
* @ClassName:HashSetDemo.java
* @Description:Set的运用
* @Author:DongGaoYun
* @AuthorEnglishName:Andy
* @QQ:1050968899
* @WeiXin:QingYunJiao
* @WeiXinGongZhongHao: JavaForum
* @Date:2019-10-25
* @Version:1.0 HashSet 优势: 去重 特有功能:
*
*/publicclassHashSetDemo1 {// 需求:// 增加新闻元素// 获取新闻总数// 操作HashSet容器,移除元素// 判断是否包含此元素// 打印输出四种方式// String类,如果在HashSet容器增加元素,如果增加的元素是相同的,就会去重。@SuppressWarnings("all")// public static void main(String[] args) {@Testpublic void testSet() {// 创建对象// 创建多态的形式,要注意的点:只能调用父类与子类重写的方法,子类特有方法无法调用Setlist= new HashSet();// 多态的形式。//1.父子关系 2.重写方法 3.父类的引用指向子类对象String s1 = newString("gdejicbegh");String s2 = newString("hgebcijedg");System.out.println(s1.hashCode()==s2.hashCode());System.out.println(s1.hashCode());System.out.println(s2.hashCode());// list.add(s1);// list.add(s2);//System.out.println(list.size());Newsnews1 = newNews(1,"张卓1","green1");Newsnews2 = newNews(2,"张卓2","green2");Newsnews11 = newNews(2,"张卓2","green2");Newsnews3 = newNews(3,"张卓3","green3");Newsnews4 = newNews(4,"张卓4","green4");Newsnews5 = newNews(5,"张卓5","green5");Newsnews6 = newNews(6,"张卓6","green6");Newsnews7 = newNews(7,"张卓7","green7");Newsnews8 = newNews(8,"张卓8","green8");// 插入数据list.add(news1);list.add(news2);list.add(news3);// 注意set是无序的,没有索引,所以报错 更多免费资料请加微信公众号:javaforum// list.add(1,news4);// 插入数据list.add(news6);//注意:自定义对象重写equals和hashCode方法后,如果是重复元素,这时就会返回falseSystem.out.println(list.add(news11));// 总条数System.out.println(list.size());// 操作HashSet容器,移除元素/*
* list.remove(0); list.remove(news1);
*/// 判断是否包含此元素System.out.println(list.contains(news7));// listSystem.out.println("-------第一种打印输出方法---start----");System.out.print(list);System.out.println("-------第一种打印输出方法---end----");System.out.println();//System.out.println("-------第二种打印输出方法-普通for--start----");// 普通for不能用/*
* for (int i = 0; i < list.size(); i++) { News newss = (News)
* list.get(i); System.out.println("News [id=" + newss.getId() + "," +
* " title=" + newss.getTitle() + "," + " author=" + newss.getAuthor() +
* "]"); }
*///System.out.println("-------第二种打印输出方法---end----");System.out.println();System.out.println("-------第二种打印输出方法-增强for--start----");for(Object object :list) {Newsnewss = (News) object;System.out.println("News [id="+ newss.getId() +","+" title="+ newss.getTitle() +","+" author="+ newss.getAuthor()+"]");}System.out.println("-------第二种打印输出方法---end----");System.out.println();System.out.println("-------第三种打印输出方法-转换成数组--start----");/**
* toArray Object[] toArray() 返回包含此 collection 中所有元素的数组。如果 collection
* 对其迭代器返回的元素顺序做出了某些保证,那么此方法必须以相同的顺序返回这些元素。 返回的数组将是“安全的”,因为此 collection
* 并不维护对返回数组的任何引用。(换句话说,即使 collection
* 受到数组的支持,此方法也必须分配一个新的数组)。因此,调用者可以随意修改返回的数组。
*
* 此方法充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。
*
* 返回: 包含此 collection 中所有元素的数组 更多免费资料请加微信公众号:javaforum
*/Object[] obj =list.toArray();for(Object object : obj) {Newsnewss = (News) object;System.out.println("News [id="+ newss.getId() +","+" title="+ newss.getTitle() +","+" author="+ newss.getAuthor()+"]");}System.out.println("-------第三种打印输出方法---end----");System.out.println();System.out.println("-------第四种打印输出方法-迭代方式--start----");/**
* iterator Iterator<E> iterator()
*
* boolean hasNext() 如果仍有元素可以迭代,则返回 true。
*
* E next() 返回迭代的下一个元素。 更多免费资料请加微信公众号:javaforum
*
* void remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。
*/Iterator it =list.iterator();while(it.hasNext()) {Newsnewss = (News) it.next();System.out.println("News [id="+ newss.getId() +","+" title="+ newss.getTitle() +","+" author="+ newss.getAuthor()+"]");}System.out.println("-------第四种打印输出方法---end----");}}
15、输出结果:
true-801038016-801038016false4false-------第一种打印输出方法---start----[News [id=1,title=张卓1,author=green1], News [id=2,title=张卓2,author=green2], News [id=6,title=张卓6,author=green6], News [id=3,title=张卓3,author=green3]]-------第一种打印输出方法---end-----------第二种打印输出方法-增强for--start----News [id=1,title=张卓1,author=green1]News [id=2,title=张卓2,author=green2]News [id=6,title=张卓6,author=green6]News [id=3,title=张卓3,author=green3]-------第二种打印输出方法---end-----------第三种打印输出方法-转换成数组--start----News [id=1,title=张卓1,author=green1]News [id=2,title=张卓2,author=green2]News [id=6,title=张卓6,author=green6]News [id=3,title=张卓3,author=green3]-------第三种打印输出方法---end-----------第四种打印输出方法-迭代方式--start----News [id=1,title=张卓1,author=green1]News [id=2,title=张卓2,author=green2]News [id=6,title=张卓6,author=green6]News [id=3,title=张卓3,author=green3]-------第四种打印输出方法---end----
16、由此可见,要想将自定义类的对象存入HashSet集合里去重,需要注意以下三点:
自定义类中必须同时重写equals()和hashCode()方法
hashCode(): 属性值相同的对象返回值必须相同, 属性值不同的返回值尽量不同(提高效率)
equals(): 属性值相同返回true, 属性值不同返回false,返回false的时候存储
17、HashSet去重原理:
使用HashSet集合调用add()方法存储对象之前,应该注意尽量使重写的hashCode()方法返回的值不同,减少调用equals方法,提升性能;在调用add方法时,系统会先调用对象的hashCode()方法得到一个哈希值, 然后在HashSet集合中比较是否有哈希值相同的对象
如果没有哈希值相同的对象就会直接存入HashSet集合
如果有哈希值相同的对象, 就和哈希值相同的对象逐个调用equals()方法进行比较,结果为false就直接存入, 为true则不存
来源:csdn
关注我,私信回复“资料”获取面试宝典《Java核心知识点整理.pdf》“,覆盖了JVM、锁、高并发、反射、Spring原理