JAVA基础加强篇08——集合
集合概述
集合和数组都是容器。
数组的特点
- 数组定义完成并启动后, 类型确定、长度固定。
- 在进行增删数据操作的时候,数组是不太合适的,增删数据都需要放弃原有数组或者移位。
数组适合的场景
当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储。
集合是 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"));//trueSystem.out.println(c.contains("java"));//falseSystem.out.println(c.contains("黑马"));//true// 6、删除某个元素:如果有多个重复元素默认删除前面的第一个!System.out.println(c.remove("java"));//falseSystem.out.println(c);System.out.println(c.remove("Java"));//trueSystem.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 新技术 LambdaSystem.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 == removeFirststack.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>{@Overridepublic void add(Teacher teacher) {}@Overridepublic void delete(int id) {}@Overridepublic void update(Teacher teacher) {}@Overridepublic Teacher queryById(int id) {return null;} }/*** @author : gxd* @date : 2022/6/26 21:31*/ public class StudentData implements Data<Student>{@Overridepublic void add(Student student) {}@Overridepublic void delete(int id) {}@Overridepublic void update(Student student) {}@Overridepublic 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*/@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(name, age, sex);}@Overridepublic 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>() {@Overridepublic 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;}@Overridepublic String toString() {return "Apple{" +"name='" + name + '\'' +", color='" + color + '\'' +", price=" + price +", weight=" + weight +'}';}/*** 方式一:自定义比较规则o1.compareTo(o2)* @param o* @return*/@Overridepublic 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;}@Overridepublic String toString() {return "Apple{" +"name='" + name + '\'' +", color='" + color + '\'' +", price=" + price +", weight=" + weight +'}';}/*** 方式一:自定义比较规则o1.compareTo(o2)* @param o* @return*/@Overridepublic 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、大小王存入到集合对象中去
JAVA基础加强篇08——集合相关推荐
- 超详细的Java面试题总结(二)之Java基础知识篇
系列文章: 超详细的Java面试题总结(一)之Java基本知识 超详细的Java面试题总结(二)之Java基础知识篇 超详细的Java面试题总结(三)之Java集合篇常见问题 超详细的Java面试题总 ...
- Java基础知识强化之集合框架笔记76:ConcurrentHashMap之 ConcurrentHashMap简介
1. ConcurrentHashMap简介: ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和Hashtable功能相同但是线程安全的方法.Conc ...
- Java核心类库篇4——集合
Java核心类库篇4--集合 1.集合的意义 记录单个数据内容时,则声明一个变量 记录多个类型相同的数据内容时,声明一个一维数组 记录多个类型不同的数据内容时,则创建一个对象 记录多个类型相同的对象数 ...
- JAVA中整型常量的长度,Java基础入门篇(三)——Java常量、变量,
Java基础入门篇(三)--Java常量.变量, 一.Java常量 (一)什么是常量 常量指的是在程序中固定不变的值,是不能改变的数据.在Java中,常量包括整型常量.浮点型常量.布尔常量.字符常量等 ...
- Java基础之数组与集合
Java基础之数组与集合 一.数组 二.冒泡排序算法 三.选择排序算法 四.二维数组 五.Arrays 工具 六.宠物管理系统 七.集合 Collection 八.List 九.重写 equals 方 ...
- Java基础(二):集合、IO流(Zip压缩输入/输出流等)、File文件类、反射、枚举
Java基础(一):编译和解释.数据类型.变量作用域.String常用方法.数组.面向对象.异常 Java基础(二):集合.IO流(Zip压缩输入/输出流等).File文件类.反射.枚举 Java异常 ...
- JAVA基础第四章-集合框架Collection篇
业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...
- (Java实习生)每日10道面试题打卡——Java基础知识篇
临近秋招,备战暑期实习,祝大家每天进步亿点点! 本篇总结的是Java基础知识相关的面试题,后续会每日更新~ 1.请你说一下什么是面向对象? Java是面向对象的编程语言,不同于C语言是面向过程的.对于 ...
- 「Java面试题精华集」Java基础知识篇(2022最新版)附PDF版
两个星期前,我和我的好朋友决定做一系列的 Java 知识点常见重要问题的小册.小册的标准就一个,那就是:取精华,取重点.每一本小册,我们都会充分关注我们所总结的知识点是否达到这个标准. 昨天晚上终于把 ...
最新文章
- 也说 ASP.NET MVC的 Script 管理
- 工作三年,我要如何提升Java技术 | 粉丝提问
- 【MM模块】 External Services 外部服务
- 读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第二部分)...
- 【Python】pandas 缺失数据处理大全(附代码)
- java yii_构建 Java 应用程序
- linux utmp结构体,Linux C编程如何使用联机帮助来解决编程问题?
- 1049. 最后一块石头的重量 II(JavaScript)
- 【LEDE】树莓派上玩LEDE终极指南-92-自己编译的LEDE为啥子不能用SSR和KoolProxy?
- 机器学习-基于Logistic回归和Sigmoid函数的分类
- nginx + tomcat 504 解决方案
- 使用NGUI实现拖拽功能(拼图小游戏)
- 徐州php溪谷_ThinkPHP溪谷H5游戏平台系统V3.0完整版源码
- 网络营销之网络炒作案例分析、精髓及方法讨论
- 电子版产品手册如何制作?简单的方法来了
- LoadRunner测试工具大全下载,破解,licence
- hive sql通过具体地址解析出行政区划(省 > 市 > 区 > 县 > 乡 > 镇 > 村)
- ROS_Kinetic_03 ROS入门向导
- 孪生素数 所谓孪生素数指的就是间隔为 2 的相邻素数,它们之间的距离已经近得不能再近了,就象孪生兄弟一样。
- 管中窥豹——应试教育与一流科学人才差距有多远
热门文章
- 20190121——不慕神仙?不羡神仙! java中的单例模式
- 第五十五讲 插件设备树
- 从telnet www baidu com 80来玩一下http
- python稳健性检验_有哪些比较好的做异常值检测的方法?
- 各位端午节快乐 -- Happy the Dragon-Boat Festival
- python3爬取图片
- pytorch中SiLU激活函数
- surface pro3深度linux,surface pro4 安装deepin教程
- 李大齐称“新女友”只是力捧选手:我要保护她
- 致大学计算机老师的一封信,优秀书信作文:致大学老师的一封信