集合概述
集合和数组都是容器。
数组的特点
- 数组定义完成并启动后, 类型确定、长度固定。
- 在进行增删数据操作的时候,数组是不太合适的,增删数据都需要放弃原有数组或者移位。
数组适合的场景
-
当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储。
集合是 Java 中存储对象数据的一种容器。
集合的特点
- 集合的大小不固定,启动后可以动态变化,类型也可以选择不固定。集合更像气球。
- 集合非常适合做元素的增删操作。
- 注意:集合中只能存储引用数据类型,如果要存储基本类型数据可以选用包装类。
集合适合的场景
-
数据的个数不确定,需要进行增删元素的时候
总结
- 数组和集合的元素存储的个数问题。
- 数组定义后类型确定,长度固定。
- 集合类型可以不固定,大小是可变的。
- 数组和集合存储元素的类型问题。
- 数组可以存储基本类型和引用类型的数据。
- 集合只能存储引用数据类型的数据。
- 数组和集合适合的场景。
- 数组适合做数据个数和类型确定的场景。
- 集合适合做数据个数不确定,且要做增删元素的场景。
Collection集合的体系特点
集合类体系结构
- Collection 单例集合,每个元素(数据)只包含一个值。
- Map 双列集合,每个元素包含两个值(键值对)。
- 注意:前期先掌握 Collection 集合体系的使用。
Collection 集合体系
Collection 集合特点
- List 系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinkedList:有序、可重复、有索引。
- Set 系列集合:添加的元素是无序、不重复、无索引。
- HashSet:无序、不重复、无索引;LinkedHashSet: 有序、不重复、无索引。
- TreeSet: 按照大小默认升序排序、不重复、无索引。
集合对于泛型的支持
-
集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类型
-
注意:集合和泛型只能支持引用数据类型,不支持基本数据类型,索引集合中存储的元素都认为是对象。
/** * @author : gxd * @date : 2022/6/23 10:30 * 目标:明确 Collection 集合体系的特点 */ public class CollectionTest1 { public static void main(String[] args) { // 有序、可重复、有索引 Collection list = new ArrayList(); list.add("Java"); list.add("Java"); list.add("Mybatis"); list.add(11); list.add(11); list.add(false); list.add(false); System.out.println(list); // 无需、不重复、无索引 Collection list1 = new HashSet(); list1.add("Java"); list1.add("Java"); list1.add("Mybatis"); list1.add(11); list1.add(11); list1.add(false); list1.add(false); System.out.println(list1); System.out.println("------------------------"); //Collection<String> list2 = new ArrayList<String>(); Collection<String> list2 = new ArrayList<>();// JDK 7 开始之后后面类型申明可以不写 list2.add("Java"); //list2.add(23); list2.add("黑马"); // 集合和泛型不支持基本数据类型,只能支持引用数据类型 //Collection<int> list3 = new ArrayList<>(); Collection<Integer> list3 = new ArrayList<>(); list3.add(23); list3.add(211); Collection<Double> list4 = new ArrayList<>(); list4.add(25.2); list4.add(1.2); list4.add(0.9); } }
总结
- 集合的代表是?
- Collection 接口。
- Collection 集合分了哪2大常用的集合体系?
- List 系列集合:添加的元素是有序、可重复、有索引。
- Set 系列集合:添加的元素是无序、不重复、无索引。
- 如何约定集合存储数据的类型,需要注意什么?
- 集合支持泛型。
- 集合和泛型不支持基本类型,只支持引用数据类型。
Collection 集合常用API
Collection 集合
- Collection 是单列集合的祖宗接口,它的功能是全部单例结合都可以继承使用的。
Collection API 如下:
/**
* @author : gxd
* @date : 2022/6/23 14:09
* 目标:掌握 Collection 以下API
* boolean add(E e);
* void clear();
* boolean isEmpty();
* int size();
* boolean contains(Object o);
* boolean remove(Object o);
* Object[] toArray();
* boolean addAll(Collection<? extends E> c);
*/
public class CollectionTest1 {
public static void main(String[] args) {
//HashSet:提价的元素是无需,不重复,无索引。
Collection<String> c = new ArrayList<>();
// 1、添加元素,添加成功返回 true。
c.add("Java");
c.add("HTML");
System.out.println(c.add("HTML"));
c.add("MySqL");
c.add("Java");
System.out.println(c.add("黑马"));
System.out.println(c);//[Java, HTML, HTML, MySqL, Java, 黑马]
// 2、清空集合的元素
//c.clear();
//System.out.println(c);//[]
// 3、判断集合是否为空,是空返回 true,反之。
System.out.println(c.isEmpty());
// 4、获取集合的大小。
System.out.println(c.size());
// 5、判断集合中是否包含某个元素。
System.out.println(c.contains("Java"));//true
System.out.println(c.contains("java"));//false
System.out.println(c.contains("黑马"));//true
// 6、删除某个元素:如果有多个重复元素默认删除前面的第一个!
System.out.println(c.remove("java"));//false
System.out.println(c);
System.out.println(c.remove("Java"));//true
System.out.println(c);
// 7、把集合转换成数组 [HTML, HTML, MySqL, Java, 黑马]
Object[] arrs = c.toArray();
System.out.println("数组:" + Arrays.toString(arrs));
System.out.println("---------------拓展----------------");
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection<String> c2 = new ArrayList<>();
c2.add("张三");
c2.add("李四");
//addAll()把c2的元素全部倒入到c1里面。
c1.addAll(c2);
System.out.println(c1);
System.out.println(c2);
}
}
Collection 集合的遍历方式
方式一:迭代器
迭代器遍历概述
- 遍历就是一个一个的把容器中的元素访问一遍。
- 迭代器在 Java 中的代表是 Iterator,迭代器是集合专用遍历方式。
Collection 集合获取迭代器
Iterator 中的常用方法
/**
* @author : gxd
* @date : 2022/6/23 15:01
* 目标:掌握Collection 集合的遍历方式一:迭代器
*/
public class CollectionTest1 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小赵");
lists.add("素素");
lists.add("灭绝");
System.out.println(lists);
// 1、得到你当前集合的迭代器对象。
Iterator<String> it = lists.iterator();
//String ele = it.next();
//System.out.println(ele);
//System.out.println(it.next());
//System.out.println(it.next());
//System.out.println(it.next());
//System.out.println(it.next());//NoSuchElementException 出现无此元素异常的错误
System.out.println("-----------------------");
// 2、定义 while 循环
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
}
迭代器执行流程
总结
- 迭代器的默认位置在哪里。
- Iterator iterator(): 得到迭代器对象,默认指向当前集合的索引0
- 迭代器如果取元素越界会出现什么问题。
- 会出现 NoSuchElementException 异常。
方式二:foreach / 增强for循环
增强 for 循环
- 增强 for 循环:既可以遍历集合也可以遍历数组。
- 它是 JDK5 之后出现的,其内部原理是一个 Iterator 迭代器,遍历集合相当于是迭代器的简化写法。
- 实现 Iterable 接口的类才可以使用迭代器和增强for,Collection 接口已经实现了 Iterable 接口。
格式
增强 for 修改变量
注意:修改第三方变量的值不会影响到集合中的元素。
/**
* @author : gxd
* @date : 2022/6/23 23:32
* 目标:掌握 foreach 和 增强for循环
*/
public class CollectionTest2 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小赵");
lists.add("素素");
lists.add("灭绝");
System.out.println(lists);
for (String ele : lists) {
System.out.println(ele);
}
System.out.println("-------------------------------");
double[] scores = {100,99.5,59.5};
for (double score : scores) {
System.out.println(score);
}
}
}
总结
-
增强 for 可以遍历哪些容器?
- 既可以遍历集合也可以遍历数组。
-
增加 for 的关键是记住它的遍历格式
方式三:lambda 表达式
Lambda 表达式遍历集合
- 得益于 JDK8 开始的新技术 Lambda 表达式,提供了一种更简单、更直接的遍历集合的方式。
Collection 结合 Lambda 遍历的 API
/**
* @author : gxd
* @date : 2022/6/23 23:46
* 目标: 掌握 JDK8 开始之后的新技术 Lambda 表达式。
*/
public class CollectionTest3 {
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("赵敏");
lists.add("小赵");
lists.add("素素");
lists.add("灭绝");
System.out.println(lists);
//lists.forEach(new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
//});
//以下代码为上面的简化
//lists.forEach(s -> {
// System.out.println(s);
//});
//lists.forEach(s -> System.out.println(s));
lists.forEach(System.out::println);
}
}
Collection 集合存储自定义类型的对象
案例 影片信息在程序中的表示
需求
- 某影院系统需要在后台存储上述三部电影,然后依次展示出来。
分析
- 定义一个电影类,定义一个集合存储电影对象。
- 创建 3 个电影对象,封装相关数据,把 3 个对象存入到集合中去。
- 遍历集合中的 3 个对象,输出相关信息。
/**
* @author : gxd
* @date : 2022/6/24 0:00
* 案例 影片信息在程序中的表示
*/
public class Test1 {
public static void main(String[] args) {
// 1、定义一个电影类
// 2、定义一个集合对象存储 3 部电影对象
Collection<Movie> movies = new ArrayList<>();
movies.add(new Movie("《你好,李焕英》",9.5,"张小斐、贾玲、沈腾、陈赫"));
movies.add(new Movie("《唐人街探案》",8.5,"王宝强、刘昊然、美女"));
movies.add(new Movie("《刺杀小说家》",8.6,"雷佳音、杨幂"));
// 3、遍历集合容器中的每个电影对象
for (Movie movie : movies) {
System.out.println("片面:" + movie.getName());
System.out.println("评分:" + movie.getScore());
System.out.println("主演:" + movie.getActor());
}
}
}
内存图
总结
- 集合中存储的是元素的什么信息?
- 集合存储的是元素对象的地址。
List 系列集合
List 集合特点、特有 API
List 系列集合特点
- ArrayList、LinkedList:有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List 集合特有方法
-
List 集合因为支持索引所以多了很多索引操作的独特 api,其他 Collection 的功能 List 也都继承了。
/**
* @author : gxd
* @date : 2022/6/24 15:04
* 目标:掌握 List 集合特有方法
* void add(int index, E element)
* E remove(int index);
* E get(int index);
* E set(int index, E element);
*
* 小结:ArrayList 集合的底层是基于数组存储数据。查询快,增删慢!(相对的)
*/
public class ListTest1 {
public static void main(String[] args) {
// 1、创建一个 ArrayList 集合对象:
// list:有序,可重复,有索引的。
List<String> list = new ArrayList<>();//一行经典代码就诞生了
list.add("Java");
list.add("Java");
list.add("MySQL");
list.add("MySQL");
// 2、在某个索引位置插入元素。
list.add(2,"HTML");
System.out.println(list);
// 3、根据索引删除元素,返回被删除元素
System.out.println(list.remove(2));
System.out.println(list);
// 4、根据索引获取元素:E get(int index); 返回集合中指定位置的元素
System.out.println(list.get(2));
// 5、修改索引位置处的元素:E set(int index, E element); 返回修改之前的数据
System.out.println(list.set(1, "高斯林"));
System.out.println(list);
}
}
总结
- List 系列集合特点
- ArrayList、LinkedList:有序,可重复,有索引。
- List 的实现类的底层原理
- ArrayList 底层是基于数组实现的,根据查询元素快,增删相对慢。
- LinkedList 底层基于双链式实现的,查询元素慢,增删首尾元素是非常快的。
List 集合的遍历方式小结
List 集合的遍历有几种?
- 迭代器
- 增强 for 循环
- Lambda 表达式
- for 循环(因为 List 集合存在索引)
/**
* @author : gxd
* @date : 2022/6/24 15:33
* list 系列集合的遍历方式有:4种。
* list 系列集合多了索引,索引多了一种按照索引遍历集合的 for 循环。
* list 遍历方式:
* 1、for 循环(独有的,因为 list 有索引)
* 2、迭代器
* 3、foreach
* 4、JDK8 新技术 Lambda
*/
public class ListTest2 {
public static void main(String[] args) {
List<String> lists = new ArrayList<>();
lists.add("java1");
lists.add("java2");
lists.add("java3");
// 1、for循环
System.out.println("------------------");
for (int i = 0; i < lists.size(); i++) {
String ele = lists.get(i);
System.out.println(ele);
}
// 2、迭代器
System.out.println("------------------");
Iterator<String> it = lists.iterator();
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
// 3、增强 for 循环
System.out.println("------------------");
for (String s : lists) {
System.out.println(s);
}
// 4、JDK8 新技术 Lambda
System.out.println("------------------");
lists.forEach(s -> {
System.out.println(s);
});
}
}
ArrayList 集合的底层原理
-
ArrayList 底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。
-
第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为 10 的数组。
List 集合存储的元素要超过容量怎么办?
list集合初始容量为10,在JDK8版本的时候做了优化。初始长度为0,而在首次添加元素,需要实际分配空间,执行数组扩容操作时,扩容长度为10。每次扩容是原来的1.5倍。
思考:为何 ArrayList查询快,增删元素相对较慢?
LinkedList 集合的底层原理
- 底层数据结构是双链式,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有 API。
LinkedList 集合的特有功能
/**
* @author : gxd
* @date : 2022/6/24 16:51
* 目标:掌握 LinkedList 特有方法
* 如果查询多而增删少用 ArrayList 集合。(用的最多的)
* 如果查询少而增删首尾较多用 LinkedList 集合。
*/
public class ListTest3 {
public static void main(String[] args) {
// LinkedList 可以完成队列结构,和栈结构(双链表)
//栈
LinkedList<String> stack = new LinkedList<>();
//压栈专业词 push == addFirst、入栈专业词pop == removeFirst
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack);
//出栈、弹栈
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
//队列
LinkedList<String> queue = new LinkedList<>();
//入队
queue.addLast("1号");
queue.addLast("2号");
queue.addLast("3号");
queue.addLast("4号");
System.out.println(queue);
//出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
}
}
补充知识:集合的并发修改异常问题
问题引出
- 当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
哪些遍历存在问题?
- 迭代器遍历集合且直接用集合删除元素的时候可能出现。
- 增强 for 循环遍历集合且直接用集合删除元素的时候可能出现。
/**
* @author : gxd
* @date : 2022/6/24 17:06
* 目标:研究集合遍历并删除元素可能出现的,并发修改异常问题。
*/
public class Test {
public static void main(String[] args) {
// 1、准备数据
List<String> list = new ArrayList<>();
list.add("黑马");
list.add("Java");
list.add("Java");
list.add("赵敏");
list.add("赵敏");
list.add("素素");
System.out.println(list);
//需求:删除全部的Java信息。
// a、迭代器遍历删除
//Iterator<String> it = list.iterator();
//while (it.hasNext()){
// String ele = it.next();
// if ("Java".equals(ele)){
// //list.remove(ele);//集合并发修改异常,ConcurrentModificationException
// it.remove();//使用迭代器删除当前位置的元素,保证不后移,能够成功遍历到全部元素!
// }
//}
//System.out.println(list);
// b、增强for循环 遍历删除(本身解决不了 集合并发修改异常,所以集合删除元素时不用它)
//for (String s : list) {
// if ("Java".equals(s)){
// list.remove(s);//集合并发修改异常,ConcurrentModificationException
// }
//}
//System.out.println(list);
// c、lambda 表达式(本身解决不了 集合并发修改异常,所以集合删除元素时不用它)
//list.forEach(s -> {
// if (s.equals("Java")) {
// list.remove(s);//集合并发修改异常,ConcurrentModificationException
// }
//});
// d、for 循环(不会出现异常错误,但是数据删除出现了问题:会漏删元素)
//for (int i = 0; i < list.size(); i++) {
// String ele = list.get(i);
// if (ele.equals("Java")){
// list.remove(ele);
// }
//}
//System.out.println(list);
//解决方案1:倒着删
//for (int i = list.size() - 1; i >= 0; i--) {
// String ele = list.get(i);
// if (ele.equals("Java")){
// list.remove(ele);
// }
//}
//System.out.println(list);
//解决方案2:i--
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if (ele.equals("Java")){
list.remove(ele);
i--;
}
}
System.out.println(list);
}
}
哪种遍历且删除元素不出问题
- 迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。
- 使用 for 循环遍历并删除元素不会存在这个问题。
- 但是会出现漏删元素问题。
- 解决方案一:倒着删。
- 解决方案二:正着删,在 i–。
- 但是会出现漏删元素问题。
补充知识:泛型深入
泛型的概述和优势
泛型概述
- 泛型:是 JDK5 中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的个数:<数据类型>;注意:泛型只能支持引用数据类型。
- 集合体系的全部接口和实现类都是支持泛型的使用的。
泛型的好处:
- 统一数类型。
- 把运行期间的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编辑阶段类型就能确定下来。
泛型可以在很多地方进行定义:
自定义泛型类
泛型类的概述
-
定义类时同时定义了泛型的类就是泛型类。
-
泛型类的格式:修饰符 class 类名<泛型变量>{}
-
此处泛型变量 T可以随便写为任意标识,常见的如 E、T、K、V等。
-
作用:编译阶段可以指定数据类型,类似于集合的作用。
课程案例导学
- 模拟 ArrayList 集合自定义一个集合 MyArrayList 集合,完成添加和删除功能的泛型设计即可。
泛型类的原理:
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
总结
- 泛型类的核心思想:
- 把出现泛型变量的地方全部替换成传输的真实数据类型
- 泛型类的作用
- 编译阶段约定操作的数据的类型,类似于集合的作用。
自定义泛型方法
泛型方法的概述
-
定义方法时同时定义了泛型的方法就是泛型方法。
-
泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
-
作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
课程案例导学
-
给你任何一个类型的数组,都能返回它的内容。也就是实现 Arrays.toString(数组) 的功能!
/** * @author : gxd * @date : 2022/6/25 17:48 * 自定义泛型方法 */ public class GenericTest1 { public static void main(String[] args) { String[] names = {"小路","蓉蓉","小何"}; printArray(names); Integer[] ages = {10,20,30}; printArray(ages); } public static <T> void printArray(T[] arr){ if (arr != null){ StringBuilder sb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { sb.append(arr[i]).append(i == arr.length - 1 ? "" : ","); } sb.append("]"); System.out.println(sb); }else { System.out.println(arr); } } }
泛型方法的原理:
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
总结
- 泛型方法的核心思想:
- 把出现泛型变量的地方全部替换成传输的真实数据类型
- 泛型方法的作用
- 方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性
自定义泛型接口
泛型接口的概述
-
使用了泛型定义的接口就是泛型接口。
-
泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}
-
作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
课程案例导学
-
教务系统,提供一个接口可约一定约束一定要完成数据(学生,老师)的增删改查操作
/** * @author : gxd * @date : 2022/6/26 21:26 * 自定义泛型接口 */ public interface Data<E> { void add(E e); void delete(int id); void update(E e); E queryById(int id); } /** * @author : gxd * @date : 2022/6/26 21:30 */ public class TeacherData implements Data<Teacher>{ @Override public void add(Teacher teacher) { } @Override public void delete(int id) { } @Override public void update(Teacher teacher) { } @Override public Teacher queryById(int id) { return null; } } /** * @author : gxd * @date : 2022/6/26 21:31 */ public class StudentData implements Data<Student>{ @Override public void add(Student student) { } @Override public void delete(int id) { } @Override public void update(Student student) { } @Override public Student queryById(int id) { return null; } }
泛型接口的原理:
- 实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
总结
- 泛型接口的作用
- 泛型接口可以约束实现类,实现类可以在实现接口的时候传入自己操作的数据类型这样重写的方法都将是针对于该类型的操作。
泛型通配符、上下限
通配符:?
- ?可以在“使用泛型”的时候代表一切类型。
- E T K V 是在定义泛型的时候使用的。
泛型通配符:案例导学
-
开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。
/** * @author : gxd * @date : 2022/6/26 21:40 * 泛型通配符:案例导学 * -开发一个极品飞车的游戏,所有的汽车都能一起参与比赛。 */ public class GenericTest1 { public static void main(String[] args) { ArrayList<BMW> bmws = new ArrayList<>(); bmws.add(new BMW()); bmws.add(new BMW()); bmws.add(new BMW()); go(bmws); ArrayList<BENZ> benzs = new ArrayList<>(); benzs.add(new BENZ()); benzs.add(new BENZ()); benzs.add(new BENZ()); go(benzs); ArrayList<Dog> dogs = new ArrayList<>(); dogs.add(new Dog()); dogs.add(new Dog()); dogs.add(new Dog()); //go(dogs); } public static void go(ArrayList<? extends Car> cars){ } } class Dog{ } class BENZ extends Car{ } class BMW extends Car{ } //父类 class Car{ }
注意:
- 虽然 BMW 和 BENZ 都继承了 Car 但是 ArrayList 和 ArrayList 与 ArrayList 没有关系的!
泛型的上下限:
- ? extends Car :?必须是 Car 或者其子类 泛型上限
- ? super Car :?必须是 Car 或者其父类 泛型下限
Set 系列集合
Set 系列集系概述
Set 系列集合特点
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通 for 循环遍历,也不能通过索引来获取元素。
Set 集合实现类特点
- HashSet:无序、不重复、无索引。
- LinkedHashSet: 有序、不重复、无索引。
- TreeSet: 排序、不重复、无索引。
Set 集合的功能上基本上与 Collection 的 API 一致。
/**
* @author : gxd
* @date : 2022/6/26 22:17
* 看看 Set 系列集合的特点:HashSet LinkedHashSet TreeSet
*/
public class SetTest1 {
public static void main(String[] args) {
//Set<String> sets = new HashSet<>();//一行金典代码 无序、不重复、无索引
Set<String> sets = new LinkedHashSet<>();//有序、不重复、无索引
sets.add("MySQL");
sets.add("MySQL");
sets.add("Java");
sets.add("Java");
sets.add("HTML");
sets.add("HTML");
sets.add("SpringBoot");
sets.add("SpringBoot");
System.out.println(sets);
}
}
总结
- Set 系列集合的特点。
- 无序、不重复、无索引。
- Set 集合的实现类特点。
- HashSet :无序、不重复、无索引。
- LinkedHashSet: 有序、不重复、无索引。
- TreeSet: 可排序、不重复、无索引。
HashSet 元素无序的底层原理:哈希表
HashSet 底层原理
- HashSet 集合底层采取 哈希表存储的数据。
- 哈希表是一种对于增删改查数据性能都较好的结构。
哈希表的组成
- JDK8 之前的,底层使用 数组+链表组成。
- JDK8 开始后,底层采用 数组+链表+红黑树组成。
在了解哈希表之前需要先理解哈希值的概念
哈希值
- 是 JDK 根据对象的 地址,按照某种规则算出来的 int 类型的 数值。
Object 类的 API
- public int hashCode():返回对象的哈希值
对象的哈希值特点
- 同一个对象多次调用 hashCode() 方法返回的哈希值是相同的。
- 默认情况下,不同对象的哈希值是不同的。
/**
* @author : gxd
* @date : 2022/6/26 22:37
* 目标:学会获取对象的哈希值,并确认一下
*/
public class SetTest2 {
public static void main(String[] args) {
String name = "itheima";
System.out.println(name.hashCode());
System.out.println(name.hashCode());
String name1 = "itheima1";
System.out.println(name1.hashCode());
System.out.println(name1.hashCode());
}
}
HashSet 1.7 版本原理解析:数组+链表+(结合哈希算法)
结论:哈希表是一种对于增删改查数据性能都较好的结构。
JDK1.8 版本开始 HashSet 原理解析
- 底层结构:哈希表( 数组、链表、红黑树的结合体)
- 当挂在元素下面的数据过多时,查询性能降低,从 JDK8 开始后,当链表长度超过 8 的时候,自动转换为红黑树。
HashSet 1.8 版本原理解析
结论:JDK8开始后,哈希表对于红黑树的引入进一步提高了操作数据的性能。
总结
- Set 集合的底层原理是什么样的
- JDK8 之前的,哈希表:底层使用 数组+链表组成
- JDK8 之后的,哈希表:底层采用 数组+链表+红黑树组成。
- 哈希表的详细流程
- 创建一个默认长度16,默认加载因为0.75的数组,数组名table
- 根据元素的哈希值跟数组的长度计算出应存入的位置
- 判断当前位置是否为 null,如果是 null 直接存入,如果位置不为 null,表示有元素,则调用 equals 方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
- 当数组存满到 16*0.75=12 时,就自动扩容,每次扩容原先的两部。
HashSet 元素去重复的底层原理
HashSet 去重复原理解析
结论:如果希望 Set 集合认为 2 个内容一样的对象是重复的,必须重写对象的 hashCode() 和 equals() 方法
案例 Set 集合去重复
需要:
创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象。
分析:
- 定义学生类,创建 HashSet 集合对象,创建学生对象。
- 把学生添加到集合
- 在学生类中重写两个方法,hashCode() 和 equals(),自动生成即可
- 遍历集合(增强 for)
/**
* @author : gxd
* @date : 2022/6/27 21:08
* 目标:让Set集合把重复内容的对象去掉一个(去重复)
*/
public class SetTest3 {
public static void main(String[] args) {
Set<Object> sets = new HashSet<>();
Student s1 = new Student("无恙",20,'男');
Student s2 = new Student("无恙",20,'男');
Student s3 = new Student("周雄",21,'男');
sets.add(s1);
sets.add(s2);
sets.add(s3);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(sets);
}
}
/**
* @author : gxd
* @date : 2022/6/27 21:09
*/
public class Student {
private String name;
private int age;
private char sex;
public Student() {
}
public Student(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
/**
* 只要 2 个对象内容一样,结果一定是true
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && sex == student.sex && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
总结
- 如果希望 Set 集合任务 2 个内容相同的对象是重复的应该怎么办?
- 重学对象的 hashCode 和 equals 方法。
实现类:LinkedHashSet
LinkedHashSet 集合概述和特点
-
有序、不重复、无索引。
-
这里的有序指的是保证存储和取出的元素顺序一致
-
原理:底层数据结构依然是哈希表,只是每个元素有额外的多了一个双链表的机制记录存储的顺序。
总结
- LinkedHashSet 集合的特点和原理是怎么样的?
- 有序、不重复、无索引
- 底层基于哈希表,使用双链表记录添加顺序。
实现类:TreeSet
TreeSet 集合概述和特点
- 不重复、无索引、可排序
- 可排序:按照元素的大小默认升序(由小到大)排序。
- TreeSet 集合底层是基于 红黑树的数据结构 实现排序的,增删改查性能都较好。
- 注意:TreeSet 集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet 集合默认的规则
- 对于数值类型:Integer、Double,官方默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如 Student 对象,TreeSet 无法直接排序。
结论:想要使用 TreeSet 存储自定义类型,需要制定排序规则。
自定义排序规则
- TreeSet 集合存储对象的时候有 2 种方式可以设计自定义比较规则
方式一:
- 让自定义的类(如学生类)实现 Comparable接口 重写里面的 comparTo 方法 来定制比较规则。
方式二:
- TreeSet 集合有参数构造器,可以设置 Comparator 接口对应的比较器对象,来定制比较规则。
两种方式中,关于返回值的规则:
- 如果认为第一个元素大于第二个元素返回正整数即可。
- 如果认为第一个元素小于第二个元素返回负整数即可。
- 如果认为第一个元素等于第二个元素返回0即可,此时 TreeSet 集合只会保留一个元素,认为两者重复。
注意:如果 TreeSet 集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
/**
* @author : gxd
* @date : 2022/6/27 22:51
* 目标:观察 TreeSet 对于有值特性的数据如何排序。
* 学会自定义类型的对象进行指定规则排序
*/
public class SetTest5 {
public static void main(String[] args) {
Set<Integer> sets = new TreeSet<>();// 不重复、无索引、可排序
sets.add(23);
sets.add(24);
sets.add(12);
sets.add(8);
System.out.println(sets);
Set<String> sets1 = new TreeSet<>();// 不重复、无索引、可排序
sets1.add("Java");
sets1.add("Java");
sets1.add("angela");
sets1.add("黑马");
sets1.add("About");
sets1.add("Python");
sets1.add("UI");
sets1.add("UI");
System.out.println(sets1);
System.out.println("-------------------------------------");
//方式二:集合自带比较器对象进行规则定制
Set<Apple> apples = new TreeSet<>(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
//return o1.getWeight() - o2.getWeight();//升序
//return o2.getWeight() - o1.getWeight();//降序
//注意:浮点型建议使用 Double.compar 进行比较
return Double.compare(o1.getPrice(),o2.getPrice());//升序
//return Double.compare(o2.getPrice(),o1.getPrice());//降序
}
});
//简化上面代码
//Set<Apple> apples = new TreeSet<>((o1,o2) -> Double.compare(o1.getPrice(),o2.getPrice()));
apples.add(new Apple("红富士","红色",9.9,500));
apples.add(new Apple("青苹果","绿色",15.9,300));
apples.add(new Apple("绿苹果","青色",29.9,400));
apples.add(new Apple("黄苹果","黄色",9.8,500));
System.out.println(apples);
}
}
/**
* @author : gxd
* @date : 2022/6/27 22:57
*/
public class Apple implements Comparable<Apple>{
private String name;
private String color;
private double price;
private int weight;
public Apple() {
}
public Apple(String name, String color, double price, int weight) {
this.name = name;
this.color = color;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
/**
* 方式一:自定义比较规则
o1.compareTo(o2)
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
return this.weight - o.weight;//去掉重量重复的,只保留一个
//return this.weight - o.weight >= 0 ? 1 : -1;//保留重量重复的,都保留下来
}
}
总结
- TreeSet 集合的特点是什么样的?
- 可排序、不重复、无索引
- 底层基于红黑树实现排序,增删改查性能较好
- TreeSet 集合自定义排序规则有几种方式
- 2 种。
- 类实现 Comparable 接口,重写比较规则。
- 集合自定义 Comparator 比较器对象,重写比较规则。
Collection 体系的特点、使用场景总结
总结
- 如果希望元素可以重复,又有索引,索引查询要快?
- 用 ArrayList 集合,基于数组的。(用的最多)
- 如果希望元素可以重复,又有索引,增删首尾比较快?
- 用 LinkedList 集合,基于链表的。
- 如果希望增删改查都快,但是元素不重复、无序、无索引。
- 用 HashSet 集合,基于哈希表的。
- 如果希望增删改查都快,但是元素不重复、有序、无索引。
- 用 LinkedHashSet 集合,基于哈希表和双链表。
- 如果要对对象进行排序。
- 用 TreeSet 集合,基于红黑树。后续也可以用 List 集合实现排序。
补充知识:可变参数
案例
假如需要定义一个方法求和,该方法可以灵活的完成如下需求:
-
计算 1 个数据的和。
-
计算 2 个数据的和。
-
计算 3 个数据的和。
-
计算 n 个数据的和,甚至可以支持不接收参数进行调用。
/** * @author : gxd * @date : 2022/6/27 23:52 * 目标:可变参数 * -可变参数的作用: * 传输参数非常灵活,方便。可以不传输参数,可以传输 1个 或者 多个,也可以传输一个数组 * * 可变参数的格式:数据类型…参数名称 */ public class MethodTest { public static void main(String[] args) { sum();//1、不传参数 sum(10);//2、可以传输一个参数 sum(10,20,30);//3、可以传输多个参数 sum(new int[]{10,20,30,40,50});//4、可以传输一个数组 } /** * 注意 * - 1、一个形参列表中可变参数只能有一个 * - 2、可变参数必须放在形参列表的最后面 * @param nums */ public static void sum(int...nums){ //注意:可变参数在方法内部其实就就是一个数组:nums。 System.out.println("元素个数:" + nums.length); System.out.println("元素内容:" + Arrays.toString(nums)); int sum = 0; for (int num : nums) { sum += num; } System.out.println("元素和:" + sum); } }
可变参数
- 可变参数用在形参中可以接收多个数据。
- 可变参数的格式: 数据类型…参数名称
可变参数的作用
- 传输参数非常灵活,方便。可以不传输参数,可以传输 1个 或者 多个,也可以传输一个数组
- 可变参数在方法内部本质上就是一个数组。
可变参数的注意事项:
- 1、一个形参列表中可变参数只能有一个
- 2、可变参数必须放在形参列表的最后面
补充知识:集合工具类 Collections
Collections 集合工具类型
- java.utis.Collections:是集合工具类
- 作用:Collections 并不属于集合,是用来操作集合的工具类。
Collections 常用的 API
Collections 排序相关 API
- 使用范围:只能对于 List 集合的排序
排序方式一:
注意:本方式不可以直接对自定义类型的 List 集合排序,除非自定义类型实现了比较规则 Comparable 接口。
排序方式二:
/**
* @author : gxd
* @date : 2022/6/28 10:25
* 目标:Collections 工具类的使用
* java.utils.Collections:是集合工具类
* Collections并不属于集合,是用来操作集合的工具类。
* Collections 有几个常用的API:
* public static <T> boolean addAll(Collection<? super T> c, T... elements):给集合对象批量添加元素!
* public static void shuffle(List<?> list):打乱集合顺序。
* public static <T extends Comparable<? super T>> void sort(List<T> list):将集合中元素按照默认规则排序。
* public static <T> void sort(List<T> list, Comparator<? super T> c):将集合中元素按照特定规则排序,自带比较器
*/
public class CollectionsTest1 {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
//names.add("楚留香");
//names.add("胡铁花");
//names.add("张无忌");
//names.add("陆小凤");
Collections.addAll(names,"楚留香","胡铁花","张无忌","陆小凤");
System.out.println(names);
//2、public static void shuffle(List<?> list):打乱集合顺序。
Collections.shuffle(names);
System.out.println(names);
//3、public static <T extends Comparable<? super T>> void sort(List<T> list):将集合中元素按照默认规则排序。(排值特性的元素)
List<Integer> list = new ArrayList<>();
Collections.addAll(list,12,23,2,4);
System.out.println(list);
Collections.sort(list);
System.out.println(list);
}
}
自定义比较规则
/**
* @author : gxd
* @date : 2022/6/28 10:25
* 目标:自定义类型的比较方法API:Collections
* -public static <T extends Comparable<? super T>> void sort(List<T> list):
* 将集合中元素按照默认规则排序。
* 对于自定义的引用类型的排序人家根本不知道怎么排,直接报错!解决方案:自定义类型实现比较规则 Comparable 接口。
* -public static <T> void sort(List<T> list, Comparator<? super T> c):
* 将集合中元素按照特定规则排序,自带比较器
*/
public class CollectionsTest2 {
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();//可以重复!
apples.add(new Apple("红富士","红色",9.9,500));
apples.add(new Apple("青苹果","绿色",15.9,300));
apples.add(new Apple("绿苹果","青色",29.9,400));
apples.add(new Apple("黄苹果","黄色",9.8,500));
//Collections.sort(apples);//方式一:Apple 类实现比较规则 Comparable 接口,重写了比较规则
//System.out.println(apples);
//方式二:sort方法自带比较器对象
//Collections.sort(apples, new Comparator<Apple>() {
// @Override
// public int compare(Apple o1, Apple o2) {
// return Double.compare(o1.getPrice(),o2.getPrice());
// }
//});
// 简化代码
Collections.sort(apples, (o1,o2) -> Double.compare(o1.getPrice(),o2.getPrice()));
System.out.println(apples);
}
}
/**
* @author : gxd
* @date : 2022/6/27 22:57
*/
public class Apple implements Comparable<Apple>{
private String name;
private String color;
private double price;
private int weight;
public Apple() {
}
public Apple(String name, String color, double price, int weight) {
this.name = name;
this.color = color;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
/**
* 方式一:自定义比较规则
o1.compareTo(o2)
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
return this.weight - o.weight;//List集合存储相同大小的元素 会保留!
}
}
Collection 体系的综合案例
案例 斗地主游戏
需求:
在启动游戏房间的时候,应该提前准备好 54 张牌,完成洗牌、发牌、牌排序、逻辑。
分析:
- 当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。
- 洗牌就是打乱牌的顺序。
- 定义三个玩家、依次发出 51 张牌
- 给玩家的牌进行排序(拓展)
- 输出每个玩家的牌数据。
/**
* @author : gxd
* @date : 2022/6/28 11:17
* 目标:斗地主游戏
*
* 功能:
* 1、做牌
* 2、洗牌
* 3、定义3个玩家
* 4、发牌
* 5、排序(拓展,了解,作业)
* 6、看牌
*/
public class GameTest {
/**
* 1、定义一个静态的集合存储54张牌对象
*/
public static List<Card> allCards = new ArrayList<>();
/**
* 2、做牌:定义静态代码块初始化牌数据
*/
static {
//3、定义点数:个数确定,类型确定,使用数组
String[] sizes = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
//4、定义花色:个数确定,类型确定,使用数组
String[] colors = {"♠","♥","♣","♦"};
//5、组合点数和花色
int index = 0;//记录牌的大小
for (String size : sizes) {
index++;
for (String color : colors) {
//6、封装成一个牌对象
Card card = new Card(size,color,index);
//7、存入到集合容器中去
allCards.add(card);
}
}
//8、大小王存入到集合对象中去🃏、🃏
Card card1 = new Card("","🃏",++index);
Card card2 = new Card("","🃏",++index);
Collections.addAll(allCards,card1,card2);
System.out.println("新牌:" + allCards);
}
public static void main(String[] args) {
//9、洗牌
Collections.shuffle(allCards);
System.out.println("新派后:" + allCards);
//10、发牌(定义三个玩家,每个玩家的牌也是一个集合容器)
List<Card> linhuchong = new ArrayList<>();
List<Card> jiumozhi = new ArrayList<>();
List<Card> renyingying = new ArrayList<>();
//11、开始发牌(从牌集合中付出51张牌给三个玩家,剩余3张作为底牌)
for (int i = 0; i < allCards.size() - 3; i++) {
Card card = allCards.get(i);
if (i % 3 == 0){
//请阿冲接牌
linhuchong.add(card);
}else if (i % 3 == 1){
//请阿鸠接牌
jiumozhi.add(card);
}else if (i % 3 == 2){
//请盈盈接牌
renyingying.add(card);
}
}
//12、拿到最后三张底牌(把最后三张牌截取成一个子集合)
List<Card> lastThreeCards = allCards.subList(allCards.size() - 3, allCards.size());
//13、给玩家的牌排序(从大到小)
sortCards(linhuchong);
sortCards(jiumozhi);
sortCards(renyingying);
//14、输出玩家的牌:
System.out.println("阿冲:" + linhuchong);
System.out.println("阿鸠:" + jiumozhi);
System.out.println("盈盈:" + renyingying);
System.out.println("三张底牌:" + lastThreeCards);
}
/**
* 给牌排序
* @param cards
*/
private static void sortCards(List<Card> cards) {
//知道牌的大小,才可以指定规则
Collections.sort(cards,(o1,o2) -> o2.getIndex() - o1.getIndex());
}
}
/**
* @author : gxd
* @date : 2022/6/28 11:20
*/
public class Card {
private String size;//点数
private String color;//颜色
private int index;//牌的真正大小
public Card() {
}
public Card(String size, String color, int index) {
this.size = size;
this.color = color;
this.index = index;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public String toString() {
return size + color;
}
}
Map 集合体系
Map 集合的概述
Map 集合概述和使用
- Map 集合是一种双列集合,每个元素包含两个数据。
- Map 集合的每个元素的格式:key=value(键值对元素)。
- Map 集合也被称为“ 键值对集合”。
Map 集合整体格式:
-
Collection 集合的格式:[元素1,元素2,元素3…]
-
Map 集合的完整格式:{key1=value1,key2=value2,key3=value3,…}
Map 集合的使用场景之一:购物车系统
总结
- Map 集合是什么?使用场景是什么样的?
- Map 集合是键值对集合
- Map 集合非常适合做购物车这样的业务场景。
Map 集合体系特点
Map 集合体系
说明
-
使用最多的 Map 集合是 HashMap。
-
重点掌握 HashMap、LinkedHashMap、TreeMap。其他的后续理解。
Map 集合体系特点
- Map 集合的特点都是由键决定的。
- Map 集合的键时无序,不重复的,无索引的,值不做要求(可以重复)。
- Map 集合后面重复的键对应的值会覆盖前面重复键的值。
- Map 集合的键值对都可以为 null。
Map 集合实现类特点
-
HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与 Map 体系一致)
-
LinkedHashMap:元素按照键是 有序,不重复,无索引,值不做要求。
-
TreeMap:元素按照键是 排序,不重复,无索引的,值不做要求。
/** * @author : gxd * @date : 2022/6/28 15:00 * 目标:认识 Map 体系的特点:按照键无序,不重复,无索引。值不做要求。 */ public class MapTest1 { public static void main(String[] args) { // 1、创建一个 Map 集合对象 Map<String,Integer> maps = new HashMap<>();//一行经典代码 //Map<String,Integer> maps = new LinkedHashMap<>(); maps.put("鸿星尔克",3); maps.put("Java",1); maps.put("枸杞",100); maps.put("Java",100);//覆盖前面数据 maps.put(null,null); System.out.println(maps); } }
总结
- Map 集合的特点
- HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与 Map 体系一致)
- LinkedHashMap:元素按照键是 有序,不重复,无索引,值不做要求。
- TreeMap:元素按照键是 排序,不重复,无索引的,值不做要求。
Map 集合常用 API
Map 集合
- Map 是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
Map API 如下:
/**
* @author : gxd
* @date : 2022/6/28 15:25
* 目标:掌握Map常用API
*/
public class MapTest {
public static void main(String[] args) {
//1、添加元素:无序,不重复,无索引。
Map<String, Integer> maps = new HashMap<>();
maps.put("iphoneX",10);
maps.put("娃娃",31);
maps.put("iphoneX",100);//Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",100);
maps.put("生活用品",10);
maps.put("手表",10);
//{huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=31}
System.out.println(maps);
//2、清空集合
//maps.clear();
//System.out.println(maps);
//3、判断集合是否为空,为空返回true,反之!
System.out.println(maps.isEmpty());
//4、根据键获取对应值:public V get(Object key)
Integer key = maps.get("huawei");
System.out.println(key);
System.out.println(maps.get("生活用品"));//10
System.out.println(maps.get("生活用品2"));//null
//5、根据键删除整个元素。
System.out.println(maps.remove("iphoneX"));
System.out.println(maps);
//6、判断是否包含某个键,包含返回true,反之!
System.out.println(maps.containsKey("娃娃"));// true
System.out.println(maps.containsKey("娃娃2"));//false
System.out.println(maps.containsKey("iphoneX"));//false
//7、判断是否包含某个值。
System.out.println(maps.containsValue(100));//true
System.out.println(maps.containsValue(10));//true
System.out.println(maps.containsValue(22));//false
//{huawei=100, 手表=10, 生活用品=10, 娃娃=31}
//8、获取全部键的集合:Set<K> keySet()
Set<String> keys = maps.keySet();
System.out.println(keys);
System.out.println("---------------------------------");
//9、获取全部值的集合:Collection<V> values()
Collection<Integer> values = maps.values();
System.out.println(values);
//10、集合的大小
System.out.println(maps.size());//4
//11、合并其他Map结合。(拓展)
Map<String, Integer> map1 = new HashMap<>();
map1.put("java1",1);
map1.put("java2",100);
Map<String, Integer> map2 = new HashMap<>();
map2.put("java2",1);
map2.put("java3",100);
map1.putAll(map2);//把集合map2的元素拷贝一份到map1中去。
System.out.println(map1);
System.out.println(map2);
}
}
Map 集合的遍历方式一:键找值
Map 集合的遍历方式有:3种。
- 方式一:键值对的方式遍历:先获取 Map 集合全部的键,再根据遍历键找值。
- 方式二:键值对的方式遍历,把“键值对”看成一个整体,难度较大。
- 方式三:JDK1.8开始之后的新技术:Lambda表达式。
遍历 Map 集合方式一:键找值流程
Map 集合的遍历方式一:键找值
- 先获取 Map 集合的全部键的 Set 集合。
- 遍历键的 Set 集合,然后通过键提取对应值。
键找值涉及到的 API:
/**
* @author : gxd
* @date : 2022/6/28 16:29
* 目标:Map 集合的遍历方式一:键找值
* - 先获取 Map 集合的全部键的 Set 集合。
* - 遍历键的 Set 集合,然后通过键提取对应值。
*/
public class MapTest1 {
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
//1.添加元素:无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
//{huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
//1、键找值:第一步:先拿到集合的全部键。
Set<String> keys = maps.keySet();
//2、第二步:遍历每个键,根据键提取值。
for (String key : keys) {
Integer value = maps.get(key);
System.out.println(key + "===>" + value);
}
}
}
Map 集合的遍历方式二:键值对
遍历 Map 集合方式二:键值对流程
Map 集合的遍历方式二:键值对
- 先把 Map 集合转换成 Set 集合,Set 集合中每个元素都是键值对实体类型了。
- 遍历 Set 集合,然后提取键以及提取值。
键值对涉及到的 API:
/**
* @author : gxd
* @date : 2022/6/28 16:41
* 目标:Map 集合的遍历方式二:键值对
* - 先把 Map 集合转换成 Set 集合,Set 集合中每个元素都是键值对实体类型了。
* - 遍历 Set 集合,然后提取键以及提取值。
*/
public class MapTest2 {
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
//1.添加元素:无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
//{huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
/**
maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
使用 foreach 遍历 map 集合,发现 Map 集合的键值对元素直接是没有类型的,所以不可以使用 foreach 遍历结合。
可以通过调用 Map 的方法:entrySet 把 Map 集合转换成 Set 集合形式
Set<Map.Entry<String,Integer>> entries = {(huawei=1000), (手表=10), (生活用品=10), (iphoneX=100), (娃娃=30)}
此时可以使用 foreach 遍历
*/
//1、把 Map 集合转换成 Set 集合
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
//2、开始遍历
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + "===>" + value);
}
}
}
Map 集合的遍历方式三:lambda 表达式
Map 集合的遍历方式三:Lambda
- 得益于 JDK8 开化寺的新技术 Lambda 表达式,提供了一种更简单、更直接的遍历集合的方式。
Map 结合 Lambda 遍历的 API
流程
-
maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
/**
* @author : gxd
* @date : 2022/6/28 23:07
* 目标:Map 集合的遍历方式三:lambda 表达式
* 得益于 JDK8 开化寺的新技术 Lambda 表达式,提供了一种更简单、更直接的遍历集合的方式。
*/
public class MapTest3 {
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
//1.添加元素:无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
//{huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
//maps.forEach(new BiConsumer<String, Integer>() {
// @Override
// public void accept(String key, Integer value) {
// System.out.println(key + "===>" + value);
// }
//});
//简化代码
maps.forEach((k,v) -> {
System.out.println(k + "===>" + v);
});
}
}
案例 Map 集合案例 - 统计投票人数
需求:
- 某个班级 80 名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
分析:
- 将 80 个学生选择的数据拿到程序中去。
- 定义 Map 集合用于存储最终统计的结果。
- 遍历 80 个学生选择的数据,看 Map 集合中是否存在,不存在存入“数据=1”,存在则其对应值+1。
/**
* @author : gxd
* @date : 2022/6/28 23:25
* Map 集合案例 - 统计投票人数
* 需求:
* - 某个班级 80 名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
*
* 分析:
* - 将 80 个学生选择的数据拿到程序中去。
* - 定义 Map 集合用于存储最终统计的结果。
* - 遍历 80 个学生选择的数据,看 Map 集合中是否存在,不存在存入“数据=1”,存在则其对应值+1。
*/
public class MapTest1 {
public static void main(String[] args) {
//1、把 80 个学生选择的数据拿进来
String[] selects = {"A","B","C","D"};
StringBuilder sb = new StringBuilder();
Random r = new Random();
for (int i = 0; i < 80; i++) {
sb.append(selects[r.nextInt(selects.length)]);
}
System.out.println(sb);
//2、定义一个 Map 集合记录最终统计的结果:A=30 B=20 C=20 D=10 键是景点,值是选择的数量
Map<Character,Integer> infos = new HashMap<>();
//3、遍历 80 个学生选择的数据
for (int i = 0; i < sb.length(); i++) {
//4、提取当前选择景点字符
char ch = sb.charAt(i);
//5、判断 Map 集合中是否存在这个键
if (infos.containsKey(ch)){
//让其值 + 1
infos.put(ch,infos.get(ch) + 1);
}else {
//说明此景点是第一次被选
infos.put(ch,1);
}
}
//4、输出集合
System.out.println(infos);
}
}
Map 集合的实现类 HashMap
HashMap 的特点
- HashMap 是 Map 里面的一个实现类。特点都是由键决定的:无序、不重复、无索引。
- 没有额外需要学习的特有方法,直接使用 Map 里面的方法就可以了。
- HashMap 跟 HashSet 底层原理是一模一样的,都是哈希表结构,只是 HashMap 的每个元素包含两个值而已。
实际上:Set 系列集合的底层就是 Map 实现的,只是 Set 集合中的元素只要键数据,不要值数据而已。
HashMap 的添加规则
总结
- HashMap 的特点和底层原理
- 由键决定:无序、不重复、无索引。HashMap 底层是哈希表结构的。
- 依赖 hashCode 方法和 equals 方法保证 键的唯一。
- 如果键要存储的是自定义对象,需要重写 hashCode 和 equals 方法。
- 基于哈希表。增删改查的性能都较好。
/**
* @author : gxd
* @date : 2022/6/29 9:36
* 目标: HashMap 的特点
*/
public class HashMapTest1 {
public static void main(String[] args) {
// Map 集合是根据键去掉重复元素。
Map<Student,String> maps = new HashMap<>();
Student s1 = new Student("无恙",20,'男');
Student s2 = new Student("无恙",20,'男');
Student s3 = new Student("周雄",21,'男');
maps.put(s1,"北京");
maps.put(s2,"上海");
maps.put(s3,"广州");
System.out.println(maps);
}
}
Map 集合的实现类 LinkedHashMap
LinkedHashMap 集合概述和特点
-
由键决定:有序、不重复、无索引。
-
这里的有序指的是保证存储和取出的元素顺序一致
-
原理:底层数据结构依然是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
/**
* @author : gxd
* @date : 2022/6/29 10:44
* 目标:LinkedHashMap 集合概述和特点
* - 由键决定:有序、不重复、无索引。
* - 这里的有序指的是保证存储和取出的元素顺序一致
* - 原理:底层数据结构依然是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
*/
public class LinkedHashMapTest2 {
public static void main(String[] args) {
//1、创建一个 Map 集合对象
Map<String, Integer> maps = new LinkedHashMap<>();
maps.put("鸿星尔克",3);
maps.put("Java",1);
maps.put("枸杞",100);
maps.put("Java",100);//覆盖前面数据
maps.put(null,null);
System.out.println(maps);
}
}
Map 集合的实现类 TreeMap
TreeMap 集合概述和特点
- 由键决定特性:不重复、无索引、可排序。
- 可排序:按照键数据的大小默认升序(由小到大)排序。只能对键排序。
- 注意:TreeMap 集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
- TreeMap 跟 TreeSet 一样底层原理是一样的。
TreeMap 集合自定义排序规则有 2 种
- 类实现 Comparable 接口,重写比较规则。
- 集合自定义 Comparator 比较器对象,重写比较规则。
/**
* @author : gxd
* @date : 2022/6/29 10:52
* 目标:TreeMap 集合概述和特点
* - 由键决定特性:不重复、无索引、可排序。
* - 可排序:按照键数据的大小默认升序(由小到大)排序。只能对键排序。
* - 注意:TreeMap 集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
* - TreeMap 跟 TreeSet 一样底层原理是一样的。
*
* TreeMap 集合自定义排序规则有 2 种
* - 类实现 Comparable 接口,重写比较规则。
* - 集合自定义 Comparator 比较器对象,重写比较规则。
*/
public class TreeMapTest3 {
public static void main(String[] args) {
Map<Integer,String> maps1 = new TreeMap<>();
maps1.put(13,"王麻子");
maps1.put(1,"张三");
maps1.put(3,"县长");
System.out.println(maps1);
// TreeMap 集合自带排序。 可排序 不重复(只要大小规则一样就认为重复) 无索引
//Map<Apple,String> maps2 = new TreeMap<>(new Comparator<Apple>() {
// @Override
// public int compare(Apple o1, Apple o2) {
// return Double.compare(o2.getPrice(),o1.getPrice());//按照价格降序排序!
// }
//});
//简化代码
Map<Apple,String> maps2 = new TreeMap<>((o1,o2) -> Double.compare(o2.getPrice(),o1.getPrice()));
maps2.put(new Apple("红富士","红色",9.9,500),"山东");
maps2.put(new Apple("青苹果","绿色",15.9,300),"广州");
maps2.put(new Apple("绿苹果","青色",29.9,400),"江西");
maps2.put(new Apple("黄苹果","黄色",9.8,500),"湖北");
System.out.println(maps2);
}
}
Map 集合实现类特点
- HashMap:元素按照键是无需,不重复,无索引,值不做要求,基于哈希表(与 Map 体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求,基于哈希表
- TreeMap:元素只能按照键排序,不重复,无索引的,值不做要求,可以做排序
补充知识:集合的嵌套
案例 Map 集合案例 - 统计投票人数
需求
- 某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A,B,C,D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。
分析
- 将 80 个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
- 定义 Map 集合用于存储最终统计的结果。
/**
* @author : gxd
* @date : 2022/6/28 23:25
* Map 集合案例 - 统计投票人数(升级 集合的嵌套)
* 需求:
* - 某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A,B,C,D),每个学生可以选择多个景点,请统计出最终哪个景点想去的人数最多。
*
* 分析:
*- 将 80 个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
* - 定义 Map 集合用于存储最终统计的结果。
*/
public class MapTest4 {
public static void main(String[] args) {
// 1、要求程序记录每个学生选择的情况。
//使用一个 Map 集合存储。
Map<String, List<String>> data = new HashMap<>();
// 2、把学生选择的数据存入进去
List<String> selects = new ArrayList<>();
Collections.addAll(selects,"A","C");
data.put("张三",selects);
List<String> selects1 = new ArrayList<>();
Collections.addAll(selects1,"B","C","D");
data.put("李四",selects1);
List<String> selects2 = new ArrayList<>();
Collections.addAll(selects2,"A","B","C","D");
data.put("王五",selects2);
System.out.println(data);
// 3、统计每个景点选择的人数。
Map<String, Integer> infos = new HashMap<>();
// 4、提取所有人选择的景点的信息。
Collection<List<String>> values = data.values();
System.out.println(values);//[[B, C, D], [A, C], [A, B, C, D]]
for (List<String> value : values) {
for (String s : value) {
if (infos.containsKey(s)){
infos.put(s,infos.get(s) + 1);
}else {
infos.put(s, 1);
}
}
}
System.out.println(infos);
}
}
创建不可变集合
什么是不可变集合?
-
不可变集合,就是不可被改变的集合。
-
集合的数据项在创建的时候提供,并且在整个生命周期中都不可改变。否则报错。
为什么要创建不可变集合?
- 如果某个数据不能被修改,把它防御性地拷贝给不可变集合中是个很好的实践。
- 或者当前集合对象被不可信的库调用时,不可变形式是安全的。
如何创建不可变集合?
-
在 List、Set 、Map集合中,都存在 of 方法,可以创建一个不可变的集合。
-
这个集合不能添加,不能删除,不能修改。
/**
* @author : gxd
* @date : 2022/6/30 0:27
* 目标:不可变集合
* - 在 List、Set 、Map集合中,都存在 of 方法,可以创建一个不可变的集合。
* - 这个集合不能添加,不能删除,不能修改。
*/
public class CollectionTest {
public static void main(String[] args) {
//1、不可变的 List 集合
List<Double> lists = List.of(569.5,700.5,523.0,570.5);
//lists.add(689.0);//报错,UnsupportedOperationException
//lists.set(2,698.5);//报错,UnsupportedOperationException
System.out.println(lists);
Double score = lists.get(1);
System.out.println(score);
//2、不可变的 Set 集合
Set<String> names = Set.of("张三","李四","王五","马六");
//Set<String> names = Set.of("张三","李四","王五","马六","王五");//报错,duplicate element: 王五
//names.add("王麻子");//报错,UnsupportedOperationException
System.out.println(names);
// 3、不可变的 Map 集合
Map<String,Integer> maps = Map.of("huawei",2,"Java开发",1,"手表",1);
//maps.put("衣服",3);//报错,UnsupportedOperationException
System.out.println(maps);
}
}
总结
- 不可变集合的特点?
- 定义完成后不可以修改,或者添加、删除
- 如何创建不可变集合?
- List、Set、Map 接口中,都存在 of 方法可以创建不可变集合。