type
status
date
slug
summary
tags
category
icon
password
集合
集合使用回顾
- ArrayList集合存储5个int类型元素
- ArrayList集合存储5个Person类型元素
概念
集合是java中提供的一种容器,可以用来存储多个数据。
集合和数组的区别:
- 数组的长度是固定的,集合的长度是可变的
- 集合中存储的元素必须是引用类型数据
集合继承关系图
- ArrayList的继承关系
查看ArrayList类,可以发现它继承抽象类AbstractList的同时实现接口List,而List接口又继承了Collection接口,Collection接口是最顶层集合接口。
- 集合继承体系
- List接口常用的子类有:ArrayList类、LinkedList类
- Set接口常用的子类有:HashSet类、LinkedHashSet类
在使用ArrayList类时,该类已经把所有抽象方法进行了重写,实现Collection接口的所有子类都会进行方法重写。
Collecton接口常用的子接口有:List接口、Set接口。
Collection的add方法
Collection的remove方法
迭代器
概述
Java中提供了很多个集合,它们在存储元素时,采用的存储方式不同,要取出这些集合中的元素,可以通过一种通用的获取方式来完成。
Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出,这种取出方式专业术语称为迭代。
每种集合的底层的数据结构不同,例如ArrayList是数组,LinkedList底层是链表,但是无论使用那种集合,都会判断是否有元素以及取出里面的元素的动作,Java提供的迭代器定义了统一的判断元素和取元素的方法。
实现原理
Iterator接口定义了两个抽象方法:
- boolean hasNext():判断集合中还有没有可以被取出的元素,如果有返回true
- next():取出集合中的下一个元素
代码实现
执行过程
迭代器的原理:
for循环的迭代写法:
集合迭代中的转型
在使用集合时,需要注意以下几点:
- 集合中存储其实都是对象的地址
- JDK1.5版本以后集合中可以存储基本数值,因为出现了基本类型包装类,它提供了自动装箱操作(基本类型对象),这样,集合中的元素就是基本数值的包装类对象
存储时提升了Object。取出时要使用元素的特有内容,必须向下转型。
注意:如果集合中存放的是多个对象,这时进行向下转型会发生类型转换异常。
Iterator接口也可以使用<>来控制迭代元素的类型的。
增强for循环遍历数组
增强for循环遍历集合
并发修改异常
集合和迭代器同时持有同一个对象,当集合在添加,和删除集合元素时(修改呢),迭代器并不知道,所以会发生并发修改异常。
注意:增强for也会产生并发修改异常。
如何解决:
- 使用普通for循环
- 使用listIterator:List特有,其他集合不能使用
ArrayList
数组可以保存多个元素,但在某些情况下无法确定到底要保存多少个元素,此时数组将不再适用,因为数组的长度不可变;为了保存这些数目不确定的元素,JDK中提供了一系列特殊的类,这些类可以存储任意类型的元素,并且长度可变,统称为集合。
ArrayList集合是程序中最常见的一种集合,它属于引用数据类型(类),在ArrayList内部封装了一个长度可变的数组,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。
创建对象
- 导包:import java.util.ArrayList;
- 创建引用类型的变量
下面给出8种基本数据类型所对应的引用数据类型表示形式:
基本数据类型 | 对应的引用数据类型表示形式 |
byte | Byte |
short | Short |
Int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
常用方法
方法声明 | 功能描述 |
boolean add(Object obj) | 将指定元素obj追加到集合的末尾 |
Object get(int index) | 返回集合中指定位置上的元素 |
int size() | 返回集合中的元素个数 |
案例如下:
强调一点,ArrayList集合相当于是一个长度可变的数组,所以访问集合中的元素也是采用索引方式访问,第一个元素存储在索引0的位置,第二个元素存储在索引1的位置,依次类推。
遍历
通过遍历,得到集合中每个元素,这是集合中最常见的操作,集合的遍历与数组的遍历很像,都是通过索引的方式。
补充方法
方法声明 | 功能描述 |
boolean add(int index, Object obj) | 在集合中指定index位置,添加新元素obj |
Object remve(int index) | 从集合中删除指定index处的元素,并返回该元素 |
void clear() | 清空集合中所有元素 |
Object set(int index, Object obj) | 用指定元素obj替代集合中指定index位置的元素 |
随机点名器案例
案例介绍
随机点名器,即在全班同学中随机的找出一名同学,打印这名同学的个人信息。它具备以下3个内容:
- 存储所有同学姓名
- 总览全班同学姓名
- 随机点名其中一人,打印到控制台
需求分析
对本案例进行分析,得出如下结果:
- 存储全班同学信息(姓名、年龄):将容器换成集合,集合中存的是Student类型
- 打印全班同学每一个人的信息(姓名、年龄):遍历集合
- 在班级总人数范围内,随机产生一个随机数,查找该随机数所对应的同学信息(姓名、年龄)
随机点名器明确地分为了三个功能,如果将多个独立功能的代码写到一起,则代码相对冗长,可以针对不同的功能可以将其封装到一个方法中,将完整独立的功能分离出来。
而在存储同学姓名时,如果对每一个同学都定义一个变量进行姓名存储,则会出现过多孤立的变量,很难一次性将全部数据持有;此时,可以采用ArrayList集合来解决多个学生信息的存储问题。
代码实现
库存管理案例
案例介绍
将原有的库存管理案例,采用更好的集合方式实现。
对下列功能进行方法封装:
- 打印库存清单功能
- 库存商品数量修改功能
- 退出程序功能
需求分析
管理员能够进行的操作有3项(查看、修改、退出),可以采用(switch)菜单的方式来完成;每一项功能操作,采用方法进行封装,增强程序的可读性。
代码实现
List接口
特点
- 它是一个元素存取有序的集合;例如,存元素的顺序是11、22、33,那么集合中,元素的存储就是按照11、22、33的顺序完成的
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素
- List接口的常用子类有:
- ArrayList集合
- LinkedList集合
特有方法
- 增加元素方法
- add(Object e):向集合末尾处,添加指定的元素
- add(int index, Object e) 向集合指定索引处,添加指定的元素,原有元素依次后移
- 删除元素删除
- remove(Object e):将指定元素对象,从集合中删除,返回值为被删除的元素
- remove(int index):将指定索引处的元素,从集合中删除,返回值为被删除的元素
- 替换元素方法
- set(int index, Object e):将指定索引处的元素,替换成指定的元素,返回值为替换前的元素
- 查询元素方法
- get(int index):获取指定索引处的元素,并返回该元素
迭代器的并发修改异常
运行上述代码会发生错误java.util.ConcurrentModificationException,这是因为在迭代过程中,使用了集合的方法对元素进行操作。导致迭代器并不知道集合中的变化,容易引发数据的不确定性。
并发修改异常解决办法:
在迭代时,不要使用集合的方法操作元素,可以使用ListIterator迭代器操作元素是;ListIterator的出现,解决了使用Iterator迭代过程中可能会发生的错误情况。
存储结构
- 栈结构:后进先出/先进后出,FILO(first in last out)
- 先进后出即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素;例如子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹
- 栈的入口、出口的都是栈的顶端位置
- 压栈:就是存元素;即把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置
- 弹栈:就是取元素;即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置
- 队列结构:先进先出/后进后出,FIFO(first in first out)
- 先进先出,即存进去的元素,要在后它前面的元素依次取出后,才能取出该元素;例如安检排成一列,每个人依次检查,只有前面的人全部检查完毕后,才能排到当前的人进行检查
- 队列的入口、出口各占一侧
- 数组结构:查询快,通过索引快速找到元素;增删慢,每次增删都需要开辟新的数组,将老数组中的元素拷贝到新数组中,开辟新数组耗费资源
- 查找元素快:通过索引,可以快速访问指定位置的元素
- 增删元素慢:
- 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置
- 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中
- 链表结构:查询慢,每次都需要从链头或者链尾找起;增删快,只需要修改元素记录的下个元素的地址值即可,不需要移动大量元素
- 多个节点之间,通过地址进行连接;例如多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了
- 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
- 增删元素快:
- 增加元素:只需要修改连接下个元素的地址即可
- 删除元素:只需要修改连接下个元素的地址即可
ArrayList集合特点
底层是可变数组,查找快,因为底层有索引,并且是连续。
LinkedList集合特点
底层采用链表结构,每次查询都要从链头或链尾找起,查询相对数组较慢,但是删除直接修改元素记录的地址值即可,不要大量移动元素。
LinkedList的索引决定是从链头开始找还是从链尾开始找;如果该元素小于元素长度一半,从链头开始找起,如果大于元素长度的一半,则从链尾找起。
LinkedList特有方法
Vector类特点
- Vector集合数据存储的结构是数组结构,为JDK中最早提供的集合,它是线程同步的
- Vector中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早期的迭代器,此接口Enumeration的功能与Iterator接口的功能是类似的
- Vector集合已被ArrayList替代,枚举Enumeration已被迭代器Iterator替代
Set接口
特点
- 它是个不包含重复元素的集合
- Set集合取出元素的方式可以采用:迭代器、增强for
- Set集合有多个子类,这里介绍HashSet、LinkedHashSet这两个集合
存储和迭代
哈希表的数据结构
加载因子是表中填入的记录数/哈希表的长度,例如哈希表长度是16,加载因子是0.75,代表其中存入16*0.75=12个元素;如果在存入第十三个(>12)元素,导致存储链子过长,会降低哈希表的性能,此时会扩充哈希表(再哈希),底层会开辟一个长度为原长度2倍的数组,把老元素拷贝到新数组中,再把新元素添加数组中。
存入元素数量>哈希表长度*加载因子就要扩容,因此加载因子决定扩容时机。
字符串对象的哈希值
哈希表的存储过程
存取原理:每存入一个新的元素都要走以下三步:
- 首先调用本类的hashCode()方法算出哈希值
- 在容器中找是否与新元素哈希值相同的老元素
- 如果没有直接存入
- 如果有转到第三步
- 新元素会与该索引位置下的老元素利用equals方法一一对比,一旦新元素.equals(老元素)返回true,停止对比,说明重复,不再存入,如果与该索引位置下的老元素都通过equals方法对比返回false,说明没有重复,存入
哈希表存储自定义对象
自定义对象重写hashCode和equals
LinkedHashSet集合
判断集合元素唯一的原理
ArrayList、HashSet判断对象是否重复的原理
ArrayList的contains方法底层依赖于equals方法。
ArrayList的contains方法会使用调用方法时,传入的元素的equals方法依次与集合中的旧元素所比较,从而根据返回的布尔值判断是否有重复元素;此时,当ArrayList存放自定义类型时,由于自定义类型在未重写equals方法前,判断是否重复的依据是地址值,所以如果想根据内容判断是否为重复元素,需要重写元素的equals方法。
HashSet的add/contains等方法判断元素是否重复原理
HashSet的add()方法和contains方法()底层都依赖 hashCode()方法与equals方法()。
Set集合不能存放重复元素,其添加方法在添加时会判断是否有重复元素,有重复不添加,没重复则添加。
HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode与equals方法的返回结果。规则如下:
- 先判断新元素与集合内已经有的旧元素的HashCode值
- 如果不同,说明是不同元素,添加到集合
- 如果相同,再判断equals比较结果。返回true则相同元素;返回false则不同元素,添加到集合
所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode与equals方法,则判断重复时,使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashcode与equals方法。
hashCode和equals方法的面试题
在 Java 应用程序执行期间:
- 如果根据equals(Object)方法,两个对象是相等的,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果
- 如果根据equals(java.lang.Object)方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果
- 两个对象不同(对象属性值不同),equals返回false:两个对象调用hashCode()方法哈希值相同
- 两个对象调用hashCode()方法哈希值不同:equals返回true
- 两个对象不同(对象属性值不同),equals返回false:两个对象调用hashCode()方法哈希值不同
- 两个对象调用hashCode()方法哈希值相同:equals返回true
所以说两个对象哈希值无论相同还是不同,equals都可能返回true。
Map接口
概述
通过查看Map接口,可以发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同:
- Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储
- Map中的集合,元素是成对存在的(理解为夫妻),每个元素由键与值两部分组成,通过键可以找对所对应的值
- Collection中的集合称为单列集合,Map中的集合称为双列集合
- 需要注意的是,Map中的集合不能包含重复的键,值可以重复,每个键只能对应一个值
- Map中常用的集合为HashMap集合、LinkedHashMap集合
常用集合概述
Map有多个子类,这里主要讲解常用的HashMap集合、LinkedHashMap集合。
- HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致;由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法
- LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构;通过链表结构可以保证元素的存取顺序一致,通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法
注意:Map接口中的集合都有两个泛型变量<K,V>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量<K,V>的数据类型可以相同,也可以不同。
常用方法
keySet方法
键找值方式:即通过元素中的键,获取键所对应的值。
操作步骤:
- 获取Map集合中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键
- 遍历键的Set集合,得到每一个键
- 根据键利用get(key)去Map找所对应的值
Entry
在Map类设计时,提供了一个嵌套接口Entry;Entry将键值对的对应关系封装成了对象,即键值对对象,这样在遍历Map集合时,就可以从每一个键值对(Entry)对象中获取对应的键与对应的值。
Entry是Map接口中提供的一个静态内部嵌套接口。
相关方法:
- getKey()方法:获取Entry对象中的键
- getValue()方法:获取Entry对象中的值
- entrySet()方法:用于返回Map集合中所有的键值对(Entry)对象,以Set集合形式返回
entrySet方法
键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值。
操作步骤:
- 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回
- 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对(Entry)对象
- 通过键值对(Entry)对象,获取Entry对象中的键与值
Map集合遍历方式增强for循环
注意:Map集合不能直接使用迭代器或者foreach进行遍历。但是转成Set之后就可以使用了。
HashMap集合存储和遍历
- 当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法
- 如果要保证map中存放的key和取出的顺序一致,可以使用LinkedHashMap集合来存放
LinkedHashMap的特点
Hashtable的特点
Collections工具类
集合嵌套
集合嵌套并不是一个新的知识点,仅仅是集合内容又是集合,如Collection集合嵌套、Collection集合与Map集合相互嵌套、Map集合嵌套。
- ArrayList嵌套ArrayList
- Map嵌套ArrayList
- Map集合嵌套
keySet遍历
entrySet遍历
集合继承体系的面向对象思想
- 接口:用来明确所有集合中该具有的功能,相当于在定义集合功能标准
- 抽象类:把多个集合中功能实现方式相同的方法,抽取到抽象类实现,具体集合不再遍写,继承使用即可
- 具体类:继承抽象类,实现接口,重写所有抽象方法,达到具备指定功能的集合。每个具体集合类,根据自身的数据存储结构方式,对接口中的功能方法,进行不同方式的实现
斗地主洗牌发牌案例
案例介绍
按照斗地主的规则,完成洗牌发牌的动作。
具体规则:
- 组装54张扑克牌
- 将54张牌顺序打乱
- 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
- 查看三人各自手中的牌(按照牌的大小排序)、底牌
手中扑克牌从大到小的摆放顺序:大王、小王、2、A、K、Q、J、10、9、8、7、6、5、4、3。
需求分析
斗地主的功能分析。
具体规则:
- 组装54张扑克牌
- 将54张牌顺序打乱
- 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
- 查看三人各自手中的牌(按照牌的大小排序)、底牌
需求分析:
- 准备牌,完成数字与纸牌的映射关系
使用双列Map(HashMap)集合,完成一个数字与字符串纸牌的对应关系(相当于一个字典)。
- 洗牌,通过数字完成洗牌发牌
- 发牌,将每个人以及底牌设计为ArrayList<String>,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌
- 存放的过程中要求数字大小与斗地主规则的大小对应
- 将代表不同纸牌的数字分配给不同的玩家与底牌
- 看牌,通过Map集合找到对应字符展示,通过查询纸牌与数字的对应关系,由数字转成纸牌字符串再进行展示
代码实现
首先,要修改java文件编码,由GBK修改为UTF-8,因为默认的字符编码GBK梅花、方片、黑桃、红桃(♠♥♦♣)等特殊字符。
准备牌
洗牌
发牌
看牌
- 作者:青山🌊
- 链接:http://cxuan.me/article/java-se-collection
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。