04.集合
集合
泛型
-
JDK5引入的特性, 可以在编译阶段约束操作的数据类型, 并进行检查
-
泛型中不能写基本数据类型
-
指定泛型的具体类型后, 传递数据时, 可以传入该类类型或其子类型
-
如果不写泛型, 类型默认是Object
-
泛型可以在很多地方上定义
- 泛型类
- 当一个类中, 某个变量的数据类型不确定时, 就可以定义带有泛型的类
- 泛型方法
- 方法中形参类型不确定时使用
修饰符<类型> 返回值类型 方法名(类型 变量名) {}
- 泛型接口
修饰符 interface 接口名<类型> {}
- 可在实现类中实现泛型, 或在实现类中继续使用泛型, 在创建对象时再实现泛型
- 泛型类
-
泛型不具备继承性, 但数据具备继承性
-
泛型通配符
- ? 表示不确定的类型, 它可以进行类型的限定
- ? extends E : 表示可以传递E或者E所有的子类类型
- ? super E : 表示可以传递E或者E所有的父类类型
-
应用场景
- 如果我们定义类, 方法, 接口的时候, 如果类型不确定, 就可以定义泛型类, 泛型方法, 泛型接口
- 如果类型不确定, 但是能知道以后只能传递某个体系中的类型, 就可以使用泛型的通配符
Collection
-
Collection是单列集合的祖宗接口, 它的功能是全部单列集合都可以继承使用的
-
常用方法
boolean add(E e)
void clear()
boolean remove(E e)
boolean contains(Object obj)
- 底层是依赖equals方法进行判断是否存在的, 如果集合中存储的 是自定义对象, 也想通过contains方法来判断是否包含, 那么在Javabean类中, 一定要重写equals方法
boolean isEmpty()
int size()
-
Collection的遍历方式
-
迭代器遍历
- 不依赖索引
- 迭代器在Java中的类是Iterator, 迭代器是集合专用的遍历方式
- Collection集合获取迭代器
Iterator<E\> iterator()
: 返回迭代器对象, 默认指向当前集合的0索引
- Iterator中的常用方法
boolean hasNext()
: 判断当前位置是否有元素E next()
: 获取当前位置的元素, 并将迭代器对象移向下一个位置void remove()
- 注意点
- 报错NoSuchElementException
- 迭代器遍历完毕, 指针不会复位, 如果要二次遍历, 只能重新创建迭代器对象
- 循环中只能用一次next方法
- 迭代器遍历时, 不能用集合的方法进行增加或删除, 如果要删除, 要使用迭代器的remove方法
-
增强for遍历
- 增强for的底层就是迭代器, 为了简化迭代器的代码书写, JDK5之后出现
- 所有单列集合和数组才能用增强for进行遍历
- 细节
- 修改增强for的变量, 不会改变集合中原本的数据
-
Lambda表达式遍历
-
JDK8后出现
-
default void forEach(Consumer<? super T> action)
1
2
3
4
5
6
7
8
9
10// 匿名内部类写法
coll.forEach(new Consumer<String>() {
publlic void accept(String s) {
// s依次表示集合中的每一个元素
}
});
// Lambda写法
coll.forEach(s -> System.out.println(s));
-
-
List
-
添加的元素是有序, 可重复, 有索引
-
常用方法
void add(int index, E e)
E remove(int index)
E set(int index, E e)
E get(int index)
-
List集合的遍历方式
- 迭代器遍历
- 列表迭代器遍历
ListIterator<E> listIterator()
- 可用于给列表添加元素
void add(E e)
- 增强for遍历
- Lambda遍历
- 普通for循环
ArrayList
-
底层原理
- 利用空参创建的集合, 在底层创建一个默认长度为0的数组
- 添加第一个元素时, 底层会创建一个新的长度为10的数组
- 存满时, 会扩容1.5倍
- 如果一次添加多个元素, 1.5倍也放不下时, 则新创建数组的长度以实际为准
LinkedList
-
底层数据结构是双链表
-
特有方法
void addFirst(E e)
void addLast(E e)
E getFirst()
E getLast()
E removeFirst()
E removeLast()
Set
-
添加的元素是无序, 不重复, 无索引
-
Set集合的方法基本上与Collection的API一致
HashSet
-
无序, 不重复, 无索引
-
要求数据去重时使用
-
底层采用哈希表存储数据
-
哈希表组成
-
JDK8之前 : 数组 + 链表
-
创建一个默认长度为16, 默认加载因子为0.75 (当数组内元素数量为 数组长度 * 加载因子 时, 数组自动扩容) 的数组, 数组名为table
HashSet<String> hm = new HashSet<>();
-
根据元素的哈希值跟数组的长度计算出应存入的位置
int index = (数组长度 - 1) & 哈希值;
-
判断当前位置是否为null, 如果是null直接存入
-
如果位置不为null, 表示有元素, 则调用equals方法比较属性值
-
一样: 不存 ; 不一样: 存入数组, 形成链表
- JDK8以前: 新元素存入数组, 老元素挂在新元素下面
- JDK8以后: 新元素直接挂在老元素下面
-
-
JDK8开始 : 数组 + 链表 + 红黑树
- 当链表长度超过8, 且数组长度大于等于64时, 自动转换为红黑树
-
-
哈希值
- 根据hashCode方法算出来的int类型的整数
- 该方法定义在Object类中, 所有对象都可以调用, 默认使用地址值进行计算
- 一般情况下, 会重写hashCode方法, 利用对象内部的属性值计算哈希值
-
对象的哈希值特点
- 如果没有重写hashCode方法, 不同对象计算出的哈希值是不同的
- 如果已经重写hashCode方法, 不同的对象只要属性值相同, 计算出的哈希值就是一样的
- 在小部分情况下, 不同的属性值或者不同的地址值计算处理的哈希值也有可能一样 (哈希碰撞)
-
存储自定义对象时, 要重写hashCode方法
-
LinkedHashSet
-
有序, 不重复, 无索引
-
要求数据去重且存取有序时使用
-
这里的有序指存储和取出的元素顺序一致
-
原理: 底层数据结构依然是哈希表, 只是每个元素又额外多了一个双链表的机制记录存储顺序
TreeSet
-
可排序, 不重复, 无索引
-
底层基于红黑树的数据结构实现排序的, 增删改查性能都较好
-
默认规则
- 对于数值类型: Integer, Double, 默认按照从小到大的顺序进行排序
- 对于字符, 字符串类型: 按照字符在ASCII码表中的数字升序进行排序
-
TreeSet的两种比较方式
-
默认排序/自然排序: Javabean类实现Comparable接口指定比较规则
1
2
3
4
5
6
7
8
9
10
11
12// 在Javabean类实现Comparable接口, Comparable接口是带泛型的, 可在实现接口的时候就确定泛型, 泛型即为该Javabean类
// 在Javabean类中实现Comparable接口中的compareTo方法
public int compareTo(E e) {
// e: 表示已经在红黑树存在的元素
// this: 表示当前要添加的元素
// 返回值:
// 负数: 表示当前要添加的元素是小的, 存左边
// 正数: 表示当前要添加的元素是大的, 存右边
// 0: 表示当前要添加的元素已经存在, 舍弃
return 0;
} -
比较器排序: 创建TreeSet对象的时候, 传递比较器Comparator指定规则
1
2
3
4
5
6
7
8
9TreeSet<E> ts = new TreeSet<>(new Comparator<E>() {
public int compare(E e1, E e2) {
// e1: 表示当前要添加的元素
// e2: 表示已经在红黑树中的元素
// 返回值同上
retrun 0;
}
}); -
默认采用第一种, 当第一种排序无法满足时, 使用第二种
-
当两种同时存在时, 使用的是第二种
-
Map
-
特点
- 双列集合一次需要存一对数据, 分别为键和值
- 键不能重复, 值可以重复
- 键和值是一一对应的, 每一个键只能找到自己对应的值
- 键 + 值 这个整体称之为"键值对", 记作"Entry对象"
-
常用方法细节
V put(K key, V value)
: 添加数据- 在添加数据的时候, 如果键不存在, 那么直接把键值对对象添加到Map集合当中
- 在添加数据的时候, 如果键存在, 那么会把原有的键值对对象覆盖, 会把被覆盖的值进行返回
V remove(Object key)
: 根据键删除键值对元素- 返回被删除的值
-
Map的遍历方式
-
通过键找值
1
2
3
4
5
6// 获取所有的键, 把这些键放到一个单列集合当中
Set<String> keys = map.keySet();
// 遍历单列集合, 得到每一个键
for (String key : keys) {
String value = map.get(key);
} -
通过键值对遍历
1
2
3
4
5
6
7// 获取所有的键值对对象, 返回一个集合
Set<Map.Entry<String, String>> entries = map.entrySet();
// 遍历entryies这个集合, 去得到里面的每一个键值对对象
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
} -
通过Lambda表达式遍历
1
2
3// 利用Lambda表达式遍历
map.forEach((key, value) -> sout(key + "=" + value));
// 底层为通过键值对遍历
-
HashMap
-
无序, 不重复, 无索引
- 针对的是键
- 如果键存储的是自定义对象, 需要重写hashCode和equals方法; 如果值存储自定义对象, 不需要重写hashCode和equals方法
-
底层: 和HashSet一样
LinkedHashMap
-
有序, 不重复, 无索引
- 针对的是键
- 有序指的是保证存储和取出的元素顺序一致
-
原理: 底层数据结构是哈希表, 只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序
TreeMap
-
不重复, 无索引, 可排序
- 可排序: 对键进行排序
- 默认按键的从小到大进行排序, 也可以自己规定键的排序规则
-
增删改查性能较好
-
底层: 和TreeSet一样
-
代码书写两种排序规则
- 实现Comparable接口, 指定比较规则
- 创建集合时传递Comparator比较器对象, 指定比较规则
- 与TreeSet的规则一样
可变参数
-
方法形参的个数时可以发生变化的
-
格式:
数据类型...名字
-
例子:
public static int getSum(int...args) {}
-
细节
- 可变参数本质上就是一个数组
- 在方法的形参中最多只能写一个可变参数
- 在方法中, 如果除了可变参数外还有其他的形参, 那么可变参数要写在最后
Collections
-
集合工具类
-
常用方法
不可变集合
-
特点: 定义完成后不可以修改, 添加, 删除
-
如何创建: List, Set, Map接口中, 都存在of方法可以创建不可变集合
-
细节
- List: 直接用
- Set: 元素不能重复
- Map: 元素不能重复, 键值对数量最多是10个, 超过10个用ofEntry方法