第十三章 集合【开发的重点】
一、集合基础概念
1. 集合是Java中用于批量存储多个对象的容器工具,区别于数组,长度可动态变化。
2. 集合全部相关接口、工具类都存放于java.util包下,使用时需要导包。
3. 学习集合统一遵循四个核心维度,所有集合都按这个思路掌握:
(1)对应顶层接口的独有特性
(2)接口中定义的增删改查核心方法
(3)接口对应的底层实现类与底层原理
(4)集合支持的全部遍历写法
二、Collection单列集合体系
(一)顶层父接口:Collection
1. 核心特性:只能存储Object类型对象,是所有单列集合的父规范,无下标、无排序强制要求。
2. 通用核心方法:
◦ add(Object o):向集合末尾添加元素,返回添加成功标记,开发高频使用
◦ contains(Object o):判断集合内是否存在指定元素,存在返回true,不存在返回false
◦ remove(Object o):匹配元素并从集合中删除,完成删除返回true
◦ size():获取集合当前存储的有效元素总个数
3. 实现规则:该接口没有直接可实例化的实现类,所有功能由List、Set两个子接口落地实现
4. 遍历规则:自身不提供直接遍历方式,遍历逻辑由两个子接口拓展实现
(二)子接口一:List有序可重复集合
1. 核心特性:存储元素有序、自带数字下标、允许存入重复元素;下标取值区间0 ~ size()-1,开发基础重点。
2. 拓展独有方法(继承Collection全部方法,新增下标操作方法):
◦ add(Object o):无参下标重载,默认把元素追加到集合尾部
◦ add(int index, Object o):按下标位置插入元素,原下标及后续元素自动后移
◦ get(int index):根据下标读取对应位置元素,List查询核心方法
◦ remove(int index):按下标删除元素,返回被删除的原对象
◦ set(int index, Object o):修改指定下标元素,返回修改前旧元素
3. 三大常用实现类
1. ArrayList
底层基于动态数组实现,随机查询速度极快,中间增删元素需要移动数组,效率偏低;诞生于JDK1.2,多线程并发下不安全,单线程运行效率高。
底层扩容规则:创建对象时不会立刻初始化数组,首次调用add添加元素才分配空间;数组初始容量10,容量不足时每次扩容至原长度1.5倍。
2. Vector
底层同样是数组,查询快、中间增删慢;JDK1.1早期类,所有方法加同步锁,多线程安全,但单线程运行效率远低于ArrayList,日常开发极少使用。
3. LinkedList
底层双向链表结构,没有数组扩容开销,首尾、中间增删元素效率极高;随机下标查询需要从头遍历链表,查询速度慢;线程不安全,单线程场景增删优先选择。
面试拓展题:ArrayList与LinkedList的核心区别,从底层结构、查询效率、增删效率、线程安全四个维度作答。
4. List三种遍历方式
1. 下标for循环遍历:依靠get方法按下标依次读取
for(int i = 0; i < list.size(); i++){
Object obj = list.get(i);
}
2. 增强for循环(foreach):开发最常用简化遍历方式
for(数据类型 变量 : 集合对象){
// 变量直接代表集合内每一个元素
}
底层本质是迭代器遍历的语法糖。
3. 迭代器Iterator遍历(foreach底层原理)
◦ 第一步:通过集合iterator()方法获取迭代器对象
◦ 第二步:迭代器两个核心API
hasNext():判断迭代器中是否还有未读取元素,有返回true
next():读取下一个元素,指针向后移动一位
◦ 完整遍历模板
Iterator it = list.iterator();
while(it.hasNext()){
Object element = it.next();
}
(三)泛型
1. 泛型集合:给集合限定统一存储数据类型,编译阶段做类型校验,避免类型转换异常,是开发重点语法。
标准定义格式:List<存储类型> 集合名 = new ArrayList<存储类型>();
2. 泛型类语法规则
1. 定义语法:class 类名<泛型标识1,泛型标识2>{},通用标识T、E、K、V
2. 使用规范:创建对象时必须前后泛型类型保持一致;基础数据类型不能作为泛型,只能使用对应包装类;创建对象不写泛型默认全部识别为Object;多泛型标识需要一次性全部指定或全部省略。
(四)集合工具类Collections
1. 定位:专门操作Collection、List、Set集合的工具类,内部全部静态方法,无需创建对象直接类名调用。
2. 高频静态方法
◦ reverse(List list):反转List集合内元素存储顺序
◦ shuffle(List list):打乱List集合元素顺序,实现随机排列
◦ sort(List list):对集合内元素进行升序排序
3. 排序配套两种比较器
1. 内置比较器Comparable:需要存储的实体类实现该接口,重写compareTo方法,在实体类内部定义排序规则;返回正数、负数、0区分大小。
2. 外置比较器Comparator:无需修改实体类,排序时通过匿名内部类单独定义比较规则,灵活切换多种排序逻辑。
面试拓展题:区分Collection接口与Collections工具类
解析:Collection是单列集合顶层父接口,定义集合通用规范;Collections是静态工具类,提供操作集合的各类工具方法。
(五)子接口二:Set不可重复集合
1. 核心特性:无序存储、无下标索引、集合内元素不允许存储重复内容
2. 方法体系:完全继承Collection接口全部方法,没有拓展下标相关方法
3. 主流实现类
1. HashSet(开发、面试双重重点)
自定义对象存入HashSet去重规则:必须重写实体类hashCode()与equals()两个方法。
◦ hashCode重写规则:相同属性对象返回一致哈希值,不同属性尽量返回不同哈希值;常规做法把所有成员变量哈希值相加返回。
◦ equals重写规则:两个对象所有属性全部相等时返回true,判定为重复元素。
HashSet去重底层流程:存入对象先计算哈希值定位存储槽位;槽位无元素直接存入;槽位已有元素则调用equals对比全部属性,相等则拒绝存入,不相等则追加存储。
2. LinkedHashSet:HashSet子类,底层链表+哈希表,保留元素添加顺序,同时保证元素不重复;自定义对象存储同样需要重写hashCode与equals。
3. TreeSet:SortedSet接口实现类,自带自动排序;自定义对象存储需要实现Comparable接口重写compareTo,返回值为0判定为重复对象。
4. Set遍历方式:仅支持增强for循环、迭代器两种遍历,无下标无法使用普通for循环。
三、Map双列集合体系
(一)Map核心特性
1. 存储单位为键值对(key=value),一个集合元素包含两个对象
2. 键key:无序、无下标、全局唯一,不允许重复
3. 值value:无序、无下标,允许存入重复数据
(二)Map通用核心方法
• put(K key,V value):添加/修改键值对;键不存在直接新增;键已存在则覆盖旧值,返回被覆盖的旧value,开发高频方法
• remove(K key):根据指定键删除整条键值对,返回被删除的value
• get(K key):根据键查询对应value,Map读取数据核心方法
• containsKey(K key):判断集合是否存在指定key
• containsValue(V value):判断集合是否存在指定value
• size():获取当前集合键值对总数量
(三)Map五大实现类
1. HashMap(开发核心重点)
JDK1.2推出,线程不安全,运行效率高;key与value都允许存入null;自定义对象作为key时,实体类必须重写hashCode和equals保证键唯一,业务中一般使用String、Integer作为key。
2. Hashtable
JDK1.1早期类,所有方法加同步锁,多线程安全,运行速度慢;key、value都不允许存入null,开发基本淘汰。
面试拓展题:HashMap与Hashtable完整区别
3. Properties
Hashtable子类,强制要求key和value只能是String字符串,项目中常用来读取配置文件。
4. LinkedHashMap:HashMap子类,底层链表维护存储顺序,遍历顺序和添加顺序保持一致。
5. TreeMap:SortedMap接口实现类,会根据key自动排序;自定义对象作为key需要实现Comparable接口。
(四)Map三种遍历方式
1. 键遍历(开发最常用)
通过keySet()获取全部key组成Set集合,再遍历key,搭配get方法读取对应value
Set<K> keySet = map.keySet();
for(K key : keySet){
V value = map.get(key);
}
2. 值遍历
通过values()获取全部value组成Collection集合,仅能操作值,无法获取对应key
Collection<V> values = map.values();
for(V v : values){}
3. 键值对整体遍历
通过entrySet()把每一组键值对封装为Map.Entry对象存入Set,通过Entry的getKey()、getValue()直接获取键和值,效率高于键遍历
Set<Map.Entry<K,V>> entrySet = map.entrySet();
for(Map.Entry<K,V> entry : entrySet){
K k = entry.getKey();
V v = entry.getValue();
}
