第七章—集合
一、集合概述
1、集合与数组
数组定义完成并启动后,类型确定、长度固定。适合元素的个数和类型确定的业务场景,不适合做需要增删数据操作。数组可以存储基本类型和引用类型的数据。
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合非常适合做元素的增删操作。集合只能存储引用数据类型的数据。
2、集合体系结构
1)集合体系说明
集合分为Collection单列集合,Map双列结合。Collection单列集合,每个元素(数据)只包含一个值。Map双列集合,每个元素包含两个值(键值对)。
2)集合体系结构
3、集合对泛型的支持
集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型。注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。
二、Collocation集合
1、Collocation集合系列的特点
1)List系列集合:添加的元素是有序、可重复、有索引。
ArrayList、LinekdList :有序、可重复、有索引。
2)Set系列集合:添加的元素是无序、不重复、无索引。
HashSet: 无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引。
TreeSet:按照大小默认升序排序、不重复、无索引。
2、Collocation集合常用API
说明:Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。
方法名称 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public boolean isEmpty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数。 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
3、Collocation集合遍历方式
1)迭代器遍历
①说明
迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式。
②方法
方法名称 | 说明 |
---|---|
Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引 |
boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
E next() | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。 |
③格式举例
Iterator<String> it = lists.iterator();
while(it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
2)foreach / 增强for循环
①格式
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
}
②注意
增强for循环既可以遍历集合也可以遍历数组。
修改第三方变量的值不会影响到集合中的元素
3)lambda表达式
①Collection结合Lambda遍历的API
方法名称 | 说明 |
---|---|
default void forEach(Consumer<? super T> action): | 结合lambda遍历集合 |
②举例
Collection<String> lists = new ArrayList<>();
//API
lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//Lambda
lists.forEach(s -> {
System.out.println(s);
});
// lists.forEach(s -> System.out.println(s));
三、List集合
1、List集合特点
ArrayList、LinekdList :有序,可重复,有索引。
①有序:存储和取出的元素顺序一致
②有索引:可以通过索引操作元素
③可重复:存储的元素可以重复
2、List集合特有方法
List也都继承了Collection的功能。
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
3、List集合遍历方式
①迭代器
②增强for循环
③Lambda表达式
④for循环(因为List集合存在索引)
4、ArrayList集合与LinkList集合底层原理
1)ArrayList
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
2)LinkList
底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。
5、LinkList特有方法
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
6、集合的并发修改问题
1)说明
从集合中的一批元素中找出某些数据并删除,存在某些问题
由于集合顺序存储元素,当删除某元素时,集合内的元素会移动,导致一边删除,一边移动,使索引可能漏掉一些元素。
2)问题
迭代器遍历集合且直接用集合删除元素的时候出错。
List<String> list = new ArrayList<>();
list.add("Java");
list.add("JavaWeb");
list.add("JavaWeb");
list.add("Java");
list.add("HTML");
list.add("Web");
System.out.println(list);
// 迭代器遍历删除
// 报错
// Iterator<String> it = list.iterator();
// while(it.hasNext()) {
// String ele = it.next();
// if ("JavaWeb".equals(ele)) {
// list.remove("JavaWeb");
// }
// }
// System.out.println(list);
// 正确:迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
Iterator<String> it = list.iterator();
while(it.hasNext()) {
String ele = it.next();
if ("JavaWeb".equals(ele)) {
//使用迭代器删除当前位置的元素
it.remove();
}
}
System.out.println(list);
增强for循环遍历集合且直接用集合删除元素的时候出错。
// 报错
// for (String s : list) {
// if ("JavaWeb".equals(s)) {
// list.remove("JavaWeb");
// }
// }
// System.out.println(list);
Lambda表达式循环遍历集合且直接用集合删除元素的时候出错。
// 报错
// list.forEach( s -> {
// if ("JavaWeb".equals(s)) {
// list.remove("JavaWeb");
// }
// });
for 循环正序删除是可能会出错
// 出错:正序删除会发生漏删现象
// for (int i = 0; i < list.size(); i++) {
// String ele = list.get(i);
// if ("JavaWeb".equals(ele)) {
// list.remove("JavaWeb");
// }
// }
// System.out.println(list);
// 正确:倒序删除
for (int i = list.size()-1; i >= 0; i--) {
String ele = list.get(i);
if ("JavaWeb".equals(ele)) {
list.remove("JavaWeb");
}
}
System.out.println(list);
// 正确:删除一个元素后,索引倒退
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if ("JavaWeb".equals(ele)) {
list.remove("JavaWeb");
// 删除一个元素后,索引倒退
i--;
}
}
System.out.println(list);
7、泛型
1)说明
可以在编译阶段约束操作的数据类型,并进行检查。
集合体系的全部接口和实现类都是支持泛型的使用的。
2)格式
泛型可以在很多地方进行定义:
①类后面:泛型类
②方法申明上:泛型方法
③接口后面:泛型接口
// 格式:<数据类型>;
// 如
<String>;
注意:泛型只能支持引用数据类型。
3)优点
统一数据类型,把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
4)自定义泛型类
①格式
此处泛型变量 T 可以随便写为任意标识,常见的如 E、T、K、V 等。
// 修饰符 class 类名<泛型变量>{ ... }
// 例
public class MyArrayList<T> { }
②作用
编译阶段可以指定数据类型,类似于集合的作用。
③原理
把出现泛型变量的地方全部替换成传输的真实数据类型。
5)自定义泛型方法
①格式
// 修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
// 例
public <T> void show(T t) { }
②作用
方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
③原理
把出现泛型变量的地方全部替换成传输的真实数据类型。
6)自定义泛型接口
①格式
// 修饰符 interface 接口名称<泛型变量>{}
// 例
public interface Data<E>{}
②作用
泛型接口可以让实现类选择当前功能需要操作的数据类型
③原理
实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
7)通配符:?
①说明
? 可以在“使用泛型”的时候代表一切类型。
②注意
子类A 和 子类B 都继承了 父类T 但是 ArrayList 和 ArrayList 与 ArrayList 没有关系的。要想使用子类,就必须声明为泛型的上下限
8)泛型的上下限
① ? extends T: ?必须是 父类T 或者其子类 泛型上限
② ? super T: ?必须是 父类T 或者其父类 泛型下限
四、Set集合
1、特点
1)Set集合特点
①无序:存取顺序不一致
②不重复:可以去除重复
③无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
2)Set集合实现类特点
①HashSet : 无序、不重复、无索引。
②LinkedHashSet:有序、不重复、无索引。
③TreeSet:排序、不重复、无索引。
3)Set集合的功能上基本上与Collection的API一致。
2、HashSet元素无序原理
1)HashSet底层原理
HashSet集合底层采取哈希表存储的数据。
2)哈希表
哈希表是一种对于增删改查数据性能都较好的结构。
3)哈希表的组成
JDK8之前的,底层使用数组+链表组成
JDK8开始后,底层采用数组+链表+红黑树组成。
4)哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
Object类的API:public int hashCode():返回对象的哈希值
5)存储过程
①创建一个默认长度16的数组,默认加载因为0.75的数组,数组名table
②根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
③判断当前位置是否为null,如果是null直接存入
④如果位置不为null,表示有元素,则调用equals方法比较
⑤ 如果一样,则不存,如果不一样,则存入数组,
⑥ JDK 8中新元素挂在老元素下面,当链表长度超过8的时候,自动转换为红黑树。
⑦当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
6)优点
哈希表是一种对于增删改查数据性能都较好的结构,进一步提高了操作数据的性能。。
3、HashSet去重复原理
1)去重复过程
①创建一个默认长度16的数组,数组名table
②根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
③判断当前位置是否为null,如果是null直接存入
④如果位置不为null,表示有元素,则调用equals方法比较
⑤如果一样,则不存,如果不一样,则存入数组,
2)注意
如果希望Set集合认为2个内容一样的对象是重复的,必须重写对象的hashCode()和equals()方法
4、LinkedHashSet
1)特点
有序、不重复、无索引。
注:这里的有序指的是保证存储和取出的元素顺序一致
2)原理
底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
5、TreeSet
1)特点
不重复、无索引、可排序
可排序:按照元素的大小默认升序(有小到大)排序。
2)原理
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
3)注意
TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
4)默认规则
对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet无法直接排序。(需要自定义排序规则)
5)自定义排序规则
方式一:让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
方式二:TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
返回值规则:
①如果认为第一个元素大于第二个元素返回正整数即可。
②如果认为第一个元素小于第二个元素返回负整数即可。
③如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
五、Map集合
1、概述
①Map集合是一种双列集合,每个元素包含两个数据。
②Map集合的每个元素的格式:key=value(键值对元素)。
③Map集合也被称为“键值对集合”。
④Map集合整体格式:{key1=value1 , key2=value2 , key3=value3 , …}
2、特点
1)Map集合体系特点
①Map集合都是由键决定的。
②Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
③Map集合后面重复的键对应的值会覆盖前面重复键的值。
④Map集合的键值对都可以为null。
2)Map集合实现类特点
①HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
②LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
③TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。
3、Map集合常用API
方法名称 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
4、Map集合的遍历方式
1)键找值
先获取Map集合全部键的Set集合,遍历键的Set集合,然后通过键提取对应值。
方法名称 | 说明 |
---|---|
Set keySet() | 获取所有键的集合 |
V get(Object key) | 根据键获取值 |
2)键值对
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。遍历Set集合,然后提取键以及提取值。
方法名称 | 说明 |
---|---|
K getKey() | 获得键 |
V getValue() | 获取值 |
3)Lambda表达式
一种更简单、更直接的遍历集合的方式
方法名称 | 说明 |
---|---|
default void forEach(BiConsumer<? super K, ? super V> action) | 结合lambda遍历Map集合 |
maps.forEach((k , v) -> {
System.out.println(k +"----->" + v);
});
5、HashMap
①无序、不重复、无索引
②是Map里面的一个实现类,直接使用Map里面的方法
③HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,只是HashMap的每个元素包含两个值而已。
注:实际上,Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
6、LinkedHashMap
1特点
由键决定:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致
2)原理
底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
7、TreeMap
1)特点
①由键决定特性:不重复、无索引、可排序
②可排序:按照键数据的大小默认升序(有小到大)排序。只能对键排序。
③注意:TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序,TreeMap跟TreeSet一样底层原理是一样的。
2)自定义排序规则
①类实现Comparable接口,重写比较规则。
②集合自定义Comparator比较器对象,重写比较规则。
六、总结与补充知识
1、特点总结
集合 | 特点 |
---|---|
ArrayList、LinekdList | 有序、可重复、有索引。 |
HashSet | 无序、不重复、无索引 |
LinkedHashSet | 有序、不重复、无索引 |
TreeSet | 按照大小默认升序排序、不重复、无索引 |
HashMap | 无序,不重复,无索引 |
LinkedHashMap | 有序,不重复,无索引 |
TreeMap | 按照键排序,不重复,无索引 |
2、使用场景
集合 | 场景 |
---|---|
ArrayList | 元素可以重复,又有索引,索引查询要快 |
LinkedList | 元素可以重复,又有索引,增删首尾操作快 |
HashSet | 增删改查都快,但是元素不重复、无序、无索引 |
LinkedHashSet | 增删改查都快,但是元素不重复、有序、无索引 |
TreeSet | 对对象进行排序 |
3、可变参数
1)说明
可变参数用在形参中可以接收多个数据。
2)格式
// 数据类型...参数名称
// 如带有可变参数的方法
public static void outputNumber(int...number) {
System.out.println(number);
}
3)作用
传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组。
可变参数在方法内部本质上就是一个数组。
4)注意
①一个形参列表中可变参数只能有一个
②可变参数必须放在形参列表的最后面
4、Collections集合工具类
方法名称 | 说明 |
---|---|
public static boolean addAll(Collection<? super T> c, T… elements) | 给集合对象批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
public static void sort(List list) | 将集合中元素按照默认规则排序 |
public static void sort(List list,Comparator<? super T> c) | 将集合中元素按照指定规则排序 |
5、不可变集合
1)说明
就是不可被修改的集合,集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。
2)格式
方法名称 | 说明 |
---|---|
static List of(E…elements) | 创建一个具有指定元素的List集合对象 |
static Set of(E…elements) | 创建一个具有指定元素的Set集合对象 |
static <K , V> Map<K,V> of(E…elements) | 创建一个具有指定元素的Map集合对象 |
注:集合创建以后不能添加,不能删除,不能修改。如果集合要求元素不重复,则添加相同元素时也会报错!