✨✨个人主页:沫洺的主页
📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏
📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏
📖Docker专栏📖Reids专栏📖MQ专栏📖SpringCloud专栏
💖💖如果文章对你有所帮助请留下三连✨✨
🍃集合
集合的特点
提供一种存储空间可变的存储模型,存储的数据容量可以发生改变
集合和数组的区别
共同点:都是存储数据的容器
不同点:
- 数组的长度不可变, 集合的长度可变
- 数组可以存基本数据类型和引用数据类型,集合只能存储引用数据类型(只要使用泛型,就不能使用基本数据类型)
- 集合有现成增删改查的方法
集合的分类
蓝框是接口,红框是实现类
集合体系分两大帮派,三大分支
两大帮派 : 单列 双列
三大分支 : List Set Map
单列
Collection是单列集合的最顶层接口
存取一次操作一个元素
双列
Map是双列集合的最顶层接口
存取一次操作一对元素
注意
java中仅仅有单列和双列集合
🍂泛型
概述
- 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的格式:<数据类型>; 注意:泛型只能支持引用数据类型。
- 集合体系的全部接口和实现类都是支持泛型的使用的。
本质
- 将数据类型参数化(参数是动态可变的)
泛型的好处
- 统一数据类型。
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
泛型可以在很多地方进行定义:
- 类后面-------------泛型类
- 方法申明上----------------泛型方法
- 接口后面--------------泛型接口
自定义泛型类
- 定义类时同时定义了泛型的类就是泛型类。
- 泛型类的格式:修饰符 class 类名<泛型变量>{ }
- 此处泛型变量可以随便写为任意标识,常见的如E、T、K、V等。
- 作用:编译阶段可以指定数据类型,类似于集合的作用。
- 核心思想:把出现泛型变量的地方全部替换成传输的真实数据类型
自定义泛型方法
- 定义方法时同时定义了泛型的方法就是泛型方法。
- 泛型方法的格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}
- 作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
- 核心思想:把出现泛型变量的地方全部替换成传输的真实数据类型
自定义泛型接口
- 使用了泛型定义的接口就是泛型接口。
- 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{}
- 作用:泛型接口可以让实现类选择当前功能需要操作的数据类型
- 泛型接口可以约束实现类,实现类可以在实现接口的时候传入自己操作的数据类型这样重写的方法都将是针对于该类型的操作。
泛型通配符,上下限
通配符:
- ?可以在“使用泛型”的时候代表一切类型
- E T K V 是在定义泛型的时候使用的。
泛型的上下限:
- ? extends Car: ?必须是Car或者其子类 泛型上限
- ? super Car : ?必须是Car或者其父类 泛型下限
注意
虽然BMW和BENZ都继承了Car但是ArrayList<BMW>和ArrayList<BENZ>与ArrayList<Car>没有关系的。
🌿Collection
collection是个接口,没有构造方法,不能实例化,所以只能通过多态的方式去创建对象
因为在实际开发中基本上我们都是直接通过collection的两个实现类去创建对象的,所以只是通过多态的形式在这演示单列集合通用的方法
单列集合常用的方法
方法名 描述 boolean add(E e) 添加元素 boolean remove(Object o) 从集合中移除指定的元素 boolean removeIf(Predicate o) 根据条件进行移除 boolean contains(Object o) 判断集合中是否存在指定的元素 int size() 集合的长度,也就是集合中元素的个数 void clear() 清空集合中的元素 boolean isEmpty() 判断集合中的元素是否为空
package cn.moming13; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class CollectionDemo { public static void main(String[] args) { Collection<String> c = new ArrayList<>();//泛型:<String>,指定集合中元素的类型为String //添加元素 c.add("沫洺"); c.add("真帅"); c.add("陌生人"); c.add("你好"); System.out.println(c); //删除元素 c.remove("陌生人"); c.removeIf(s -> s.startsWith("你"));//Lambda表达式 System.out.println(c); //是否存在 System.out.println(c.contains("沫洺")); //集合的长度 System.out.println(c.size()); //清空集合中的元素 c.clear(); System.out.println(c); //判断集合中的元素是否为空 System.out.println(c.isEmpty()); } }
[沫洺, 真帅, 陌生人, 你好] [沫洺, 真帅] true 2 [] true
遍历集合
1.普通for循环
2.迭代器(iterator)
使用集合变量名.iterator() 就可以获取到一个迭代器对象了
例: Iterator<String> iterator = collection.iterator();Iterator类中的方法
iterator.hasNext(); //判断集合中是否还有元素
iterator.next(); //获取集合中元素3.增强for
集合的增强for底层其实还是迭代器
例:for(String s : list){}
String - 集合中存储元素的数据类型
s - 变量,代表集合中的每一个元素 (名称可以随便定义)
list - 想要循环遍历的集合
package cn.moming13; import java.util.*; import java.util.function.Consumer; public class CollectionDemo { public static void main(String[] args) { //补充两种添加元素的方式 //Arrays.asList() //List.of() 此方法是java 9.0增加的 Collection c1 = new ArrayList(Arrays.asList(1,2,3,"hello","php",new Date())); Collection c2 = new ArrayList(List.of(1,2,3,"hello","php",new Date())); //遍历 //方式一,普通for for (int i = 0; i < c1.size(); i++) { Object o = ((ArrayList<?>) c1).get(i);//类型转换 System.out.println(o); } System.out.println("============="); //方式二,迭代器 Iterator iterator = c1.iterator(); while (iterator.hasNext()){ Object next = iterator.next(); System.out.println(next); } System.out.println("============="); //方式三,增强for for (Object o : c1) { System.out.println(o); } System.out.println("============="); //方式四,匿名内部类 c1.forEach(new Consumer() { @Override public void accept(Object o) { System.out.println(o); } }); //方式五,Lambda表达式 c1.forEach(o -> System.out.println(o)); //简化模式 c1.forEach(System.out::println); } }
1 2 3 hello php Sat Aug 13 17:03:12 CST 2022
总结:
普通for: 如果需要操作索引,使用普通for循环
迭代器: 如果在遍历过程中需要删除元素时,使用迭代器
增强for、Lambda表达式: 如果仅仅想遍历,可以使用这些方式迭代器注意事项
- 一次循环里面只能调用一次next()方法
- while循环里面如果没有调用 next方法() 就会进入死循环
- 同一个迭代器对象只能循环遍历一次
可以理解为迭代器将集合(拷贝)了一份,通过循环遍历将拷贝的集合中的元素一个个拿出来(调用next()方法一次就拿一个),拿完之后,这个拷贝的集合就变成空的了,就不能再次遍历使用了,如果没有拿,就说明集合中还有元素(hasNext()一直为true)。
迭代器最长使用的作用通过迭代器去删除集合的元素
package cn.moming13; import java.util.*; public class CollectionDemo1 { //作用: 循环遍历过程中删除集合中的数据 public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("b"); list.add("c"); list.add("d"); Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("b".equals(s)) { it.remove();//注意这里调用的是迭代器对象,不能使用集合对象 } } System.out.println(list); } }
[a, c, d]
注意:
- 在循环遍历集合的过程中,如果想要删除元素只能使用迭代器的删除方法,注意:这里说到是循环遍历集合的过程中
- 遍历过程中删除元素只能使用迭代器(Iterator)类中的删除方法
- 增强for的语法中是没有迭代器对象,所以没办法调用迭代器(Iterator)类中的删除方法
🍁List
List集合特点
- ArrayList、LinekdList :有序,可重复,有索引。
- 有序:存储和取出的元素顺序一致
- 有索引:可以通过索引操作元素
- 可重复:存储的元素可以重复
List特有方法
List集合因为支持索引,所以多了很多索引操作的独特api,其他Collection的功能List也都继承了。
方法名 描述 void add(int index,E element) 在此集合中的指定位置插入指定的元素 E remove(int index) 删除指定索引处的元素,返回被删除的元素 E set(int index,E element) 修改指定索引处的元素,返回被修改的元素 E get(int index) 返回指定索引处的元素
package cn.moming13; import java.util.ArrayList; import java.util.List; public class ListDemo { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); System.out.println(list); list.add(0, "A");//在此集合中的指定位置插入指定的元素 System.out.println(list); list.remove(0);//删除指定索引处的元素,返回被删除的元素 System.out.println(list); System.out.println(list.set(0, "A"));//修改指定索引处的元素,返回被修改的元素 System.out.println(list.get(0));//返回指定索引处的元素 } }
[a, b, c] [A, a, b, c] [a, b, c] a A
🌳数据结构
概述:数据结构是计算机底层存储、组织数据的方式。数据在内存中存储格式,是指数据相互之间是以什么方式排列在一起的。
常见的数据结构:
- 栈:后进先出,先进后出
- 队列:先进先出,后进后出
- 数组:数据在内存存储时都是一个挨着一个的,地址值都是连续的,所以查询快、增删慢
- 链表:链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址,所以查询慢、增删快
- 二叉树:
- 二叉查找树:
- 平衡二叉树:任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
- 红黑树:
红黑规则:
总结:
- 队列:先进先出,后进后出。
- 栈:后进先出,先进后出。
- 数组:内存连续区域,查询快,增删慢。
- 链表:元素是游离的,查询慢,首尾操作极快。
- 二叉树:永远只有一个根节点, 每个结点不超过2个子节点的树。
- 查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。
- 平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。
- 红黑树(就是基于红黑规则实现了自平衡的排序二叉树)
🌾ArrayList
ArrayList底层数据结构是数组(查询快,增删慢)
ArrayList常用的方法和上面Collextion及List演示的方法一样
注意事项如下代码注释
package cn.moming13; import java.util.ArrayList; /** * ArrayLit底层数据结构是数组,查询快,增删满 * 底层数组elementData * size默认指向0索引 * 默认长度为10,满容量后自动扩容原数组长度*1.5 */ public class ArrayListDemo { public static void main(String[] args) { //注意说的是底层数组 ArrayList<String> list = new ArrayList<>();//仅仅创建对象时 底层的数组长度是0 list.add("a");//第一次调用add方法,添加数据时, 数组长度是10 } }
💐LinkedList
LinkedList底层数据结构是双向链表(查询慢,增删快)
查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。
特有方法
方法名称
说明
public void addFirst(E e)
在该列表开头插入指定的元素
public void addLast(E e)
将指定的元素追加到此列表的末尾
public E getFirst()
返回此列表中的第一个元素
public E getLast()
返回此列表中的最后一个元素
public E removeFirst()
从此列表中删除并返回第一个元素
public E removeLast()
从此列表中删除并返回最后一个元素
package linkedList1; import java.util.LinkedList; /** * 底层数据结构是链表,查询慢,增删快 */ public class LiLi1 { public static void main(String[] args) { LinkedList<String> list = new LinkedList<>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); //addFirst(E e)头部添加 list.addFirst("ooo"); System.out.println(list); //addLast(E e)尾部添加 list.addLast("000"); System.out.println(list); //getFirst(E)获取第一个 String s1 = list.getFirst(); System.out.println(s1); //getLast()获取最后一个 String s2 = list.getLast(); System.out.println(s2); //removeFirst()删除第一个 list.removeFirst(); System.out.println(list); //removeLast()删除最后一个 list.removeLast(); System.out.println(list); } }
[ooo, aaa, bbb, ccc, ddd] [ooo, aaa, bbb, ccc, ddd, 000] ooo 000 [aaa, bbb, ccc, ddd, 000] [aaa, bbb, ccc, ddd]
🌲set
存取无序 不可以重复 没有索引
set集合的特点:
- 元素不可以重复
- 没有索引
- 存数据和取数据的顺序不一致
- set集合的两个实现类: HashSet TreeSet
package cn.moming13; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; public class SetDemo { private static double[] arr = {2.2,5.5,6.6,2.2,8.8,1.1,8.8,5.5,2.2,6.6}; public static void main(String[] args) { Set<Double> set = new TreeSet<>(); for (double v : arr) {//将数组元素添加到集合中 set.add(v); } System.out.println(set); //遍历 //迭代器 Iterator<Double> iterator = set.iterator(); while (iterator.hasNext()){ Double next = iterator.next(); System.out.println(next); } System.out.println("============="); //增强for for (Double integer : set) { System.out.println(integer); } System.out.println("============="); //Lambda表达式 set.forEach(System.out::println); } }
[1.1, 2.2, 5.5, 6.6, 8.8] 1.1 2.2 5.5 6.6 8.8
遍历set集合不能使用普通for,因为set集合没有索引
🌺HashSet
HashSet底层数据结构是哈希表
哈希表:
- JDK7,底层采用数组+链表实现,可以说是一个元素为链表的数组
- JDK8以后,在长度比较长的时候,底层实现了优化,数组+链表(红黑树阈值为6时转换为链表)+红黑树(链表阈值为8时转换为红黑树)
哈希值:
- 是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值:
- public int hashCode():返回对象的哈希码值
对象的哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
默认长度为16,加载因子0.75,当达到12时,会扩容2倍
当挂在下面的元素过多,那么不利于查询,所以在JDK8以后, 当链表长度超过8的时候,自动转换为红黑树。 存储流程不变。
总结
- 创建一个默认长度16,默认加载因为0.75的数组,数组名table
- 根据元素的哈希值跟数组的长度计算出应存入的位置
- 判断当前位置是否为null,如果是null直接存入
- 如果位置不为null,表示有元素,则调用equals方法比较属性值
- 如果一样,则不存,如果不一样,则存入数组,老元素挂在新元素下面
- 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
注意
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以元素唯一
- 如果HashSet要存储自定义的类时,必须重写HashCode和equals
用户类
package cn.moming13; public class User { private String name; private int age; public User() { } public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
集合类
package cn.moming13; import java.util.HashSet; public class HashSetUser { public static void main(String[] args) { HashSet<User> set = new HashSet<>(); set.add(new User("张三",18)); set.add(new User("张三",15)); set.add(new User("张三",18)); set.add(new User("李四",12)); System.out.println(set); } }
有重复对象
再次运行
🌷TreeSet
TreeSet底层数据结构是树
特点
- 元素不可以重复
- 没有索引
- 存数据和取数据的顺序不一致
- 可以对元素进行排序
package cn.moming13; import java.util.Iterator; import java.util.TreeSet; public class SetDemo { private static double[] arr = {2.2,5.5,6.6,2.2,8.8,1.1,8.8,5.5,2.2,6.6}; public static void main(String[] args) { TreeSet<Double> set = new TreeSet<>(); for (double v : arr) { set.add(v); } System.out.println(set); //遍历 //迭代器 Iterator<Double> iterator = set.iterator(); while (iterator.hasNext()){ Double next = iterator.next(); System.out.println(next); } System.out.println("============="); //增强for for (Double integer : set) { System.out.println(integer); } System.out.println("============="); //Lambda表达式 set.forEach(System.out::println); } }
TreeSet集合存储学生对象
学生类
package cn.moming13; public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } 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; } }
集合类
package cn.moming13; import java.util.TreeSet; public class TreeSetStudent { public static void main(String[] args) { TreeSet<Student> set = new TreeSet<>(); Student stu1 = new Student("张三",18); Student stu2 = new Student("张三",18); Student stu3 = new Student("李四",20); Student stu4 = new Student("王五",22); set.add(stu1); set.add(stu2); set.add(stu3); set.add(stu4); System.out.println(set); } }
运行出现异常
想要使用TreeSet存储对象, 必须需制定排序规则,以下两种排序方式
自然排序
自然排序Comparable的使用
- 使用空参构造创建TreeSet集合
- 自定义的Student类实现Comparable接口
- 重写里面的compareTo方法
重写compareTo()方法
再对学生类进行toString重写
package cn.moming13; public class Student implements Comparable<Student>{ private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } 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; } @Override public int compareTo(Student o) { //按照对象的年龄进行排序 //主要判断条件 int result = this.age-o.age; //次要判断条件 //如果年龄相等,就按照姓名排序,年龄不相等就按年龄排序 result=result==0?this.name.compareTo(o.name):result; return result; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
再次运行
比较器排序
- TreeSet的带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
自然排序是对学生类进行接口实现,而比较器就方便的多,直接带参构造重写compare
学生类
package cn.moming13; public class Student{ private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } 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; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
集合类
package cn.moming13; import java.util.Comparator; import java.util.TreeSet; public class TreeSetStudent { public static void main(String[] args) { TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { int result=o1.getAge()-o2.getAge(); result=result==0?o1.getName().compareTo(o2.getName()):result; return result; } }); Student stu1 = new Student("张三",18); Student stu2 = new Student("张三",18); Student stu3 = new Student("李四",20); Student stu4 = new Student("王五",22); set.add(stu1); set.add(stu2); set.add(stu3); set.add(stu4); System.out.println(set); } }
Lambda表达式
两种比较方式小结
- 自然排序:自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序。
- 比较器排序:创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序。
- 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,使用比较器排序
两种方式中,关于返回值的规则:
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
- 如果返回值为正数,表示当前存入的元素是较大值,存右边