Java基础day14笔记:集合框架|迭代器|ArrayList
2019/7/1 二刷结束留念
mark:重写equals和hashCode方法的情景~
01-集合框架(体系概述)
为什么会出现集合类呢?
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式。
那存到哪里去捏?
有两种存储的地方可以用,第一种是数组,第二种是集合。
对象多了用集合存,数据多了用对象存,像姓名年龄这些都是数据~
可是已经有数组啦,为什么还要有集合呢?
数组是固定长度的,集合是可变长度的。
而且,数组当中存储对象的时候,它只能存储同一种类型的对象,比如说全存Demo或者全存Person,因为数组定义的时候已经指定了数据类型啦。
而集合不是,对它来说只要是对象就行。先存一个new Person()进去,再存一个new Demo()进去,都没问题哒。
所以,集合的特点是:集合的长度是可变的,集合可以存储不同类型的对象。
集合作为一种容器,也划分成了很多种。对它们不断的进行共性抽取,就形成了一个体系,我们叫做集合框架。
嘿嘿,这个图看着理解一下:
![](https://img.haomeiwen.com/i14399848/520917e233694541.png)
这个体系里面包含很多内容。
框架产生之后,我们一般会先看什么呢?
是不是会先看顶层呀~因为顶层当中定义的是这个体系当中最共性、最基本的行为,把这个顶层看明白之后,这个体系的基本功能就了解了。
看完了顶层之后,找底层来用~
为什么呢?
有两点原因:
1,不断向上抽取出来的东西很有可能是不能创建对象的,它可能是抽象的。
2,创建子类对象,方法更多一些。
所以说,参阅顶层,创建底层。
而这个顶层,就叫Collection,收集、集合的意思。
集合框架是工具包中的一个成员,它在java.util这个包中。
![](https://img.haomeiwen.com/i14399848/c20f19900fa43c61.png)
Collection是一个接口。在不断向上抽取的过程中,全变成抽象辣~它有很多实现类,也有很多子接口。
![](https://img.haomeiwen.com/i14399848/f52a20a08e579871.png)
因为子接口太多,就不挨个讲啦。重点讲两个:List和Set。
把刚刚那个示意图简单填充了一下:
![](https://img.haomeiwen.com/i14399848/c069091d2b3eb150.png)
这些就是我们要重点讲的内容。
那么问题来啦,为什么要出现这么多种容器呢?
因为每一个容器对数据的存储方式都有不同。
这个存储方式称之为:数据结构。
02-集合框架(共性方法)
我们先从Collection开始学起~
Colletion是Colletion层次结构中的跟接口。
![](https://img.haomeiwen.com/i14399848/1159331d207444d2.png)
方法摘要:
![](https://img.haomeiwen.com/i14399848/706c7f5e69ba4854.png)
在上述方法中,我们发现了一些不清楚的数据类型:
![](https://img.haomeiwen.com/i14399848/b0434bf9d116254d.png)
![](https://img.haomeiwen.com/i14399848/c902c2346dd2d5e1.png)
我们看到的这些不懂的字母,就先把它们理解成Object类~
顶层方法看完该建立子类对象啦,我们先建立ArrayList对象吧~
![](https://img.haomeiwen.com/i14399848/29904f2527f6dcdb.png)
add方法的参数类型是Object,以便于接收任意类型对象。
集合中存储的都是对象的引用(地址)。就像这样,简单示意图:
![](https://img.haomeiwen.com/i14399848/2aa302cb69dd72f6.png)
打印集合对象:
![](https://img.haomeiwen.com/i14399848/0dbafdee8f1721a0.png)
![](https://img.haomeiwen.com/i14399848/90a968b982f70205.png)
删除元素:
![](https://img.haomeiwen.com/i14399848/a6f2228d44c26a3a.png)
运行结果:
![](https://img.haomeiwen.com/i14399848/a2ee16cf43b146ce.png)
写这句就全删啦(这个叫清空集合):
![](https://img.haomeiwen.com/i14399848/78b05030205ee08c.png)
![](https://img.haomeiwen.com/i14399848/e6a8285762ec7a6e.png)
判断元素:
![](https://img.haomeiwen.com/i14399848/e07c5813e3d5819d.png)
![](https://img.haomeiwen.com/i14399848/faa6077e711e2609.png)
取交集:
![](https://img.haomeiwen.com/i14399848/b3fee592bfdf0dd7.png)
![](https://img.haomeiwen.com/i14399848/b5b227b97d0952ae.png)
取交集,al1中只会保留和al2中相同的元素~
再试试removeAll,是将和al2中相同的元素删掉~
![](https://img.haomeiwen.com/i14399848/e47104c04677271b.png)
![](https://img.haomeiwen.com/i14399848/0b4b91ef6171e4ea.png)
03-集合框架(迭代器)
在Collection中,有一个方法叫iterator:
![](https://img.haomeiwen.com/i14399848/8bf5ea3cb0d2db61.png)
它是一个接口诶:
![](https://img.haomeiwen.com/i14399848/5277ceff7f530cbf.png)
这个接口中有三个方法:
![](https://img.haomeiwen.com/i14399848/efb4e9e243da1b50.png)
试着用一下:
Iterator it=al.iterator();//获取迭代器,用于取出集合中的元素。
![](https://img.haomeiwen.com/i14399848/fd43069308510aa4.png)
![](https://img.haomeiwen.com/i14399848/eecc190272ac7c8f.png)
我们发现它将集合中的前两个元素拿出来啦。
但是如果这个集合中元素很多,这样一个一个写就太麻烦了。
我们有更方便的方法呢:
![](https://img.haomeiwen.com/i14399848/14001836128ab703.png)
那么什么是迭代器呢?
其实就是集合的取出元素的方式。
对于取出这个动作,一个函数不能完全描述,需要用多个功能来体现,这种情况下,就需要将功能们封装到一个对象中去。
画个图解释一下迭代器的特点,这里有三种容器,每个容器中都有一个取出的对象:
![](https://img.haomeiwen.com/i14399848/5cd0e6ecda0452db.png)
而且,因为数据结构的不同,每个取出对象中的实现方式也不一样。
那么,这个取出就需要被描述一下,怎么描述呢?
通过一个类来完成,而这个类,就定义在了集合的内部。
为什么定义在集合内部呢?
因为元素就在集合内部,如果想要操作这些元素,是不是集合的内部类最方便啦。
一般取出分为两步:1,判断元素是否存在2,若存在则取出。
![](https://img.haomeiwen.com/i14399848/20a5afdcbf830d2b.png)
根据容器数据结构的不同,每个容器判断和取出的具体实现细节不一样。但是它们都是有共性内容:判断和取出,那么可以将共性抽取,形成一个接口,这个接口就是Iterator:
![](https://img.haomeiwen.com/i14399848/ce05f0ddef6b9dc2.png)
这些内部类都符合一个规则,该规则就是Iterator。
如何获取集合的取出对象呢?
通过一个对外提供的方法:iterator();
这个对外提供的方法就相当于娃娃机外面的操纵杆,而夹子就相当于迭代器,它是封装在娃娃机里面的,可以取出娃娃,只对外暴露了操纵杆这个方法,可以让人使用。不同的娃娃机,它们的夹子也不一样,有两个钩的,有五个钩的,实现方法都不一样,但它们都是夹子~
后面我们再定义新的容器,只要将它的取出方法实现Iterator接口就可以啦:
![](https://img.haomeiwen.com/i14399848/fe6169eec10878a6.png)
取出方式就这样被统一啦,再新加集合也不怕~
除了用while循环,它也可以用for循环写:
![](https://img.haomeiwen.com/i14399848/3756e48bc3a58cf7.png)
这两种循环有什么不同呢?
![](https://img.haomeiwen.com/i14399848/8fdf6b3399cece4a.png)
推荐用for循环,因为for循环结束后,it就释放了,不再占用内存空间,而while循环的话则依然占用内存空间。
04-集合框架(List集合共性方法)
![](https://img.haomeiwen.com/i14399848/2557c2ba9e720122.png)
Collection
|——List:元素是有序的,元素可以重复,因为该集合体系有索引。
|——Set:元素是无序,元素不可以重复,该集合当中没有索引。
我们先讲List。
我们去List类中看一看它的方法~共性方法就不说啦,它的特有方法有:
在指定位置插入指定元素:
![](https://img.haomeiwen.com/i14399848/79367f1de98f6e67.png)
通过索引获取元素:
![](https://img.haomeiwen.com/i14399848/52af117b16261810.png)
获取元素的索引:
![](https://img.haomeiwen.com/i14399848/228ed16a2edc28d8.png)
List集合特有的迭代器:
![](https://img.haomeiwen.com/i14399848/e83f30c328c30e1b.png)
移除指定元素:
![](https://img.haomeiwen.com/i14399848/efeee50a3701abe9.png)
改变指定位置的元素:
![](https://img.haomeiwen.com/i14399848/71a2ececba107881.png)
获取指定区间元素(包含头不包含尾):
![](https://img.haomeiwen.com/i14399848/a0414595c8db100a.png)
综上,凡是可以操作角标的方法都是List体系特有的方法。
总结一下:
![](https://img.haomeiwen.com/i14399848/0e3d7efe3229ce73.png)
接下来挨个演示一遍。
在指定位置添加元素:
![](https://img.haomeiwen.com/i14399848/34ed0de06bcb289f.png)
删除指定位置的元素:
![](https://img.haomeiwen.com/i14399848/13ceb0926d3ca2b2.png)
修改元素:
![](https://img.haomeiwen.com/i14399848/a2d0bae4e7ff0123.png)
通过角标获取元素:
![](https://img.haomeiwen.com/i14399848/edad4c963d81a654.png)
获取所有元素:
![](https://img.haomeiwen.com/i14399848/5cfd774b569bb450.png)
![](https://img.haomeiwen.com/i14399848/bbe8dc36f478c17b.png)
如上,List集合有自己特殊的取值方式,就是遍历。
我们用迭代器也可以实现这个功能:
![](https://img.haomeiwen.com/i14399848/e570e701a11e8e86.png)
05-集合框架(ListIterator)
通过indexOf获取对象的位置、获取指定区间的元素:
![](https://img.haomeiwen.com/i14399848/8b27196953e7da16.png)
![](https://img.haomeiwen.com/i14399848/67d2f09be801a7ee.png)
接下来看一下列表迭代器listIterator。
它和普通迭代器到底有什么不同呢?
看示例:
![](https://img.haomeiwen.com/i14399848/a9f3d100755df7dd.png)
程序运行过程中挂掉了:
![](https://img.haomeiwen.com/i14399848/abb5f7c6f27de197.png)
我们来看一下这个异常:
![](https://img.haomeiwen.com/i14399848/547b3bb7baa38487.png)
我们来分析一下:
![](https://img.haomeiwen.com/i14399848/8178ee839d599710.png)
所以就存在安全隐患啦。不能同时用多种方式操作同一种元素,否则可能会发生并发修改异常。
那该怎么解决呢?
要么全用集合的方法,要么全用迭代器的方法。
全用迭代器的方法:
![](https://img.haomeiwen.com/i14399848/8c10c69c8d19bd5b.png)
java02不是被删除了吗?为什么篮框部分还被打印了呢?
因为虽然这个元素的引用被移除了,可是在移除前它还被obj使用了,后面打印的是obj~
注意,刚刚用的都是迭代器的操作,做了判断、取出和移除,迭代器只能做这三个动作,添加和修改动作它做不了,有局限性。
不要怕~
它也知道自己有局限性,所以安排了一个小弟:
![](https://img.haomeiwen.com/i14399848/aaf5422bb88940b5.png)
列表迭代器ListIterator有指针,有角标,所以它的方法比它的爸爸Iterator多多啦:
![](https://img.haomeiwen.com/i14399848/9991b38d0799ff6b.png)
增删查改样样都可以呢。
List集合特有的迭代器:ListIterator是Iterator的子接口。
我们用一下~
添加:
![](https://img.haomeiwen.com/i14399848/158aa2e8af9aba05.png)
修改:
![](https://img.haomeiwen.com/i14399848/a3d8699cbbac074d.png)
除了hasNext,它还有hasPrevious:
![](https://img.haomeiwen.com/i14399848/8755b251cecef916.png)
试一下~
![](https://img.haomeiwen.com/i14399848/d50da814806a245d.png)
我们反向取一次:
![](https://img.haomeiwen.com/i14399848/69f82c4b44028fd1.png)
![](https://img.haomeiwen.com/i14399848/f9d0db36e0a6a810.png)
实际开发中逆向遍历用的少一些,正向遍历用的多一些。
ListIterator出现后,可以对集合进行在遍历过程中的增删改查。
06-集合框架(List集合具体对象的特点)
Colletion中List集合的共性方法说完以后,介绍一下List集合中常见的三个子类对象: ArrayList、LinkedList、Vetor。
这三个子类对象的出现,是因为底层的数据结构不一样而出现的,那么它们的底层到底是什么样的数据结构呢?换句话说,它们的底层到底是怎样对数据进行存储的呢?
ArrayList:底层的数据结构使用的是数组结构。特点:查询速度快。但是增删稍慢(元素不多还好,多了之后慢得就比较明显)。线程不同步。
![](https://img.haomeiwen.com/i14399848/2dc69e2f7bc9508e.png)
LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。
![](https://img.haomeiwen.com/i14399848/807b3b6ba53e54f3.png)
Vector:底层是数组数据结构。线程同步。(它1.0就出现了,那个时候还木有集合框架呢,而ArrayList是1.2出现的)被ArrayList替代了。
ArrayList和Vector中,建议用ArrayList,因为效率高,无论增删还是查询,Vector都稍慢。
那多线程怎么办呐?
可以自己加锁~
那我自己加锁不安全怎么办呢?
就怕你不会加所以Java的集合框架中提供了另外一个功能,专门帮助小笨笨来加锁的~
怎么加呢?“把你的不安全的给我,我给你个安全的。”好霸道总裁。。。
还有一个小问题~ArrayList和Vector都是数组数据结构,而数组数据结构的特点是它的长度是固定的,集合却是可变长度的,所以ArrayList和Vector是可变长度的数组。什么叫可变长度的数组呢?
ArrayList默认的长度是10:
![](https://img.haomeiwen.com/i14399848/c53fe63074013f00.png)
当长度超过之后,它会new一个新的数组,长度是多少呢?50%延长,变15。然后,把原来数组中的元素copy到新数组中来,再把新元素拼接到它们后面。
而Vector长度也是10,超过之后new的新数组是100%延长。
两者相比,ArrayList好一些,因为它既可以延长又可以节省空间。Vector就比较浪费空间啦。
总的来说Vector现在都不用了,像这张图中都没有Vector:
![](https://img.haomeiwen.com/i14399848/4850a0290a8d517e.png)
最常用的就是加粗的这四块。
07-集合框架(Vector中的枚举)
接下来讲一下Vector的特点。(不能理解既然它都不用了还要讲它的特点是为什么。。。)
Collection通用的方法:
![](https://img.haomeiwen.com/i14399848/acd6edf8cc4ac56e.png)
Vector特有的方法(一般带Element的都是它的特有方法):
添加元素:
![](https://img.haomeiwen.com/i14399848/76495ed100469f21.png)
查找元素:
![](https://img.haomeiwen.com/i14399848/635a39a682822224.png)
插入:
![](https://img.haomeiwen.com/i14399848/b14c6ba1f46027fe.png)
删除:
![](https://img.haomeiwen.com/i14399848/4cfb6c9cfb6100e8.png)
还有其他的:
![](https://img.haomeiwen.com/i14399848/de583570c71962ec.png)
![](https://img.haomeiwen.com/i14399848/23a6080079d85cf2.png)
我们来看一下这个方法:
![](https://img.haomeiwen.com/i14399848/42bf6d211dbbefa2.png)
这是什么东西鸭,木有见过呢:
![](https://img.haomeiwen.com/i14399848/7fb80479fdff9771.png)
点进去这个接口看一下(这个接口叫枚举):
![](https://img.haomeiwen.com/i14399848/2964e342e9c8ce2e.png)
那这个接口都有什么方法呢:
![](https://img.haomeiwen.com/i14399848/db5c5f6165346302.png)
话不多说,先玩一下~
![](https://img.haomeiwen.com/i14399848/79a16eefbc16947e.png)
![](https://img.haomeiwen.com/i14399848/b3b2c49ad4fe6618.png)
枚举就是Vector特有的取出方式。
发现枚举和迭代器很像。
其实,枚举和迭代是一样的。
因为枚举的名称以及方法的名称都过长,所以被迭代器取代了,枚举郁郁而终了。
但是IO当中有一个对象用到了枚举,因为那个对象也是1.0粗现的,那个时候没有迭代,只有枚举。
Vector和ArrayList的区别是什么?
Vector支持枚举,ArrayList没有枚举。
08-集合框架(LinkedList)
我们来看一看LinkedList里面有什么特有方法~
![](https://img.haomeiwen.com/i14399848/2e316186e138dbbf.png)
试一下,addFirst:
![](https://img.haomeiwen.com/i14399848/061ac61224f453df.png)
![](https://img.haomeiwen.com/i14399848/3da9c717a068bbdc.png)
再一次,addLast和getFirst、getLast:
![](https://img.haomeiwen.com/i14399848/b4987c804af8206d.png)
![](https://img.haomeiwen.com/i14399848/21adb2b80ab40ede.png)
removeFirst:
![](https://img.haomeiwen.com/i14399848/0bf5da893c69f27e.png)
![](https://img.haomeiwen.com/i14399848/0779005c3ac5e6fa.png)
get...()和remove...()方法的区别:get可以获取元素但不删除元素,remove获取元素,但是元素被删除。
想把里面元素全取出来~
正着取:
![](https://img.haomeiwen.com/i14399848/ebbff10a6671917f.png)
![](https://img.haomeiwen.com/i14399848/d350f99d90ea2c35.png)
倒着取:
![](https://img.haomeiwen.com/i14399848/4ed4d1c3d5944976.png)
![](https://img.haomeiwen.com/i14399848/3c93e6217b80ce5d.png)
removeFirst方法会抛出异常:
![](https://img.haomeiwen.com/i14399848/8307ec49e05e46d7.png)
到了后期,LinkedList方法升级了,它里面加了一个新的方法,pollFirst/Last:
![](https://img.haomeiwen.com/i14399848/87efd6529e1adcc2.png)
![](https://img.haomeiwen.com/i14399848/4acc0493f764d259.png)
如果列表为空它不再抛出异常,而是返回null,这是1.6版本出现的,以后推荐用这个方法喔。
获取但不移除元素的新方法:
![](https://img.haomeiwen.com/i14399848/b8638a350052a727.png)
插入元素的新方法:
![](https://img.haomeiwen.com/i14399848/c0d8c6ddd63e3cbe.png)
它们替代了之前旧的方法。
09-集合框架(LinkedList练习)
练习:使用LinkedList模拟一个堆栈或者队列数据结构。
![](https://img.haomeiwen.com/i14399848/9fe5c80b4fe444f9.png)
描述队列:
![](https://img.haomeiwen.com/i14399848/489d7b70490e786d.png)
封装好之后,我们就可以直接使用啦:
![](https://img.haomeiwen.com/i14399848/9416131448208843.png)
注意哦,像这样的封装很常见。
为什么要封装呢?我们直接用LinkedList不就可以完成了吗?
但是LinkedList只有自身的含义,叫做链表,我们想把它做成跟我们项目相关的一些容器,那么我们需要起一些特定的名称来用更方便一些,这个时候我们就会将原有的集合封装进我们的描述当中,并且对外提供一个自己更容易识别的方法名称。
好啦,存完之后该取啦:
![](https://img.haomeiwen.com/i14399848/c7dc59fce5123ea9.png)
![](https://img.haomeiwen.com/i14399848/633001eabd9af0dd.png)
10-集合框架(ArrayList练习)
练习:去除ArrayList中的重复元素。
思路:创建一个新容器,将旧容器的东西一个个放进去,放的时候判断它是否已存在在新容器~
代码:
![](https://img.haomeiwen.com/i14399848/2034565a90386844.png)
![](https://img.haomeiwen.com/i14399848/25bf1893ffb44477.png)
![](https://img.haomeiwen.com/i14399848/cb4eef3e5f47eb97.png)
接下来删除重复的:
![](https://img.haomeiwen.com/i14399848/e631027ebf580e8d.png)
![](https://img.haomeiwen.com/i14399848/f331890518e02dcb.png)
11-集合框架(ArrayList练习2)
练习:将自定义对象作为元素存储到ArrayList集合总,并去除重复元素。
比如:存人对象。将同姓名同年龄,视为同一个人,为重复元素。
思路:
![](https://img.haomeiwen.com/i14399848/57764cb0c846f680.png)
Person类:
![](https://img.haomeiwen.com/i14399848/3b3d217e3e18a01a.png)
主函数中:
![](https://img.haomeiwen.com/i14399848/d1b50c3ebc5581d6.png)
可是为什么这样写会出错呢?
![](https://img.haomeiwen.com/i14399848/fbc7e0b2be8c05e1.png)
因为在往容器里面存Person的时候,add的其实是Object,al.add(Object obj),因为只有它能接收任意对象。把Person传进去,其实相当于Object obj=new Person("lisi01",30);,这时Person就被提升为Object了。而后面的it.next()往回返的时候,返回的是Object,Object中并没有getName方法,多态编译失败。
应该改成这样:
![](https://img.haomeiwen.com/i14399848/28c2081e9f9f3191.png)
两句合一句:
![](https://img.haomeiwen.com/i14399848/6f0a59e45b3c4083.png)
下面一句话也改一下:
![](https://img.haomeiwen.com/i14399848/3b83a74e035f308d.png)
运行:
![](https://img.haomeiwen.com/i14399848/84785c640c7cc92b.png)
搞定。
接下来去重。
将之前写好的singleElement方法直接拿过来用:
![](https://img.haomeiwen.com/i14399848/7e4faa023a6c78d9.png)
我们可以看到,contains方法中其实调用了equals方法:
![](https://img.haomeiwen.com/i14399848/9796dffce1ae4751.png)
而默认的equals方法显然不适用于判断我们新建立的对象,所以我们要在Person类中重写equals方法:
![](https://img.haomeiwen.com/i14399848/6a533dde47939eec.png)
调用singleElement方法:
![](https://img.haomeiwen.com/i14399848/7c1d69320a51ebbc.png)
删除成功:
![](https://img.haomeiwen.com/i14399848/b8d4fa6c8c0c02f2.png)
List集合判断元素是否相同,依据的是元素的equals方法。 (其他的集合肯定不一样)
虽然主函数中没有直接调用equals方法,但是因为contains方法调用了equals方法,而singleElement方法又调用了contains方法。
这个问题搞明白之后,我们还会明白一系列其他问题。
先将重写的equals方法注释掉,我们现在删除一下其中一个元素:
![](https://img.haomeiwen.com/i14399848/5d77b90ad575f034.png)
但运行结果是false而且删除失败了:
![](https://img.haomeiwen.com/i14399848/1bd65e7053443b0d.png)
为什么呢?
因为要删除这个元素,就得现在容器中寻找这个元素。remove底层也调用了equals。而默认的equals方法是对比对象是否是同一个,new的Person是新的对象,地址值不相同,所以肯定会删除失败。
现在将重写后的equals方法去掉注释,再试一下:
![](https://img.haomeiwen.com/i14399848/941101d287697096.png)
成功啦。
12-集合框架(HashSet)
讲完了List,那么ArrayList和LinkedList该用哪一个呢?
如果元素中涉及了频繁的增删操作,用LinkedList。
如果增删操作不频繁,可以选择用LinkList,也可以选择用ArrayList。
如果涉及了增删,同时又涉及了查询,建议使用ArrayList。因为其实频繁的增删操作并不多见,一般的情况下都是查询比较多。所以ArrayList作为一个最常见、最大频率使用的容器存在,当实在不知道该用谁,就用ArrayList。
List派系讲完啦,接下来讲Set派系。
|——Set:元素是无序(存入和取出的顺序不一定一致),元素不可以重复。(想重复找List,不重复找Set)
|——HashSet:底层数据结构是哈希表。
|——TreeSet:
看一下Set接口的方法,发现Set集合的功能和Colletion是一致的。Colletion的方法我们之前都讲过啦,所以就不重复讲啦。
![](https://img.haomeiwen.com/i14399848/fcbf7777b971dfc5.png)
每个对象都有哈希值,那什么是哈希表呢?
示例:
![](https://img.haomeiwen.com/i14399848/772819fb96351255.png)
发现它们都有哈希值:
![](https://img.haomeiwen.com/i14399848/b6829872c95afd24.png)
接下来讲讲什么是哈希表:
![](https://img.haomeiwen.com/i14399848/286b560324d4a013.png)
很简单,存放哈希值的表就叫哈希表。哈希表的顺序和元素的存放顺序无关,和哈希值的大小有关。而取元素的时候也是按哈希值来取。
那会不会有哈希值相同的情况呢?
若哈希值相同,会判断哈希值所指的对象是否相同,若不同,会在这个哈希值上再向下顺延一个,表中有两个这个哈希值(不知道我理解的对不对,如果相同,那该怎么找对应的对象呢):
![](https://img.haomeiwen.com/i14399848/0c7940bcb959fd01.png)
验证无序:
![](https://img.haomeiwen.com/i14399848/4912a8d14558ed67.png)
![](https://img.haomeiwen.com/i14399848/eeac92ffaeba9118.png)
验证唯一性:
![](https://img.haomeiwen.com/i14399848/070d2209cc51cd9a.png)
![](https://img.haomeiwen.com/i14399848/9ccef9206c572bb5.png)
13-集合框架(HashSet存储自定义对象)
还和11中的例子一样,存储Person到HashSet中。
将那个例子中的代码都复制了过来,主函数中这样写:
![](https://img.haomeiwen.com/i14399848/b1907fdfa5ae12b3.png)
![](https://img.haomeiwen.com/i14399848/27dbd02ed8c7f0ac.png)
我们再试试存入重复的Person对象,发现成功了:
![](https://img.haomeiwen.com/i14399848/9526b7904011867b.png)
![](https://img.haomeiwen.com/i14399848/e69c3ded93126e23.png)
为什么即使重写了equals方法,还是会出现重复呢?
因为现在往里面存的四个对象都是独立的,都有独立的哈希值,都存入了哈希表。
而我们现在要做的,就是覆盖Person的哈希值方法,建立Person对象自己的哈希值。
怎么建立哈希值呢?
你的判断条件是什么,我就依据你的条件来建立哈希值。比如这里是按照姓名和年龄来判断对象是否重复的,那我们就按照姓名和年龄来建立哈希值。
代码:
![](https://img.haomeiwen.com/i14399848/0f0fdc2eff2d81b1.png)
![](https://img.haomeiwen.com/i14399848/7ba9f37245b7c6c6.png)
注意,只有当哈希值相同时,才会调用equals方法对比内容是否相同。
|——Set:元素是无序(存入和取出的顺序不一定一致),元素不可以重复。(想重复找List,不重复找Set)
|——HashSet:底层数据结构是哈希表。
HashSet是如何保证元素唯一性的呢?
是通过元素的两个方法,hashCode和equals来完成。
如果元素的HashCode值相同,才会判断equals是否为true。
如果元素的hashcode值不同,不会调用equals。
|——TreeSet:
所以注意,在开发中,只要描述的对象要往哈希集合中存时,一般都会复写hashCode和equals。
还有注意,为了保证哈希值的唯一性,一般会给这里乘个数字(两个对象万一两部分加起来正好是同一个值就重复了):
![](https://img.haomeiwen.com/i14399848/713d368dd3a3dd7a.png)
14-集合框架(HashSet判断和删除的依据)
注意,HashSet对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCode和equals方法,而且先依赖hashCode,再依赖equals。
ArrayList判断元素是否存在,以及删除等操作,只依赖equals。
这个原因全都在数据结构上,数据结构不同,它依赖的方法也不一样。