Java 基础入门 · 全套重点总结
整理自:1~6.md + 1~6.xmind(共 12 个文件)
一、Java 入门 1.1 JDK / JRE / JVM 关系
组件
包含关系
说明
JDK
= JRE + 开发工具(javac、java 等)
开发者必须安装
JRE
= JVM + Java 核心类库
只运行已有程序时安装
JVM
Java 虚拟机
实现跨平台的核心,一次编译处处运行
1.2 编写 → 编译 → 运行 1 .java 源文件 →(javac 编译)→ .class 字节码 →(java 运行,JVM 解释执行)→ 结果
1.3 基本数据类型(8 种)
类型
关键字
占用字节
默认值
说明
字节型
byte
1
0
-128 ~ 127
短整型
short
2
0
整型
int
4
0
整数默认 类型
长整型
long
8
0L
字面量后加 L
单精度
float
4
0.0F
字面量后加 F
双精度
double
8
0.0
浮点数默认 类型
字符型
char
2
‘\u0000’
单引号,长度必须为 1
布尔型
boolean
1
false
true / false
1.4 类型转换
类型
方向
规则
隐式转换 (自动)
小范围 → 大范围
byte → short → int → long → float → double
强制转换
大范围 → 小范围
(目标类型) 变量,可能损失精度
⚠️ 两个 byte 相加结果提升为 int;char 参与运算时转为其 ASCII 值(int)。
1.5 常用运算符速查
类型
运算符
说明
算术
+ - * / %
整数相除只得整数;% 取余
赋值
+= -= *= /= %=
扩展赋值运算符隐含强制类型转换
比较
> < >= <= == !=
返回 boolean
逻辑
&& || !
&& 短路:左侧 false 则右侧不算;|| 短路:左侧 true 则右侧不算
三元
条件 ? 值1 : 值2
条件为 true 取值1,为 false 取值2
+ 操作的三种情况:
场景
行为
数字 + 数字
算术相加(注意类型提升)
字符 + 数字
字符转 ASCII 值后相加,结果为 int
包含字符串
字符串拼接,从左到右逐步执行
1 2 3 System.out.println("5+5=" + 5 + 5 ); System.out.println("5+5=" + (5 + 5 )); System.out.println(1 + 99 + "年" );
1.6 标识符命名规范
规范
说明
组成
字母、数字、_、$,不能以数字开头,不能是关键字
小驼峰
方法名、变量名:myVariableName
大驼峰
类名、接口名:MyClassName
二、流程控制 2.1 if-else 结构 1 2 3 4 5 6 7 8 if (条件) { } else if (条件2 ) { } else { }
2.2 switch 结构
特性
说明
支持类型
byte short int char String 枚举
break
不写会发生穿透 (继续执行下一个 case)
default
所有 case 都不匹配时执行
JDK14+ 箭头语法
case 值 -> 语句; 自动避免穿透
1 2 3 4 5 switch (light) { case 1 -> System.out.println("红灯停" ); case 2 -> System.out.println("绿灯行" ); default -> System.out.println("故障" ); }
2.3 三种循环对比
循环
格式
特点
适用场景
for
for(初始化; 条件; 步进)
先判断后执行
次数确定 时推荐
while
while(条件){ }
先判断后执行
次数不确定 时推荐
do-while
do{ }while(条件);
先执行后判断
至少执行一次时用
死循环写法:for(;;) / while(true) / do{ }while(true)
2.4 break 和 continue
关键字
作用
使用位置
break
结束整个 所在循环(或 switch)
循环、switch
continue
结束本次 循环,继续下一次
循环
标号语句
标号: for(...){ ... break/continue 标号; }
用于跳出指定的外层循环
2.5 Scanner(键盘输入) 1 2 3 4 5 6 import java.util.Scanner;Scanner sc = new Scanner (System.in);int num = sc.nextInt(); double d = sc.nextDouble(); String word = sc.next(); String line = sc.nextLine();
2.6 Random(随机数) 1 2 3 4 5 6 import java.util.Random;Random r = new Random ();int n1 = r.nextInt(); int n2 = r.nextInt(100 ); int n3 = r.nextInt(100 ) + 1 ; int n4 = r.nextInt(min, max);
三、数组 3.1 核心概念
项目
说明
定义
存储同类型、固定长度 的数据容器
索引
从 0 开始,最大索引为 length - 1
存储位置
堆内存(new 出来的都在堆);数组变量本身在栈中,存的是地址值
3.2 两种初始化方式
方式
格式
特点
动态初始化
int[] arr = new int[长度];
指定长度,元素为默认值
静态初始化
int[] arr = {1, 2, 3};
指定元素,系统决定长度
默认初始值:
类型
默认值
int / byte / short / long
0
float / double
0.0
char
'\u0000'(空字符)
boolean
false
引用类型(String 等)
null
3.3 两个常见异常
异常
原因
解决
ArrayIndexOutOfBoundsException
访问的索引不在 [0, length-1] 范围内
检查索引范围
NullPointerException
数组变量为 null 后仍操作
确保数组有有效地址
3.4 常用操作代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); }for (int num : arr) { System.out.println(num); }int max = arr[0 ];for (int i = 1 ; i < arr.length; i++) { if (arr[i] > max) max = arr[i]; }for (int i = 0 ; i < arr.length - 1 ; i++) { for (int j = i + 1 ; j < arr.length; j++) { if (arr[i] > arr[j]) { int t = arr[i]; arr[i] = arr[j]; arr[j] = t; } } } for (int i = 0 ; i < arr.length - 1 ; i++) { for (int j = 0 ; j < arr.length - i - 1 ; j++) { if (arr[j] > arr[j+1 ]) { int t = arr[j]; arr[j] = arr[j+1 ]; arr[j+1 ] = t; } } } int min = 0 , max = arr.length - 1 ;while (min <= max) { int mid = (min + max) / 2 ; if (target > arr[mid]) min = mid + 1 ; else if (target < arr[mid]) max = mid - 1 ; else { System.out.println("找到,索引:" + mid); break ; } }
3.5 内存分配(方法区 / 栈 / 堆)
区域
存储内容
方法区
.class 字节码文件
栈
方法运行时的局部变量(含基本类型变量和引用变量)
堆
new 出来的对象和数组
两个引用变量指向同一数组时,一个修改,另一个也能看到变化(地址相同)。
四、方法 4.1 方法定义格式 1 2 3 4 public static 返回值类型 方法名(参数类型 参数名, ...) { return 返回值; }
4.2 方法注意事项
规则
说明
先定义后调用
方法必须先定义才能调用
不能嵌套定义
方法内部不能再定义方法
void 方法
可省略 return,或写 return;(不带值)
return 后无代码
return 后面的代码是无效代码
方法在栈中执行
每次调用进栈,执行完弹栈
4.3 形参 vs 实参(⭐ 常考)
参数类型
传递方式
形参改变影响实参?
基本数据类型
传递数据值
❌ 不影响
引用数据类型(数组/对象)
传递地址值
✅ 会影响
1 2 3 4 5 static void change (int a) { a = 100 ; } static void fill (int [] arr) { arr[0 ] = 999 ; }
4.4 定义方法的两个明确
明确项
问题
返回值类型
方法执行完是否需要返回结果?有则写类型,没有写 void
参数列表
完成功能需要哪些外部数据?写对应的类型和数量
五、方法重载 · 可变参数 · 循环嵌套 · 递归 5.1 方法重载(Overload)
项目
说明
定义
同一类中,方法名相同,参数列表不同(个数/类型/顺序不同)
与什么无关
与返回值类型、参数名称、修饰符无关
作用
同一功能对不同类型/数量参数的统一命名
1 2 3 4 public static int sum (int a, int b) { return a + b; }public static int sum (int a, int b, int c) { return a + b + c; }public static double sum (double a, double b) { return a + b; }
5.2 可变参数
项目
说明
格式
数据类型... 变量名(如 int... nums)
本质
方法内部是一个数组
调用方式
可传参数列表、可传数组、可不传参
限制1
一个方法只能有一个 可变参数
限制2
可变参数必须在参数列表最后
1 2 3 4 5 6 7 8 public static int sum (int ... nums) { int total = 0 ; for (int n : nums) total += n; return total; } sum(1 , 2 , 3 ); sum(new int []{1 , 2 }); sum();
5.3 循环嵌套
特点
说明
执行次数
外层执行 1 次,内层执行完整一遍
总次数
外层次数 × 内层次数
标号语句
标号: for(...) 配合 break/continue 标号 控制指定层循环
1 2 3 4 5 for (int i = 1 ; i <= 3 ; i++) { for (int j = 1 ; j <= 5 ; j++) System.out.print("*" ); System.out.println(); }
5.4 递归
项目
说明
定义
方法自己调用自己
必须条件
① 找到规律 (递推公式);② 找到出口 (终止条件)
风险
递归次数过多会导致栈内存溢出(StackOverflowError)
1 2 3 4 5 public static int sum (int n) { if (n == 1 ) return 1 ; return n + sum(n - 1 ); }
六、面向对象(OOP) 6.1 三大特征速记
特征
核心关键字
说明
封装
private + getter/setter
隐藏内部实现,对外提供安全接口
继承
extends
子类复用父类非私有成员,减少重复代码
多态
父类引用指向子类对象
同一接口,不同实现,提升扩展性
6.2 类的组成
成员
说明
成员变量
类中方法外,存于堆内存 ,有默认初始化值
成员方法
去掉 static 的方法,描述行为
构造方法
方法名 = 类名,无返回值类型,创建对象时调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; } }
6.3 构造方法注意事项
规则
说明
无返回值类型
连 void 都没有
方法名 = 类名
大小写完全一致
调用时机
new 类名() 时自动调用,每次 new 执行一次
不能手动调用
不能用对象调用构造方法
默认空参
未定义任何构造方法时,JVM 提供默认无参构造
定义后失效
一旦自定义了构造方法,JVM 不再提供默认空参构造 (建议手动补写空参)
6.4 this 关键字
用途
格式
说明
区分同名变量
this.成员变量
成员变量 vs 局部变量重名时区分
调用本类方法
this.方法名()
在方法内调用本对象方法
调用本类构造
this(参数)
必须在构造方法第一行
代表当前对象
this
谁调用方法,this 就代表谁
6.5 成员变量 vs 局部变量
对比项
成员变量
局部变量
位置
类中方法外
方法内部或方法参数
内存
堆内存
栈内存
生命周期
随对象存在/消失
随方法调用存在/结束
默认值
✅ 有默认初始化值
❌ 无,必须先赋值再使用
6.6 JavaBean 标准类(实体类)结构 1 2 3 4 5 6 7 8 9 10 11 12 13 public class 类名 { private 数据类型 属性名; public 类名() { } public 类名(参数...) { this .属性 = 参数; } public 数据类型 get属性名() { return 属性名; } public void set属性名(数据类型 属性名) { this .属性名 = 属性名; } }
6.7 对象内存图(简要) 1 2 3 4 5 栈内存 堆内存 ┌──────────┐ ┌─────────────┐ │ stu │──地址──→ │ name: null │ │ (变量) │ │ age: 0 │ └──────────┘ └─────────────┘
多个对象引用同一地址时,一处修改,所有引用都能看到变化。
七、综合速查 流程控制选择
场景
推荐
判断区间范围、复杂条件
if-else
判断固定值、多分支
switch
次数确定
for
次数不确定
while
至少执行一次
do-while
方法相关核心口诀
概念
口诀
方法重载
方法名相同,参数列表不同 (与返回值/修饰符无关)
基本类型参数
值传递,形参不影响实参
引用类型参数
地址传递,形参改变影响实参
可变参数
本质是数组,放在参数列表最后,只能有一个
递归
找规律 + 找出口 ,递归不能过深
封装规范速记 1 2 3 成员变量:private(封装) 构造方法:空参 + 满参(都要有) 对外方法:public getter + public setter
API · String · StringBuilder · StringJoiner · 重点总结 一、API 概述
项目
说明
全称
Application Programming Interface(应用程序编程接口)
使用步骤
① 查看类(所在包、是否需要导包)② 查看构造方法 ③ 查看成员方法
二、String 类 2.1 核心概念
项目
说明
所在包
java.lang,无需导包
不可变性
字符串内容创建后不能改变 ;”改变”实际是创建了新对象
底层实现
JDK 8 及以前:char[];JDK 9 及以后:byte[]
共享性
内容不可变,所以可以被多个引用共享
2.2 两种创建方式对比(⭐ 常考)
创建方式
示例
内存行为
直接赋值 (推荐)
String s = "abc"
先查字符串常量池,有则复用,无则新建,同内容只有一个对象
构造方法创建
String s = new String("abc")
直接在堆内存开辟新空间,每次 new 地址都不同
建议:只有在将字节数组或字符数组转字符串 时才考虑用构造方法,其他场景用直接赋值。
1 2 3 4 5 6 String s1 = "abc" ;String s2 = "abc" ;String s3 = new String ("abc" );System.out.println(s1 == s2); System.out.println(s1 == s3); System.out.println(s1.equals(s3));
2.3 String 常用构造方法
构造方法
说明
new String()
创建空白字符串,等价于 ""
new String(char[] chs)
将字符数组转为字符串
new String(byte[] bs)
将字节数组转为字符串
2.4 String 常用方法速查表 ① 判断类
方法
说明
equals(String s)
比较内容,区分 大小写
equalsIgnoreCase(String s)
比较内容,忽略 大小写
contains(String str)
是否包含指定子串
startsWith(String str)
是否以指定字符串开头
endsWith(String str)
是否以指定字符串结尾
isEmpty()
是否为空字符串(长度为 0)
② 获取类
方法
说明
length()
返回字符串长度
charAt(int index)
返回指定索引处的字符
indexOf(String str)
返回子串首次出现的索引,不存在返回 -1
substring(int begin)
从 begin 截取到末尾
substring(int begin, int end)
截取 [begin, end) 区间,含头不含尾
③ 转换类
方法
说明
toCharArray()
字符串 → 字符数组
getBytes()
字符串 → 字节数组
toLowerCase()
转小写
toUpperCase()
转大写
trim()
去除首尾空格
valueOf(任意类型)
其他类型 → 字符串(静态方法)
④ 操作类
方法
说明
replace(String old, String new)
替换所有匹配的子串
split(String regex)
按规则切割,返回字符串数组
concat(String str)
拼接字符串,等价于 +
2.5 split 的两个注意点
情况
说明
头/尾切分
留头不留尾 (末尾的空字符串会被丢弃)
按 . 切分
. 是正则特殊字符,需写成 "\\."
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 String tel = "13812341234" ;String start = tel.substring(0 , 3 ); String end = tel.substring(7 ); System.out.println(start + "****" + end); String s = "Hello" ;for (int i = 0 ; i < s.length(); i++) { System.out.print(s.charAt(i) + " " ); } String[] arr = "张三,23" .split("," ); System.out.println(arr[0 ]); System.out.println(arr[1 ]);
2.6 String 拼接的底层说明
场景
底层行为
字符串常量拼接
javac 编译时直接优化合并,等价于直接写字符串
变量参与的拼接
通过 StringBuilder 对象完成,每次 + 产生新对象
三、StringBuilder 类 3.1 核心概念
项目
说明
定义
内容可变 的字符串容器
作用
提升字符串大量拼接的效率(全程只有一个对象)
3.2 String vs StringBuilder(⭐ 常考)
对比项
String
StringBuilder
内容
不可变
可变
创建方式
双引号或 new
只能用构造方法
拼接效率
低(每次 + 创建新对象,产生大量垃圾)
高(始终操作同一对象)
比较内容
✅ equals() 可用
❌ 不能直接比较内容
适用场景
内容固定或少量拼接
大量字符串拼接、反转
3.3 StringBuilder 构造方法
方法
说明
new StringBuilder()
创建空白可变字符串(内部默认 16 字符的数组)
new StringBuilder(String str)
用指定字符串内容初始化
3.4 StringBuilder 常用方法
方法
说明
返回值
append(任意类型)
追加内容到末尾
返回 this 本身 (支持链式编程)
reverse()
将内容反转
返回 this 本身
toString()
转换为 String
String
length()
返回长度
int
insert(int index, 内容)
在指定位置插入
StringBuilder
delete(int start, int end)
删除 [start, end) 区间
StringBuilder
3.5 链式编程原理 append() 和 reverse() 都返回 this(对象本身),因此可以一直链式调用:
1 2 3 4 5 6 7 8 String result = new StringBuilder () .append("Hello" ) .append(" " ) .append("World" ) .reverse() .toString(); System.out.println(result);
3.6 String ↔ StringBuilder 互转
转换方向
方法
String → StringBuilder
new StringBuilder(str)
StringBuilder → String
sb.toString()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 String s = "hello" ;StringBuilder sb = new StringBuilder (s);StringBuilder sb = new StringBuilder ();sb.append("hello" ); String s = sb.toString();public static String reverse (String str) { return new StringBuilder (str).reverse().toString(); } public static String arrayToString (int [] arr) { StringBuilder sb = new StringBuilder ("[" ); for (int i = 0 ; i < arr.length; i++) { sb.append(arr[i]); if (i < arr.length - 1 ) sb.append(", " ); } return sb.append("]" ).toString(); }
四、StringJoiner 类(Java 8+) 4.1 核心概念
项目
说明
所在包
java.util,需要导包
定义
专门用于构建带分隔符 的字符串序列,可选前缀和后缀
优势
自动处理分隔符,无需手动判断是否是最后一个元素
4.2 构造方法
构造方法
说明
new StringJoiner(String delimiter)
只设置分隔符
new StringJoiner(String delimiter, String prefix, String suffix)
设置分隔符 + 前缀 + 后缀
4.3 常用方法
方法
说明
返回值
add(String element)
添加元素
返回 this (支持链式调用)
toString()
转为 String
String
setEmptyValue(String emptyValue)
设置为空时的默认显示值
void
length()
返回当前结果字符串长度
int
1 2 3 4 5 6 7 8 9 10 11 12 13 14 StringJoiner sj1 = new StringJoiner (", " );sj1.add("Java" ).add("Python" ).add("C++" ); System.out.println(sj1); StringJoiner sj2 = new StringJoiner (", " , "[" , "]" );sj2.add("Apple" ).add("Banana" ).add("Orange" ); System.out.println(sj2); StringJoiner sj3 = new StringJoiner (", " , "{" , "}" );sj3.setEmptyValue("{}" ); System.out.println(sj3);
五、三种拼接方式对比速查(⭐ 选型指南)
方式
优点
缺点
适用场景
+ 运算符
简单直接
每次创建新对象,性能差;手动管理分隔符麻烦
简单的少量拼接(1~3个)
StringBuilder
性能高,功能灵活
需手动处理分隔符(if 判断)
循环大量拼接、反转、性能敏感场景
StringJoiner
自动处理分隔符,支持前后缀,代码清晰
Java 8+;略有额外开销
格式化列表(如 [a, b, c])、SQL IN 语句、路径拼接
六、综合对比速查 == vs equals
比较对象
==
equals()
基本类型
比较数据值
—
引用类型
比较地址值
比较内容 (String 已重写)
⚠️ 字符串比较内容必须用 equals() ,不能用 ==。
String 常量池图解 1 2 3 4 String s1 = "abc"; → 常量池:["abc"] String s2 = "abc"; → s2 复用常量池中的 "abc",s1 == s2 为 true String s3 = new String("abc"); → 堆中新建对象,s1 == s3 为 false s1.equals(s3) 为 true(内容相同)
选用建议总结
需求
推荐
固定内容字符串
String 直接赋值
循环拼接大量字符串
StringBuilder
带分隔符格式化输出(如列表)
StringJoiner
将数组/字节数组转字符串
new String(数组)
字符串反转
new StringBuilder(str).reverse().toString()
忽略大小写比较
s1.equalsIgnoreCase(s2)
继承 · 抽象类 · final · Object · 重点总结 一、继承 1.1 核心概念
项目
说明
定义
让类与类之间产生父子关系,子类可直接使用父类非私有 的属性和方法
关键字
extends
好处
提高代码复用性 (共性抽取到父类);提高代码维护性 (修改一处即可)
弊端
类的耦合性增强,父类变化子类也必须跟着变化,削弱子类独立性
使用场景
is-a 关系:A 是 B 的一种(如:猫是动物的一种)
1.2 Java 继承特点
特点
说明
单继承
一个类只能继承一个 父类(class A extends B, C ❌)
多层继承
支持多层:A → B → C(具有传递性)
顶层父类
所有类都直接或间接继承 Object 类
子类则可以使用父类中非私有的成员
1.3 继承中的变量访问——就近原则 访问顺序:局部变量 → 子类成员变量 → 父类成员变量 → 报错
1 2 3 4 5 6 7 8 9 10 class Fu { int num = 10 ; }class Zi extends Fu { int num = 20 ; public void show () { int num = 30 ; System.out.println(num); System.out.println(this .num); System.out.println(super .num); } }
1.4 this 和 super 全面对比(⭐ 常考)
用途
this(本类)
super(父类)
访问成员变量
this.变量名
super.变量名
调用成员方法
this.方法名()
super.方法名()
调用构造方法
this() / this(参数)
super() / super(参数)
⚠️ this() 和 super() 都只能写在构造方法第一行,且二者不能共存。
1.5 继承中构造方法的规则
规则
说明
子类构造默认首行
隐式调用 super(),完成父类数据初始化
父类无无参构造
子类必须手动调用父类可用的带参构造 super(参数)
目的
子类使用父类数据前,必须先初始化父类部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 class Fu { int num; public Fu () { System.out.println("父空参" ); } public Fu (int num) { this .num = num; System.out.println("父有参" ); } } class Zi extends Fu { public Zi () { super (); System.out.println("子空参" ); } public Zi (int num) { super (num); System.out.println("子有参" ); } } public class Employee { private String name; private int age; private int salary; public Employee () {} public Employee (String name, int age, int salary) { this .name = name; this .age = age; this .salary = salary; } public void work () { System.out.println("员工: " + name + ", 正在努力的工作~" ); } ... } public class Teacher extends Employee { public Teacher () { } public Teacher (String name, int age, int salary) { super (name, age, salary); } @Override public void work () { System.out.println("讲师: " + getName() + ", 正在努力的讲解面向对象(OOP)编程~" ); } }
1.6 方法重写(Override)
项目
说明
定义
子类中定义与父类方法名、参数列表完全一样 的方法
作用
子类可以在沿袭父类功能基础上,定制自己的特有行为
注解
@Override:编译器校验是否真正构成重写(推荐加上)
访问规则
重写后,调用时遵循就近原则 ,优先执行子类重写的方法
方法重写的三大注意事项:
规则
说明
私有方法
❌ 不能被重写 (子类继承不到私有方法)
访问权限
子类重写方法的权限必须 ≥ 父类 (不能降低)
静态方法
❌ 不能被重写 (子类定义同名静态方法不构成重写)
1 2 3 4 5 6 7 8 9 10 class Fu { private void show () { } void method () { } } class Zi extends Fu { @Override public void method () { } }
1.7 权限修饰符(⭐ 常考)
修饰符
本类
同包其他类
不同包子类
不同包无关类
private
✅
❌
❌
❌
缺省(无修饰符)
✅
✅
❌
❌
protected
✅
✅
✅
❌
public
✅
✅
✅
✅
开发规范:成员变量用 private 封装,对外提供的方法用 public 。
二、抽象类 2.1 核心概念
项目
说明
定义
含有抽象方法的类必须定义为抽象类
关键字
abstract
不能实例化
抽象类不能直接 new 对象 ,只能被继承
有构造方法
抽象类可以有构造方法 ,供子类 super() 调用
抽象方法
只有声明,没有方法体(public abstract void eat();)
2.2 抽象类的特点
规则
说明
有抽象方法 → 必须是抽象类
public abstract class 类名 {}
抽象类中不一定有抽象方法
可以只有普通方法
子类处理方式
① 重写所有 抽象方法;② 子类本身也声明为抽象类
抽象方法不能有方法体
public abstract void eat() {} ❌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public abstract class Animal { private String name; public abstract void eat () ; public void breath () { System.out.println("呼吸" ); } public Animal (String name) { this .name = name; } } public class Dog extends Animal { public Dog (String name) { super (name); } @Override public void eat () { System.out.println(getName() + "吃骨头" ); } } Dog d = new Dog ("旺财" );
2.3 模板方法设计模式 用途 :解决多个子类中存在重复代码的问题。
步骤
说明
1
抽象类定义模板方法 (含固定流程),用 final 修饰防止被重写
2
把方法中不确定的部分 定义为抽象方法
3
子类继承抽象类,只重写抽象方法即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public abstract class WritingTemplate { public final void write () { System.out.println("开头:我的爸爸" ); content(); System.out.println("结尾:这就是我的爸爸!" ); } public abstract void content () ; } public class Tom extends WritingTemplate { @Override public void content () { System.out.println("那是一个秋天,爸爸骑车接我放学……" ); } }
三、final 关键字 3.1 final 修饰三种目标
修饰目标
效果
类
该类不能被继承 (俗称”太监类”,如 String)
方法
该方法不能被子类重写
变量
变量值只能赋值一次 ,相当于常量
3.2 final 修饰变量的细节
变量类型
final 的约束
基本数据类型
数据值 不能改变
引用数据类型
地址值 不能改变,但地址指向的对象内容可以改变
局部变量
在方法内只赋值一次即可(先定义后赋值也行)
成员变量
系统默认值失效,必须在构造方法中 或定义时 完成赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 final int [] arr = {10 , 20 , 30 };arr[0 ] = 100 ; class MyClass { final int num; final int num2 = 20 ; public MyClass () { num = 10 ; } } public final class StringLike { }public class Fu { public final void show () { } } public class Zi extends Fu { }
四、Object 类 4.1 核心概念
项目
说明
地位
所有类的最终父类,任何类都继承自 Object
继承方式
定义类时不写 extends,默认 extends Object
4.2 常用方法
方法
默认行为
重写目的
toString()
返回 类全名@十六进制哈希值,如 Student@1a2b3c
返回对象内容 (属性拼接的字符串)
equals(Object o)
用 == 比较地址值 (只有同一个对象才返回 true)
比较对象内容 (属性是否相同)
hashCode()
根据对象地址生成 int 哈希值
与 equals 配套重写,保证逻辑一致性
System.out.println(对象) 内部会自动调用对象的 toString() 方法。
4.3 equals 重写三步骤(⭐ 标准模板) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; if (age != student.age) return false ; return Objects.equals(name, student.name); } @Override public int hashCode () { int result = name != null ? name.hashCode() : 0 ; result = 31 * result + age; return result; }
4.4 hashCode 三条规定
规定
说明
同对象一致性
同一对象多次调用 hashCode(),结果必须相同
equals 为 true → hashCode 必须相同
内容相同的对象哈希值必须一致
equals 为 false → hashCode 最好不同
哈希值不同可减少哈希碰撞(允许相同,但影响性能)
五、综合对比速查 继承 vs 接口(预告)
对比项
继承(extends)
接口(implements)
数量限制
单继承(只能一个父类)
多实现(可实现多个接口)
构造方法
父类有构造方法
接口无构造方法
成员变量
可以是任意类型
只能是常量
方法
可以是普通方法
默认只有抽象方法(JDK8+ 可有默认/静态方法)
抽象类 vs 普通类
对比项
普通类
抽象类
关键字
class
abstract class
能否实例化
✅
❌
构造方法
✅
✅
抽象方法
❌
✅(可以有,也可以没有)
子类要求
无特殊要求
必须重写所有抽象方法(或子类也是抽象类)
toString / equals 重写总结
方法
不重写时
重写后
快捷键
toString()
打印地址值(无意义)
打印对象属性内容
Alt + Insert → toString
equals()
比较地址值(几乎都是 false)
比较内容(三步:地址→类型→内容)
Alt + Insert → equals and hashCode
hashCode()
基于地址生成
基于内容属性生成,与 equals 保持一致
与 equals 一起生成
final 修饰目标速查
修饰
效果
示例
final class
不能被继承
public final class String
final method
不能被重写
模板方法模式中的模板方法
final 基本变量
值不能改变
final int MAX = 100;
final 引用变量
地址不能改变,内容可变
final int[] arr = {1,2,3};
static · 接口 · 多态 · 内部类 · 工具类 · 重点总结 一、static 关键字 1.1 核心特点
特点
说明
属于类
不依赖对象,依赖类,内存中唯一一份
随类加载
第一次使用该类时,static 内容优先加载到方法区的静态区
所有对象共享
一处修改,所有对象可见
只能用静态
静态内容中只能访问静态成员,不能用 this/super,不能访问实例成员
非静态可用静态
非静态方法可以访问静态变量和静态方法
1.2 类变量 vs 实例变量
对比项
类变量(static 修饰)
实例变量(无 static)
归属
属于类
属于对象
内存份数
唯一一份
每个对象各一份
调用方式
类名.变量名(推荐)
对象名.变量名
初始化时机
类加载时
创建对象时
适用场景
所有对象共享的数据(如班级名、饮水机品牌)
每个对象独有的数据(如姓名、年龄)
1.3 类方法 vs 实例方法
对比项
类方法(static 修饰)
实例方法(无 static)
调用方式
类名.方法名()(推荐)
对象名.方法名()
能否用 this
❌
✅
能访问实例成员
❌
✅
能访问静态成员
✅
✅
典型用途
工具类方法、静态工厂方法
操作对象自身数据的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Student { static String room; String name; public static void printRoom () { System.out.println("教室:" + room); } public void printName () { System.out.println(name + " 在 " + room); } } Student.room = "B101" ; Student.printRoom();
1.4 代码块执行顺序(⭐ 常考)
代码块类型
格式
执行时机
执行次数
静态代码块
static { }
类加载时(第一次使用该类)
只执行一次
构造代码块
{ }
每次调用构造方法时,在 super() 之后第一行之前
每次创建对象都执行
构造方法
类名()
创建对象时
每次创建对象都执行
总口诀:先静态、后实例;先父类、后子类;先代码块、后构造方法
1 2 3 第一次 new Dog(): ① 父类静态块 → ② 子类静态块 → ③ 父类构造代码块 → ④ 父类构造方法 → ⑤ 子类构造代码块 → ⑥ 子类构造方法 第二次 new Dog(): ③ 父类构造代码块 → ④ 父类构造方法 → ⑤ 子类构造代码块 → ⑥ 子类构造方法 (静态块不再执行)
二、接口 2.1 接口成员(按 JDK 版本)
成员类型
JDK 版本
默认修饰符
说明
常量
全版本
public static final
值不可修改,可省略修饰符
抽象方法
全版本
public abstract
可省略修饰符,实现类必须重写
默认方法
JDK 8+
public default
default 不可省略,不强制重写,解决接口升级问题
静态方法
JDK 8+
public static
static 不可省略,只能通过接口名调用
私有方法
JDK 9+
private
供默认方法/静态方法内部复用,外部不可见
⚠️ 接口没有构造方法,不能实例化。
2.2 接口中各方法调用规则
方法类型
能否通过对象调用
能否通过实现类名调用
只能通过接口名调用
抽象方法(重写后)
✅
❌
❌
默认方法
✅(可重写)
❌
❌
静态方法
❌
❌
✅
2.3 类与接口的关系
关系
规则
示例
类 → 类
单继承,可多层继承
class A extends B
类 → 接口
多实现 ,可同时继承类
class A extends B implements C, D
接口 → 接口
多继承
interface A extends B, C
2.4 多接口冲突处理规则
冲突场景
处理规则
多个接口有相同抽象方法
实现类重写一次即可(签名相同算一个)
多个接口有相同默认方法
实现类必须重写 该方法
父类方法与接口默认方法同名
优先使用父类方法 (类优先于接口)
多接口继承时存在相同方法声明
不支持多继承 (编译报错)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class A extends Student implements Driver , Singer { @Override public void drive () { } @Override public void sing () { } } public static void show () {} public interface A { public default void show () { System.out.println("a接口的show方法..." ); } public static void show4 () { System.out.println("a接口的show4方法..." ); } } public interface B { public default void show () { System.out.println("B接口的show方法..." ); } public static void show4 () { System.out.println("B接口的show4方法..." ); } } A.show4(); A.show4(); @Override public void show () { A.super .show(); }
三、多态 3.1 核心概念
项目
说明
定义
同一个对象,在不同时刻表现出来的不同形态(对象多态、行为多态)
三个前提
① 有继承/实现关系;② 有方法重写;③ 父类引用指向子类对象
3.2 成员访问特点(⭐ 常考)
成员类型
编译(看谁)
运行(看谁)
口诀
成员变量
父类
父类
编译运行都看左(父)
成员方法
父类
子类 (重写后)
编译看左,运行看右
静态方法
父类
父类 (按变量规则走)
静态方法不参与多态
1 2 3 4 5 6 class Fu { int num = 10 ; public void method () { System.out.println("Fu" ); } }class Zi extends Fu { int num = 20 ; @Override public void method () { System.out.println("Zi" ); } }Fu f = new Zi ();System.out.println(f.num); f.method();
3.3 多态的好处与弊端
方面
说明
好处
提升扩展性:方法参数/返回值用父类类型,可接收任意子类对象
弊端
不能调用子类特有方法(只能调用父类中定义的方法)
3.4 向上转型 vs 向下转型
转型方向
格式
时机
风险
向上转型 (自动)
父类 变量 = new 子类()
多态的基础,自动完成
无
向下转型 (强制)
子类 变量 = (子类) 父类引用
需要调用子类特有方法时
类型不符会抛 ClassCastException
1 2 3 4 5 6 7 8 Animal a = new Dog (); a.eat(); if (a instanceof Dog) { Dog dog = (Dog) a; dog.watchHome(); }
3.5 instanceof 关键字
用途
格式
返回值
判断对象的实际类型
变量 instanceof 类型
boolean,是该类型(或子类)返回 true
⚠️ 原本是什么类型,才能转回什么类型。
四、内部类 4.1 四种内部类对比
类型
定义位置
创建对象格式
特点 / 使用场景
成员内部类
类的成员位置
外部类.内部类 对象 = new 外部类().new 内部类()
可访问外部类所有成员(含私有)
静态内部类
类的成员位置,有 static
外部类.内部类 对象 = new 外部类.内部类()
只能访问外部类静态 成员
局部内部类
方法内部
只能在方法内创建使用
局限性强,开发中几乎不用
匿名内部类
方法内部(无名)
new 父类/接口() { 重写方法 }
最常用 ,简化创建子类/实现类对象
4.2 匿名内部类(⭐ 重点)
项目
说明
本质
没有名字的子类对象 或 接口实现类对象
使用前提
必须有一个父类或接口作为”父亲”
作用
把”定义子类→重写方法→创建对象→调用方法”四步合成一步
使用次数
通常只用一次(用完即弃)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 new 父类/接口() { @Override 重写的方法() { ... } }; new Animal () { @Override public void eat () { System.out.println("吃东西" ); } }.eat(); Animal a = new Animal () { @Override public void eat () { System.out.println("吃东西" ); } public void special () { } }; a.eat(); Arrays.sort(arr, new Comparator <String>() { @Override public int compare (String o1, String o2) { return o1.length() - o2.length(); } });
五、工具类设计 5.1 工具类特征
特征
说明
方法全为 static
通过类名直接调用,无需创建对象
构造方法 private
禁止外部实例化,强制使用类名调用
无状态
不维护实例变量,所有逻辑通过参数传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyUtils { private MyUtils () { } public static String createCode (int n) { String data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ; Random r = new Random (); StringBuilder code = new StringBuilder (); for (int i = 0 ; i < n; i++) { code.append(data.charAt(r.nextInt(data.length()))); } return code.toString(); } }
5.2 Arrays 工具类常用方法
方法
说明
注意事项
Arrays.toString(arr)
数组 → 字符串,格式 [e1, e2, ...]
推荐用此方法打印数组内容
Arrays.sort(arr)
对数组升序排序(原地修改)
对象数组需实现 Comparable
Arrays.binarySearch(arr, key)
二分查找,返回索引
数组必须已排序 ,未找到返回负数
Arrays.copyOf(arr, newLen)
复制数组,可扩展/截断
超出长度部分填充默认值
Arrays.copyOfRange(arr, from, to)
复制指定范围 [from, to)
不包含 to 索引
1 2 3 4 5 6 7 8 9 int [] arr = {1 , 4 , 7 , 2 , 5 , 8 , 3 };System.out.println(Arrays.toString(arr)); Arrays.sort(arr); System.out.println(Arrays.toString(arr)); System.out.println(Arrays.binarySearch(arr, 5 )); int [] copy = Arrays.copyOf(arr, 3 );System.out.println(Arrays.toString(copy)); int [] range = Arrays.copyOfRange(arr, 2 , 5 );System.out.println(Arrays.toString(range));
5.3 Objects 工具类常用方法
方法
说明
优势
Objects.toString(obj)
对象 → 字符串,null 返回 "null"
不会抛空指针
Objects.toString(obj, default)
null 时返回默认值
安全处理 null
Objects.isNull(obj)
判断是否为 null
语义清晰
Objects.nonNull(obj)
判断是否不为 null
语义清晰
Objects.equals(a, b)
安全比较,避免空指针
任意一方为 null 也不报错
Objects.requireNonNull(obj, msg)
null 时抛出 NullPointerException
参数校验
1 2 3 4 5 String s = null ;System.out.println(Objects.toString(s)); System.out.println(Objects.toString(s, "默认值" )); System.out.println(Objects.equals(s, "hello" ));
六、综合对比速查 接口 vs 抽象类
对比项
接口(interface)
抽象类(abstract class)
关键字
interface
abstract class
能否实例化
❌
❌
构造方法
❌ 无
✅ 有
成员变量
只能是常量(public static final)
可以是普通变量
方法
抽象/默认/静态/私有
可以有普通方法
继承/实现
类可多实现 ,接口可多继承
类只能单继承
设计目的
制定规范,功能扩展
提取共性,模板方法
多态转型关系 1 2 3 4 向上转型(自动) 向下转型(强制,需 instanceof 检查) Animal a = new Dog() → Dog dog = (Dog) a; ↑ 扩大视野(看父类) ↓ 缩小视野(回到子类) 失去子类特有方法 恢复子类特有方法
单元测试 · 枚举 · 异常 · 重点总结 一、单元测试(JUnit) 1.1 概述
项目
说明
定义
针对最小功能单元 编写测试代码,验证方法正确性的框架
来源
第三方开源框架,IDEA 已集成;也可手动导入 jar 包(放入 lib 目录后 Add as Library)
优点
不依赖 main 方法;可单独运行任意方法;结果直观(绿条=正常,红条=异常)
1.2 @Test 方法要求
要求
说明
返回值
必须是 void
参数
必须没有参数
修饰符
必须是 public,不能是 static
运行方式
方法上右键运行单个;类上右键运行类中全部 @Test 方法
1.3 生命周期注解
注解
执行时机
方法要求
典型用途
@BeforeClass
所有测试方法执行前,只执行一次
必须 static
加载配置、初始化数据库连接
@Before
每个 @Test 方法执行前都执行
非静态
初始化测试数据、准备资源
@After
每个 @Test 方法执行后都执行(即使测试抛异常也执行)
非静态
清理数据、关闭资源
@AfterClass
所有测试方法执行后,只执行一次
必须 static
释放数据库连接、清理全局资源
执行顺序:
1 @BeforeClass → [@Before → @Test → @After] × N → @AfterClass
1.4 断言 断言是对测试结果进行”鉴定”,结果符合预期则成功,否则测试失败并提示。
方法
说明
Assert.assertEquals(msg, expected, actual)
断言期望值与实际值相等
Assert.assertNotEquals(msg, unexpected, actual)
断言两个值不相等
Assert.assertTrue(msg, condition)
断言条件为 true
Assert.assertNull(msg, object)
断言对象为 null
Assert.assertNotNull(msg, object)
断言对象不为 null
1 2 3 4 5 6 7 @Test public void testGetIndex () { int [] arr = {3 , 6 , 9 }; int index = getIndex(arr, 6 ); Assert.assertEquals("逻辑有误" , 1 , index); }
二、枚举 2.1 概述
项目
说明
定义
一种特殊的引用数据类型,与类、接口同级;用 enum 关键字定义
本质
一个类的多个固定且内容不可改变 的对象的集合
父类
所有枚举隐式继承 java.lang.Enum(而非直接继承 Object)
适用场景
对象有限固定,如:性别、星期、交通灯、季节、线程状态等
2.2 枚举的特点
特点
说明
枚举项修饰符
自动加 public static final(不能手动写)
构造方法
必须是 private(禁止外部创建新对象)
枚举项命名
建议全大写,多个用逗号分隔;有字段/方法时最后加分号
继承限制
已隐式继承 Enum,不能再继承其他类
抽象方法
可以定义,但所有枚举对象都必须重写
2.3 枚举定义格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public enum Light { RED("红灯" , "停" ), GREEN("绿灯" , "行" ), YELLOW("黄灯" , "等" ); private final String color; private final String info; private Light (String color, String info) { this .color = color; this .info = info; } public String getColor () { return color; } public String getInfo () { return info; } }
2.4 枚举常用方法(继承自 Enum)
方法
说明
枚举类.枚举项
获取指定枚举对象(static,类名直接调用)
枚举对象.name()
返回枚举项的名称字符串(定义时写的名字)
枚举对象.ordinal()
返回枚举项的序号(从 0 开始)
枚举类.values()
返回所有枚举对象的数组
枚举类.valueOf(String name)
根据名称字符串获取对应枚举对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (Light v : Light.values()) { System.out.println((v.ordinal()+1 ) + ": " + v.name() + " → " + v.getColor() + " → " + v.getInfo()); } public static void handle (Light light) { switch (light) { case RED: System.out.println("请停车" ); break ; case GREEN: System.out.println("请通行" ); break ; case YELLOW: System.out.println("请等待" ); break ; } }
三、异常 3.1 异常体系 1 2 3 4 5 Throwable ├── Error ← 致命错误,程序无法继续(如 OutOfMemoryError、StackOverflowError) └── Exception ← 程序可处理的问题(程序员重点关注) ├── 编译时异常(受检异常) → 直接继承 Exception(如 ParseException、IOException) └── 运行时异常(非受检异常)→ 继承 RuntimeException(如 NullPointerException)
3.2 两类异常对比
对比项
编译时异常(Checked)
运行时异常(Unchecked)
继承自
Exception
RuntimeException
编译时
必须处理 ,否则编译不通过
不强制处理,编译通过
常见例子
ParseException、FileNotFoundException
NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException
JVM 默认处理
不允许不处理
打印异常信息 + 终止程序
3.3 throw vs throws(⭐ 核心区分)
对比项
throw
throws
作用
抛出 一个具体的异常对象
声明 该方法可能抛出的异常类型
后面跟
异常对象 (new XxxException(...))
异常类型 (可多个,逗号分隔)
写的位置
方法内部
方法声明 (小括号后面)
数量
一次只能 throw 一个对象
可声明多个类型
含义
把异常对象抛给调用者,由调用者决定如何处理
告知调用者:此方法内部未处理,请你来处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static int getValue (int [] arr, int index) throws Exception { if (arr == null ) throw new Exception ("数组不能为 null" ); if (index < 0 || index >= arr.length) throw new ArrayIndexOutOfBoundsException ("非法索引:" + index); return arr[index]; } public static void abc (int a) throws NullPointerException, Exception { if (a < 0 ) throw new NullPointerException ("a 不能为负数" ); throw new Exception ("其他异常" ); }
3.4 异常处理方式 方式一:throws(甩给调用者) 适用场景:方法内部无法确定 如何处理,让调用者自己决定。
1 2 3 public static void readFile (String path) throws FileNotFoundException { throw new FileNotFoundException ("文件不存在:" + path); }
方式二:try-catch(自己捕获处理) 1 2 3 4 5 6 7 8 9 10 11 12 13 try { getValue(arr, 5 ); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("索引越界:" + e.getMessage()); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("资源已释放" ); }
try-catch 执行流程:
场景
行为
try 中无异常
正常执行,catch 跳过,finally 执行,程序继续
try 中有异常,catch 匹配成功
跳转到对应 catch,执行完后 finally 执行,程序继续
try 中有异常,catch 未匹配
继续向上抛给 JVM,finally 仍然执行
finally 中有 return
以 finally 的 return 为最终返回结果 ⚠️
finally 中无 return
finally 中修改变量不影响 前面 return 已取到的值
3.5 Throwable 常用方法
方法
说明
使用频率
e.getMessage()
获取异常描述信息(构造时传入的字符串)
一般
e.toString()
获取异常类型 + 描述信息
较少
e.printStackTrace()
打印完整异常栈信息(红色字体),不停止程序运行
⭐ 最常用
3.6 方法重写与异常的规则
规则
说明
父类方法没有 throws
子类重写时不能加 throws ,只能内部 try-catch
父类方法有 throws 异常 A
子类只能 throws A 或 A 的子类 ,不能抛出更大的异常
1 2 3 4 5 6 7 8 9 10 11 12 13 class Fu { public void method () { } } class Zi extends Fu { @Override public void method () { try { throw new Exception ("问题" ); } catch (Exception e) { e.printStackTrace(); } } }
四、自定义异常 4.1 定义步骤
步骤
说明
1
继承 Exception(编译时异常)或 RuntimeException(运行时异常)
2
提供空参构造 + 带 message 的满参构造 (调用 super(message))
3
业务代码中用 throw 抛出,用 throws 或 try-catch 处理
4.2 选择父类
选择
适用场景
继承 Exception
调用者必须 处理(强制性强,适合关键业务)
继承 RuntimeException
调用者可选择 处理(更灵活,适合参数校验等)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class UserNameRegisterException extends Exception { public UserNameRegisterException () { } public UserNameRegisterException (String message) { super (message); } } public static void register (String name) throws UserNameRegisterException { if ("admin" .equals(name)) throw new UserNameRegisterException ("用户名 [" + name + "] 已被注册" ); System.out.println("注册成功:" + name); } try { register("admin" ); } catch (UserNameRegisterException e) { e.printStackTrace(); }
五、综合对比速查 异常处理方式选择
场景
推荐方式
方法内无法确定 如何处理,让调用者决定
throws 声明
方法内可以处理 ,并有备用逻辑
try-catch 捕获
需要无论如何都执行 的代码(关闭资源等)
finally
父类方法无 throws ,子类重写内部有异常
内部 try-catch(不能加 throws)
常见运行时异常速查
异常类
触发原因
NullPointerException
对 null 对象调用方法或访问属性
ArrayIndexOutOfBoundsException
数组索引越界
StringIndexOutOfBoundsException
字符串索引越界
ClassCastException
强制类型转换失败
ArithmeticException
算术错误(如除以零)
NumberFormatException
字符串转数字时格式不合法
JUnit 注解执行顺序总结 1 2 3 4 5 @BeforeClass(static,全局1次) ↓ @Before → @Test → @After 重复执行,每个 @Test 方法对应一组 ↓ @AfterClass(static,全局1次)
包装类 · 日期时间 · System · 对象克隆 · 重点总结 一、包装类 1.1 基本类型 → 包装类对照表
基本类型
包装类
所在包
byte
Byte
java.lang
short
Short
java.lang
int
Integer
java.lang
long
Long
java.lang
float
Float
java.lang
double
Double
java.lang
char
Character
java.lang
boolean
Boolean
java.lang
集合 <> 中只能写引用类型,存储基本数据类型时必须使用对应包装类。
1.2 装箱 / 拆箱
概念
说明
JDK5+
装箱
基本类型 → 包装类
自动完成,无需手动转换
拆箱
包装类 → 基本类型
自动完成,无需手动转换
1 2 3 4 5 6 7 8 Integer i1 = Integer.valueOf(123 );Integer i2 = 456 ;int i3 = i2;
1.3 Integer 缓存机制(⭐ 面试常考)
创建方式
是否走缓存
缓存范围
new Integer(n)
❌ 每次都创建新对象
—
Integer.valueOf(n) / 自动装箱
✅ 先查缓存池
-128 ~ 127
1 2 3 4 5 6 7 8 9 10 11 Integer a = new Integer (100 );Integer b = new Integer (100 );System.out.println(a == b); Integer c = Integer.valueOf(100 ); Integer d = Integer.valueOf(100 );System.out.println(c == d); Integer e = Integer.valueOf(200 ); Integer f = Integer.valueOf(200 );System.out.println(e == f);
1.4 Integer 常用方法
方法
说明
Integer.MAX_VALUE / MIN_VALUE
int 最大值 / 最小值常量
Integer.toBinaryString(int i)
转二进制字符串
Integer.toOctalString(int i)
转八进制字符串
Integer.toHexString(int i)
转十六进制字符串
Integer.valueOf(int/String)
int / String → Integer
Integer.parseInt(String s)
String → int(最常用 )
1.5 基本类型 ↔ 字符串 互转
转换方向
方法
示例
基本类型 → String
数据 + ""
100 + "" → "100"
基本类型 → String
String.valueOf(数据)
String.valueOf(100)
基本类型 → String
Integer.toString(int)
Integer.toString(100)
String → int
Integer.parseInt(str)
Integer.parseInt("20") → 20
String → double
Double.parseDouble(str)
Double.parseDouble("3.14")
String → boolean
Boolean.parseBoolean(str)
Boolean.parseBoolean("true")
⚠️ Character 没有 parseXxx 方法。parseXxx 的字符串中不能包含非数字字符,否则抛出 NumberFormatException。
1 2 3 4 5 6 7 String s = "22,1,555,128,44,77,99,25,6,888,111" ;String[] arr = s.split("," ); int [] nums = new int [arr.length];for (int i = 0 ; i < arr.length; i++) nums[i] = Integer.parseInt(arr[i]);Arrays.sort(nums); System.out.println(Arrays.toString(nums));
二、JDK7 日期类 2.1 Date 类
概念
说明
时间原点
1970-01-01 00:00:00 为 0 毫秒
表示方式
Date 对象内部以**毫秒值(long)**存储时间
方法
说明
new Date()
创建代表当前时间 的 Date 对象
new Date(long millis)
根据毫秒值创建 Date 对象
date.getTime()
获取 Date 对象的毫秒值
date.setTime(long millis)
修改 Date 对象的时间
System.currentTimeMillis()
直接获取当前时间毫秒值(无需创建 Date)
1 2 3 Date d = new Date (); long time = d.getTime(); Date d2 = new Date (time + 2 * 1000 );
概念
说明
格式化
Date → String(format 方法)
解析
String → Date(parse 方法,需处理 ParseException)
关键要求
解析时格式字符串必须与目标字符串格式完全一致
常用模式字母:
字母
含义
示例
yyyy
年(4位)
2026
MM
月(2位补零)
03
dd
日(2位补零)
24
HH
时(24小时制)
14
mm
分
30
ss
秒
05
SSS
毫秒
123
EEE
星期
周一
a
上午/下午
下午
字母写 1 位按实际位数显示;写 2 位则强制 2 位(不足补 0)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" );String str = sdf.format(new Date ()); Date d = sdf.parse("2026-03-24 15:30:05" );SimpleDateFormat sdf2 = new SimpleDateFormat ("yyyy-M-d H:m:s" );long start = sdf2.parse("2026-3-1 0:0:0" ).getTime();long end = sdf2.parse("2026-3-1 0:10:0" ).getTime();long user = sdf2.parse("2026-3-1 0:3:47" ).getTime();System.out.println(user >= start && user <= end ? "秒杀成功" : "秒杀失败" );
2.3 Calendar 类
方法
说明
Calendar.getInstance()
获取代表当前时间的日历对象(不能 new )
get(Calendar.YEAR) 等
获取年/月/日/时等信息(月份从 0 开始,0=1月)
set(Calendar.字段, 值)
修改指定字段
add(Calendar.字段, 增量)
对指定字段增加(负数为减少)
getTime()
日历对象 → Date 对象
getTimeInMillis()
获取毫秒值
1 2 3 4 5 6 7 Calendar c = Calendar.getInstance();int year = c.get(Calendar.YEAR);int month = c.get(Calendar.MONTH) + 1 ; int day = c.get(Calendar.DAY_OF_MONTH);c.add(Calendar.DAY_OF_YEAR, 100 ); c.add(Calendar.HOUR, -12 );
三、JDK8 新增日期类(推荐使用) 3.1 JDK7 vs JDK8 日期类对比
对比项
JDK7(Date/Calendar)
JDK8(LocalDateTime 等)
线程安全
❌ 不安全
✅ 不可变对象,天然线程安全
对象可变性
❌ 可变,易出 bug
✅ 不可变,每次操作返回新对象
精度
毫秒
纳秒
API 设计
较少,月份从 0 开始,易出错
丰富,月份从 1 开始,语义清晰
3.2 三个核心日期类
类
表示内容
获取当前
LocalDate
年、月、日
LocalDate.now()
LocalTime
时、分、秒、纳秒
LocalTime.now()
LocalDateTime
年、月、日、时、分、秒、纳秒
LocalDateTime.now()
3.3 LocalDateTime 常用 API
分类
方法
说明
获取
getYear() getMonthValue() getDayOfMonth()
获取年/月/日
获取
getHour() getMinute() getSecond() getNano()
获取时/分/秒/纳秒
获取
getDayOfYear() getDayOfWeek().getValue()
第几天 / 周几
修改
withYear(n) withMonth(n) …
返回修改后的新对象
增加
plusYears(n) plusDays(n) plusHours(n) …
返回增加后的新对象
减少
minusYears(n) minusDays(n) …
返回减少后的新对象
比较
equals(other) isBefore(other) isAfter(other)
比较两个时间
创建
LocalDateTime.of(年,月,日,时,分,秒)
指定时间创建对象
转换
toLocalDate() toLocalTime()
拆分为日期/时间对象
1 2 3 4 5 6 7 8 9 10 11 LocalDateTime ldt = LocalDateTime.now();System.out.println(ldt.getYear() + "年" + ldt.getMonthValue() + "月" ); LocalDateTime next = ldt.plusDays(10 ).withHour(8 );LocalDateTime target = LocalDateTime.of(2029 , 12 , 12 , 0 , 0 , 0 );System.out.println(target.isAfter(ldt));
3.4 时区相关类
类
说明
关键方法
ZoneId
表示时区
systemDefault() 获取系统时区;of("Asia/Shanghai") 指定时区
ZonedDateTime
带时区的日期时间
ZonedDateTime.now(zoneId) 获取指定时区时间
1 2 3 ZoneId zone = ZoneId.of("America/New_York" );ZonedDateTime nyTime = ZonedDateTime.now(zone); ZonedDateTime utc = ZonedDateTime.now(Clock.systemUTC());
3.5 Instant 类
项目
说明
表示
从时间原点到此刻的总秒数 + 纳秒余数
用途
记录代码执行时间点,分析性能;记录用户操作时间戳
方法
说明
Instant.now()
获取当前时刻
getEpochSecond()
获取总秒数
getNano()
获取不足 1 秒的纳秒数
plusNanos(n) / minusSeconds(n)
加/减时间
1 2 3 4 5 Instant start = Instant.now();Instant end = Instant.now();Duration cost = Duration.between(start, end);System.out.println("耗时:" + cost.toMillis() + "ms" );
操作
写法
创建格式化器
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
格式化 (日期对象已知)
ldt.format(formatter)
解析 (无日期对象)
LocalDateTime.parse(str, formatter)
1 2 3 4 5 6 7 8 DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" );LocalDateTime now = LocalDateTime.now();String str = now.format(fmt); LocalDateTime dt = LocalDateTime.parse("2026-03-24 15:30:05" , fmt);
3.7 Period 和 Duration(时间间隔)
类
粒度
适用类型
获取方式
Period
粗粒度(年、月、日)
LocalDate
Period.between(start, end)
Duration
细粒度(时、分、秒、纳秒)
LocalTime LocalDateTime Instant
Duration.between(start, end)
Period 方法
说明
getYears()
相差的年数零头
getMonths()
相差的月数零头
getDays()
相差的天数零头
Duration 方法
说明
getSeconds()
相差总秒数
toHours()
相差总小时数
toDays()
相差总天数
toMinutes()
相差总分钟数
1 2 3 4 5 6 7 8 9 10 LocalDate birth = LocalDate.of(2001 , 5 , 3 );Period p = Period.between(birth, LocalDate.now());System.out.println(p.getYears() + "年" + p.getMonths() + "月" + p.getDays() + "天" ); LocalDateTime login = LocalDateTime.of(2026 , 3 , 23 , 20 , 20 , 10 );Duration d = Duration.between(login, LocalDateTime.now());long sec = d.getSeconds();System.out.println(sec/3600 + "小时" + sec%3600 /60 + "分" + sec%60 + "秒" );
四、System 工具类
方法
说明
System.currentTimeMillis()
获取当前时间毫秒值(常用于计时)
System.exit(int status)
终止 JVM,0 表示正常退出
System.arraycopy(src, srcPos, dest, destPos, length)
数组复制(底层,性能最高)
System.gc()
建议 JVM 进行垃圾回收(不保证立即执行)
1 2 3 4 long t1 = System.currentTimeMillis();long t2 = System.currentTimeMillis();System.out.println("耗时:" + (t2 - t1) + "ms" );
五、对象克隆 5.1 浅克隆 vs 深克隆
对比项
浅克隆(Shallow Clone)
深克隆(Deep Clone)
基本类型 / String
✅ 复制值到新对象
✅ 复制值到新对象
引用类型属性
❌ 只复制地址(两个对象共享同一个引用)
✅ 创建新对象,完全独立
修改引用属性
会影响原对象
不影响原对象
实现方式
super.clone() 默认实现
在 clone() 中手动克隆引用属性
5.2 实现步骤
类实现 Cloneable 接口(标记接口,无方法,不实现会抛 CloneNotSupportedException)
重写 Object 类的 clone() 方法(提升访问权限为 public,返回具体类型)
方法体内调用 super.clone() 完成浅克隆
深克隆:对引用类型属性再单独克隆一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Phone implements Cloneable { private String brand; private double price; private double [] memory; @Override public Phone clone () throws CloneNotSupportedException { Phone clone = (Phone) super .clone(); clone.setMemory(memory.clone()); return clone; } } Phone original = new Phone ("锤子" , 998 , new double []{8 , 12 , 16 });Phone copy = original.clone();copy.setBrand("魅族" ); copy.getMemory()[1 ] = 4 ; System.out.println(original);
六、综合对比速查 日期类选型
需求
推荐
简单获取当前毫秒值
System.currentTimeMillis()
日期格式化 / 解析(旧项目)
SimpleDateFormat + Date
日期格式化 / 解析(新项目)
DateTimeFormatter + LocalDateTime
仅操作年月日
LocalDate
仅操作时分秒
LocalTime
带时区的时间
ZonedDateTime
计算代码耗时 / 时间戳
Instant
计算日期间隔(年月日)
Period
计算时间间隔(时分秒)
Duration
JDK7 → JDK8 日期类迁移对照
JDK7
JDK8 替代
Date
LocalDateTime / Instant
SimpleDateFormat
DateTimeFormatter
Calendar
LocalDate / LocalDateTime
Calendar.getInstance()
LocalDateTime.now()
sdf.format(date)
ldt.format(formatter)
sdf.parse(str)
LocalDateTime.parse(str, formatter)
Collection · List · 泛型 · 重点总结 一、数组 vs 集合
对比项
数组
集合
长度
固定 ,不可变
可变 ,动态扩容
存储类型
基本类型 + 引用类型
只能存引用类型 (基本类型需用包装类)
功能
简单,无内置工具
丰富,配套工具类(Collections)
二、集合体系结构 1 2 3 4 5 6 7 8 9 10 11 12 13 集合 ├── 单列集合(Collection) ← 每次操作一个元素 │ ├── List(有序、有索引、可重复) │ │ ├── ArrayList │ │ └── LinkedList │ └── Set(无索引、不可重复) │ ├── HashSet │ ├── LinkedHashSet │ └── TreeSet └── 双列集合(Map) ← 每次操作一对键值 ├── HashMap ├── LinkedHashMap └── TreeMap
三、Collection 通用方法
方法
说明
boolean add(E e)
添加元素,Collection 始终返回 true;Set 重复时返回 false
boolean addAll(Collection c)
将另一个集合的全部元素添加进来
boolean remove(Object o)
删除指定元素(依赖 equals),只删第一个匹配项
boolean removeIf(Predicate p)
按条件批量删除 ,传入函数式接口(可用 Lambda)
boolean contains(Object o)
判断是否包含某元素(依赖 equals)
boolean isEmpty()
是否为空
int size()
元素个数
void clear()
清空集合
Object[] toArray()
转为 Object 数组
T[] toArray(T[] arr)
转为指定类型数组
1 2 3 4 5 6 7 8 9 Collection<String> c = new ArrayList <>(); c.add("aa" ); c.add("aaa" ); c.add("bb" ); c.add("ddd" ); c.removeIf(s -> s.length() == 2 ); System.out.println(c); String[] arr = c.toArray(new String [c.size()]);
四、Collection 三种遍历方式
遍历方式
适用范围
说明
迭代器 Iterator
所有 Collection
通用,可在遍历中安全删除(用 it.remove())
增强 for
所有 Collection + 数组
本质是迭代器的语法糖,不能在遍历中修改集合长度
forEach + Lambda
所有 Collection
最简洁,JDK8+,内部也是迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Collection<String> c = new ArrayList <>(); c.add("赵敏" ); c.add("小昭" ); c.add("灭绝" ); Iterator<String> it = c.iterator(); while (it.hasNext()) { String s = it.next(); if ("小昭" .equals(s)) it.remove(); System.out.println(s); } for (String s : c) { System.out.println(s); } c.forEach(s -> System.out.println(s));
五、List 集合 5.1 List 特点
特点
说明
有序
存入和取出的顺序一致
有索引
可通过整数索引精确访问元素
可重复
允许存储相同内容
5.2 List 特有方法(索引相关)
方法
说明
void add(int index, E e)
在指定索引处插入元素,原有元素后移
E get(int index)
获取指定索引处的元素
E set(int index, E e)
修改指定索引处的元素,返回被替换的旧值
E remove(int index)
删除指定索引处的元素,返回被删除的元素
⚠️ remove 有两个重载:remove(int index) 按索引删,remove(Object o) 按元素删。删 Integer 时需显式转型:list.remove(Integer.valueOf(11)),否则默认当索引处理。
5.3 List 四种遍历方式
方式
特点
普通 for + 索引
List 独有,可按需控制索引(适合删除操作)
迭代器
通用,可安全删除
增强 for
简洁,不可修改集合长度
forEach + Lambda
最简洁,JDK8+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 List<String> list = new ArrayList <>(); list.add("蜘蛛精" ); list.add("至尊宝" ); list.add("牛夫人" ); for (int i = 0 ; i < list.size(); i++) System.out.println(list.get(i));for (String s : list) System.out.println(s);Iterator<String> it = list.iterator(); while (it.hasNext()) System.out.println(it.next());list.forEach(s -> System.out.println(s));
5.4 遍历中删除元素的正确姿势 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 List<Integer> list = new ArrayList <>(); Collections.addAll(list, 11 , 11 , 22 , 11 , 33 , 11 ); for (int i = list.size() - 1 ; i >= 0 ; i--) { if (list.get(i) == 11 ) list.remove(i); } for (int i = 0 ; i < list.size(); ) { if (list.get(i) == 11 ) list.remove(i); else i++; } list.removeIf(i -> i == 11 );
六、ArrayList vs LinkedList
对比项
ArrayList
LinkedList
底层结构
动态数组
双向链表
查询速度
✅ 快(直接按索引定位)
❌ 慢(需从头/尾遍历)
增删速度
❌ 慢(需移动元素/扩容)
✅ 快(只改指针指向)
扩容机制
创建新数组,复制旧数据,默认 1.5 倍扩容
无需扩容,按需分配节点
额外特性
—
可模拟栈 和队列
使用场景
查询多、增删少
增删多、查询少
LinkedList 特有方法(操作首尾)
方法
说明
addFirst(E e) / push(E e)
在头部插入(等价,模拟压栈)
addLast(E e)
在尾部插入(模拟入队)
getFirst()
获取头部元素
getLast()
获取尾部元素
removeFirst() / pop()
删除并返回头部元素(等价,模拟弹栈/出队)
removeLast()
删除并返回尾部元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 LinkedList<String> queue = new LinkedList <>(); queue.addLast("第1号" ); queue.addLast("第2号" ); System.out.println(queue.removeFirst()); System.out.println(queue.removeFirst()); LinkedList<String> stack = new LinkedList <>(); stack.push("子弹1" ); stack.push("子弹2" ); stack.push("子弹3" ); System.out.println(stack.pop()); System.out.println(stack.pop());
七、Collections 工具类
方法
说明
Collections.addAll(集合, 元素...)
批量添加元素(可传数组或参数列表)
Collections.shuffle(list)
随机打乱 List 顺序
Collections.sort(list)
对 List 自然排序(元素需实现 Comparable)
Collections.sort(list, comparator)
按自定义规则排序
1 2 3 4 5 6 List<String> colors = new ArrayList <>(); Collections.addAll(colors, "♥" , "♠" , "♣" , "♦" ); List<Integer> nums = new ArrayList <>(); Collections.addAll(nums, 3 , 1 , 4 , 1 , 5 , 9 ); Collections.shuffle(nums);
八、泛型 8.1 概述与好处
项目
说明
本质
参数化类型,把数据类型当参数传递,用 <T> <E> <K> <V> 等占位符表示
好处1
将运行时 的类型转换异常提前暴露到编译期 ,降低排错成本
好处2
避免强制类型转换,代码更安全、更简洁
好处3
代码模板化,一套逻辑可处理多种类型
限制
泛型不支持多态 :List<Object> 不能接收 List<String>(需用通配符 ?)
8.2 泛型定义位置与确定时机
位置
定义格式
类型何时确定
泛型类
class 类名<T> {}
创建对象时 new 类名<具体类型>()
泛型接口
interface 接口名<T> {}
实现类写死 类型,或实现类也带泛型(创建对象时确定)
泛型方法
public <T> 返回值 方法名(T t)
调用方法时 ,根据传入参数类型自动推断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Box <T> { private T value; public T getValue () { return value; } public void setValue (T value) { this .value = value; } } Box<String> b1 = new Box <>(); b1.setValue("hello" ); Box<Integer> b2 = new Box <>(); b2.setValue(100 ); public <E> void show (E e) { System.out.println(e); }show("Hello" ); show(100 ); public static <K> void fun (K k) { System.out.println(k); }
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface A <T> { T process (T t) ; }class Impl1 implements A <String> { public String process (String t) { return t.toUpperCase(); } } class Impl2 <T> implements A <T> { public T process (T t) { return t; } } Impl2<Integer> obj = new Impl2 <>();
8.3 泛型通配符 ?
语法
含义
场景
<?>
任意类型,不限范围
只读,不关心具体类型
<? extends T>
上限 :T 及 T 的子类
读取数据(生产者)
<? super T>
下限 :T 及 T 的父类
写入数据(消费者)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void readNumbers (List<? extends Number> list) { for (Number n : list) System.out.println(n); } readNumbers(new ArrayList <Integer>()); readNumbers(new ArrayList <Double>()); public static void print (List<? super Teacher> list) { for (Object o : list) System.out.println(o); } print(new ArrayList <Teacher>()); print(new ArrayList <Person>());
九、综合对比速查 集合遍历方式汇总
遍历方式
List
Set
能否遍历中删除
普通 for + 索引
✅
❌(无索引)
✅(逆向遍历时安全)
迭代器
✅
✅
✅(用 it.remove())
增强 for
✅
✅
❌(会抛异常)
forEach + Lambda
✅
✅
❌(会抛异常)
泛型上下限记忆口诀
场景
选择
口诀
只从集合读 数据
<? extends T>
PECS:Producer Extends
只向集合写 数据
<? super T>
PECS:Consumer Super
既读又写
直接指定具体类型 <T>
—
Set 集合 · 排序 · 重点总结 一、Set 集合概述 1.1 Set vs List 核心区别
特性
List
Set
是否有序
✅ 有序(存入顺序)
取决于实现类
是否有索引
✅ 有
❌ 无
是否可重复
✅ 可重复
❌ 不可重复(唯一)
去重依据
—
hashCode() + equals()
1.2 三大实现类对比
特性
HashSet
LinkedHashSet
TreeSet
底层结构
哈希表(数组 + 链表/红黑树)
哈希表 + 双向链表
红黑树
元素顺序
无序
有序 (存入顺序)
排序 (升序/自定义)
去重依据
hashCode() + equals()
hashCode() + equals()
compareTo() 返回值是否为 0
自定义类做元素
必须重写 hashCode + equals
必须重写 hashCode + equals
实现 Comparable 或传入 Comparator
线程安全
❌(效率高)
❌(效率高)
❌(效率高)
⚠️ TreeSet 去重不看 hashCode/equals,只看比较器返回是否为 0 ,返回 0 就视为重复元素,不再存储。
二、HashSet 底层原理 2.1 哈希表结构演进
JDK 版本
底层结构
JDK 8 之前
数组 + 单向链表
JDK 8 及之后
数组 + 单向链表 + 红黑树 (链表长度 > 8 且数组元素 ≥ 64 时转换)
2.2 元素存储流程(去重原理) 1 2 3 4 5 6 7 8 9 添加元素 ↓ 计算 hashCode → hashCode % 数组长度 = 索引 ↓ 该索引位置是否有元素? ├── 没有 → 直接存储 ✅ └── 有 → 调用 equals() 比较 ├── true → 重复,不存储 ❌ └── false → 不重复,以链表/红黑树形式挂在同一索引下 ✅
数组默认长度 16,加载因子 0.75,超过 16 × 0.75 = 12 个元素时扩容 。
2.3 hashCode 规则
规则
说明
同一对象多次调用
返回值必须相同
未重写 hashCode
根据系统资源计算,每次 new 出来的对象哈希值都不同
哈希值不同
内容一定 不同
哈希值相同
内容不一定 相同(哈希碰撞),需继续调用 equals() 判断
String 的 hashCode 算法(核心公式):
1 2 3 4 h = 31 * h + 当前字符的 ASCII 值 (从左到右遍历每个字符) "abc" → h=0 → 31×0+97=97 → 31×97+98=3105 → 31×3105+99=96354 "重地".hashCode() == "通话".hashCode() == 96354 ← 哈希碰撞示例
2.4 自定义类存入 HashSet 必须重写的两个方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Student { private String name; private int age; @Override public int hashCode () { int result = name != null ? name.hashCode() : 0 ; result = 31 * result + age; return result; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; if (age != student.age) return false ; return Objects.equals(name, student.name); } }
三、LinkedHashSet 底层原理 在 HashSet 的哈希表基础上,额外维护一条双向链表 来记录元素的存入顺序。
特点
说明
头节点
第一个添加的元素
尾节点
最后一个添加的元素
遍历顺序
按双向链表顺序,与存入顺序一致
去重方式
与 HashSet 相同,依赖 hashCode + equals
四、排序接口 4.1 两种排序接口对比
对比维度
Comparable(自然排序)
Comparator(定制排序)
所在包
java.lang
java.util
实现方式
元素类自身实现 接口,重写 compareTo(T o)
外部传入 比较器对象,重写 compare(T o1, T o2)
方法参数个数
1 个 (compareTo(o),this 是另一个)
2 个 (compare(o1, o2))
代码侵入性
需要修改元素类源码
无需修改元素类
灵活性
只能定义一种固定 排序规则
可动态定义多种 排序规则
优先级
低
高 (Comparator 和 Comparable 同时存在时,Comparator 生效)
典型场景
元素类默认排序(如 Integer、String)
需自定义排序,或元素类未实现 Comparable
4.2 返回值含义(两个接口通用)
返回值
含义
效果
< 0
当前/o1 小于 参数/o2
排在前面
= 0
两者相等
TreeSet 视为重复,不存储
> 0
当前/o1 大于 参数/o2
排在后面
4.3 升序 / 降序写法口诀
写法
顺序
说明
this.age - o.age / o1 - o2
升序 ↑
小的在前
o.age - this.age / o2 - o1
降序 ↓
大的在前
o1.compareTo(o2)
升序 ↑
字典序 a → z
o2.compareTo(o1)
降序 ↓
字典序 z → a
Double.compare(a, b)
升序 ↑
浮点数必须用此方法 ,不能直接相减(精度问题)
4.4 Comparable 实现示例 1 2 3 4 5 6 7 8 9 10 11 12 13 public class Book implements Comparable <Book> { private String name; private double price; @Override public int compareTo (Book o) { int i = Double.compare(this .price, o.price); return i == 0 ? this .name.compareTo(o.name) : i; } }
4.5 Comparator 实现示例(匿名内部类 / Lambda) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Comparator<String> comp = new Comparator <String>() { @Override public int compare (String o1, String o2) { int i = o2.length() - o1.length(); return i == 0 ? o1.compareTo(o2) : i; } }; Arrays.sort(arr, comp); Arrays.sort(arr, (o1, o2) -> { int i = o2.length() - o1.length(); return i == 0 ? o1.compareTo(o2) : i; });
五、TreeSet 5.1 特点
项目
说明
底层结构
红黑树
元素顺序
按比较规则自动排序
去重依据
比较器(compareTo 或 compare)返回 0 → 视为重复
元素要求
必须实现 Comparable,或创建时传入 Comparator
5.2 两种构造方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 TreeSet<Integer> set1 = new TreeSet <>(); TreeSet<Book> set2 = new TreeSet <>(new Comparator <Book>() { @Override public int compare (Book o1, Book o2) { int i = Double.compare(o1.getPrice(), o2.getPrice()); return i == 0 ? o1.getName().compareTo(o2.getName()) : i; } }); TreeSet<Book> set3 = new TreeSet <>((o1, o2) -> { int i = Double.compare(o1.getPrice(), o2.getPrice()); return i == 0 ? o1.getName().compareTo(o2.getName()) : i; });
⚠️ 必须添加次要排序条件 :若只比较主条件(如价格),价格相同的不同书籍会被当成重复元素而丢失!
六、Collections 工具类排序 6.1 sort 方法重载
方法签名
适用场景
Collections.sort(List<T> list)
元素已实现 Comparable,使用自然排序
Collections.sort(List<T> list, Comparator<T> c)
元素未实现 Comparable,或需要自定义规则
6.2 其他常用工具方法
方法
说明
Collections.addAll(集合, 元素...)
批量添加元素
Collections.shuffle(list)
随机打乱顺序
list.clone()
浅克隆 :复制集合结构,但元素对象仍是同一引用
list.retainAll(other)
交集 :保留两个集合的共同元素
list.addAll(other)
并集 :将另一个集合所有元素加入
list.removeAll(other)
差集 :从集合中移除另一集合中包含的元素
七、集合间转换与运算 7.1 单列集合相互转换 1 2 3 List<Integer> list = new ArrayList <>(set); Set<Integer> set = new HashSet <>(list);
7.2 交集 / 并集 / 差集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ArrayList<Integer> list1 = new ArrayList <>(); ArrayList<Integer> list2 = new ArrayList <>(); Collections.addAll(list1, 1 , 2 , 5 , 8 , 3 , 6 , 9 ); Collections.addAll(list2, 2 , 4 , 6 , 8 ); ArrayList<Integer> union = (ArrayList<Integer>) list1.clone(); union.addAll(list2); ArrayList<Integer> intersection = (ArrayList<Integer>) list1.clone(); intersection.retainAll(list2); ArrayList<Integer> difference = (ArrayList<Integer>) list1.clone(); difference.removeAll(list2); Set<Integer> deduped = new HashSet <>(difference);
⚠️ clone() 是浅克隆 :集合结构独立,但元素对象是同一引用。修改元素内部属性会影响原集合。
八、综合对比速查 Set 实现类选型
需求
推荐集合
只需去重,不关心顺序
HashSet
去重 + 保持插入顺序
LinkedHashSet
去重 + 自动排序
TreeSet
自定义类存入集合的要求
集合
必须实现
说明
HashSet / LinkedHashSet
hashCode() + equals()
两者缺一不可,否则无法正确去重
TreeSet(自然排序)
Comparable → compareTo()
元素类内部定义排序规则
TreeSet(定制排序)
无需修改元素类
创建时传入 Comparator 对象
Collections.sort(list)
Comparable → compareTo()
同上
Collections.sort(list, comp)
无需修改元素类
传入 Comparator 对象
Map · 集合嵌套 · 不可变集合 · XML · 注解 · 重点总结 一、Map 集合 1.1 核心概念
项目
说明
接口
java.util.Map<K, V>,双列集合根接口,与 Collection 无关
键
唯一,不可重复
值
可重复
关系
一个键对应一个值(映射关系);只能通过键找值,不能通过值找键
Entry
Map.Entry<K,V> 表示一个键值对对象,内含 getKey() / getValue()
1.2 三大实现类对比
特性
HashMap
LinkedHashMap
TreeMap
底层结构
哈希表(数组 + 链表/红黑树)
哈希表 + 双向链表
红黑树
键的顺序
无序
有序 (存入顺序)
排序 (默认升序)
键是否可为 null
✅
✅
❌(需比较,null 不可比较)
线程安全
❌(效率高)
❌(效率高)
❌(效率高)
键唯一依据
hashCode() + equals()
hashCode() + equals()
compareTo() 或 Comparator
常用场景
通用,查询快
需要保留插入顺序
需要对键排序
Hashtable vs HashMap :Hashtable 线程安全但效率低,不允许 null 键/值,已基本淘汰。
1.3 Map 常用方法
方法
说明
put(K key, V value)
添加/覆盖键值对;首次添加返回 null,覆盖返回旧值
get(Object key)
根据键获取值;键不存在返回 null
getOrDefault(key, defaultVal)
键不存在时返回默认值,推荐用于计数统计
remove(Object key)
删除键值对,返回被删除的值
containsKey(Object key)
判断是否包含指定键
containsValue(Object value)
判断是否包含指定值
keySet()
返回所有键组成的 Set 集合
values()
返回所有值组成的 Collection 集合
entrySet()
返回所有键值对对象组成的 Set<Map.Entry<K,V>>
size() / clear() / isEmpty()
获取大小 / 清空 / 判断是否为空
1.4 三种遍历方式
方式
原理
适用场景
键找值 keySet()
先获取所有键,再用 get(key) 取值
只需要键或需要根据键做逻辑
键值对 entrySet()
直接获取 Entry 对象,同时拿到键和值
同时操作键和值,推荐
forEach map.forEach()
传入 BiConsumer,内部自动遍历
代码最简洁,Lambda 友好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 HashMap<Integer, String> map = new HashMap <>(); map.put(207 , "11期" ); map.put(206 , "12期" ); map.put(205 , "办公室" ); for (Integer key : map.keySet()) { System.out.println(key + " => " + map.get(key)); } for (Map.Entry<Integer, String> entry : map.entrySet()) { System.out.println(entry.getKey() + " -> " + entry.getValue()); } map.forEach((k, v) -> System.out.println(k + " --> " + v));
1.5 自定义类型作为键的注意事项
集合类型
要求
HashMap / LinkedHashMap
键所在类必须重写 hashCode() 和 equals(),否则无法正确去重
TreeMap
键所在类需实现 Comparable 接口,或 创建时传入 Comparator
1 2 3 4 5 6 7 8 9 class Student implements Comparable <Student> { @Override public int compareTo (Student o) { return this .age - o.age; } } Map<Student, String> map = new TreeMap <>((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
1.6 统计字符出现次数(getOrDefault 经典用法) 1 2 3 4 5 6 String s = "abacaDad123" ;HashMap<Character, Integer> map = new HashMap <>(); for (char c : s.toCharArray()) { map.put(c, map.getOrDefault(c, 0 ) + 1 ); } System.out.println(map);
二、Properties 集合 2.1 特点
项目
说明
继承关系
Properties → Hashtable → Map,是 Map 的间接实现类
泛型
创建时不能 指定泛型(内部已固定为 Object)
特色
唯一与 IO 流配合使用的双列集合;提供操作 String 键值的专属方法
2.2 Properties 特有方法
方法
说明
setProperty(String key, String value)
存储字符串键值对(等价于 put)
getProperty(String key)
根据键获取字符串值,不存在返回 null
getProperty(String key, String defaultVal)
键不存在时返回默认值
stringPropertyNames()
获取所有由 setProperty 存储的键,返回 Set<String>
注意 :stringPropertyNames() 只能遍历通过 setProperty 存入的数据,通过 put 存入的非 String 数据无法获取。
三、集合嵌套 3.1 概念 将一个集合作为元素存入另一个集合,形成嵌套结构,用于表达一对多 或多对多 的数据关系。
3.2 常见嵌套结构
结构
类型声明
典型场景
List 嵌套 List
List<List<Integer>>
二维数据(如行列数据、多组数字)
List 嵌套 Map
List<Map<K, V>>
多个对象的属性集合(如多行记录)
Map 嵌套 List
Map<String, List<String>>
一对多(如省份 → 城市列表)
Map 嵌套 Map
Map<String, Map<K, V>>
二级分类(如楼栋 → 教室编号)
1 2 3 4 5 6 7 8 9 10 Map<String, List<String>> map = new HashMap <>(); List<String> cities = new ArrayList <>(); Collections.addAll(cities, "南京市" , "苏州市" , "扬州市" ); map.put("江苏省" , cities); map.forEach((province, cityList) -> { System.out.println(province + " -> " + cityList); });
1 2 3 4 5 6 7 8 9 10 11 12 Map<String, Map<Integer, String>> map = new HashMap <>(); map.put("3号楼" , Map.of(207 , "11期" , 206 , "12期" )); map.put("4号楼" , Map.of(413 , "9期" , 415 , "10期" )); for (Map.Entry<String, Map<Integer, String>> entry : map.entrySet()) { System.out.println(entry.getKey() + " 有以下教室:" ); entry.getValue().forEach((room, cls) -> System.out.print(room + "->" + cls + " " )); System.out.println(); }
四、不可变集合 4.1 概念与创建
项目
说明
用途
初始化后只读,不涉及增删改,适合做常量数据
创建方式
调用各集合接口的静态方法 of(...)
不支持操作
add / remove / set,调用会抛出 UnsupportedOperationException
支持操作
get / contains 等查询操作
4.2 三种不可变集合
类型
创建语法
特殊限制
List
List.of(e1, e2, ...)
允许重复元素
Set
Set.of(e1, e2, ...)
不允许重复 元素,否则抛出异常
Map
Map.of(k1,v1, k2,v2, ...)
键不允许重复 ,否则抛出异常
1 2 3 4 5 6 7 8 9 List<Integer> list = List.of(1 , 3 , 5 , 2 , 4 , 6 ); List<Integer> mutableList = new ArrayList <>(list); mutableList.add(88 ); Set<String> set = Set.of("Java" , "Python" , "Go" ); Map<String, Integer> map = Map.of("a" , 1 , "b" , 2 );
五、XML 5.1 XML 概述
项目
说明
全称
可扩展标记语言(eXtensible Markup Language)
作用
① 存储/传输数据;② 作为配置文件(如 MyBatis)
与 HTML 区别
XML 标签自定义、语法严格、用于存储数据;HTML 标签预定义、语法松散、用于展示数据
5.2 XML 语法规则
组成部分
规则
文档声明
必须在第 0 行 0 列:<?xml version="1.0" encoding="UTF-8"?>
根标签
全文只能有一个 根标签,其他标签在其内部
标签命名
区分大小写;不含空格和冒号;不建议以 xml 开头
属性
写在开始标签内,格式 属性名="属性值",值必须加引号
注释
<!-- 注释内容 -->
转义字符
< → <,> → >,& → &,' → '," → "
空标签
<tag/> 自闭合
1 2 3 4 5 6 <?xml version="1.0" encoding="UTF-8" ?> <beans > <bean id ="001" className ="cn.demo.User" > <property name ="username" value ="jack" > 杰克</property > </bean > </beans >
5.3 三种解析方式对比
方式
特点
优点
缺点
DOM
整个 XML 加载到内存,生成 Document 树
可增删改查,操作灵活
文件过大可能内存溢出
SAX
逐行扫描,事件驱动
速度快,可处理大文件
只能读,不能增删改
PULL
类似 SAX,Android 内置
轻量
功能较简单
5.4 DOM4J 解析 API
对象 / 方法
说明
new SAXReader()
创建解析器对象
reader.read("路径")
读取 xml 文件,返回 Document 对象
document.getRootElement()
获取根标签(Element)
element.elements()
获取所有子标签列表
element.element("名称")
获取指定名称的第一个子标签
element.getName()
获取标签名
element.getText()
获取标签体文本
element.attributeValue("属性名")
获取属性值
element.attribute("属性名").getValue()
先获取 Attribute 对象再取值
1 2 3 4 5 6 7 8 9 10 11 12 SAXReader reader = new SAXReader ();Document doc = reader.read("xml/a.xml" ); Element root = doc.getRootElement(); List<Element> beans = root.elements(); for (Element bean : beans) { String id = bean.attributeValue("id" ); List<Element> props = bean.elements(); for (Element prop : props) { System.out.println(prop.getName() + " = " + prop.getText()); } }
六、注解 6.1 概述
项目
说明
定义
代码级别的说明(元数据),JDK 1.5 引入,与类/接口/枚举同级
三大作用
① 编译检查(如 @Override);② 代码分析(反射读取);③ 生成文档
常见内置注解
@Override 重写检查 / @FunctionalInterface 函数式接口检查 / @SuppressWarnings 抑制警告
6.2 自定义注解语法 1 2 3 4 public @interface 注解名 { 数据类型 属性名(); 数据类型 属性名() default 默认值; }
属性可用数据类型 :8 种基本类型、String、枚举、注解、Class,以及上述类型的一维数组 。
6.3 注解使用规则
规则
说明
无属性注解
直接 @注解名 使用
有属性注解
@注解名(属性名=值, 属性名=值)
属性名为 value 且只有一个属性
赋值时可省略属性名 ,直接写值
数组属性只有一个值
{} 可省略;多个值时不可省略
同一位置同一注解
只能使用一次
同一位置
可使用多个不同 注解
1 2 3 4 5 @MyAnno("张三") @MyAnno(hobbies="看球")
6.4 元注解
元注解是注解注解的注解 ,用于限制自定义注解的行为。
元注解
作用
常用参数
@Target
限制注解可以写在哪些位置
ElementType.TYPE(类/接口)FIELD(成员变量)METHOD(方法)PARAMETER(参数)CONSTRUCTOR(构造方法)LOCAL_VARIABLE(局部变量)
@Retention
限制注解的生命周期(存活阶段)
SOURCE(仅源码)CLASS(源码+字节码,默认)RUNTIME(源码+字节码+运行时内存 ,可被反射读取)
1 2 3 4 5 6 7 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Books { String value () ; double price () default 100 ; String[] authors(); }
6.5 注解解析(反射获取注解)
方法
说明
clazz.isAnnotationPresent(注解.class)
判断该类上是否存在指定注解
clazz.getAnnotation(注解.class)
获取类上指定注解的对象
method.isAnnotationPresent(注解.class)
判断方法上是否存在指定注解
method.getAnnotation(注解.class)
获取方法上指定注解的对象
1 2 3 4 5 6 7 8 Class<?> clazz = Class.forName("itheima06.BookStore" ); if (clazz.isAnnotationPresent(Books.class)) { Books anno = clazz.getAnnotation(Books.class); System.out.println(anno.value()); System.out.println(anno.price()); System.out.println(Arrays.toString(anno.authors())); }
七、综合对比速查 Map 实现类选择
需求
推荐集合
通用键值存储,不关心顺序
HashMap
需要保证键的插入顺序
LinkedHashMap
需要对键排序
TreeMap
需要和文件/IO 配合
Properties
集合嵌套遍历思路
结构
遍历口诀
List<List<T>>
外层 for-each 取子 List → 内层 for-each 取元素
List<Map<K,V>>
外层 for-each 取子 Map → 对子 Map 用 forEach 或 entrySet
Map<K, List<V>>
entrySet 取 Entry → entry.getValue() 得到 List → 再遍历
Map<K, Map<K,V>>
entrySet 取外层 Entry → entry.getValue() 得到内层 Map → 再遍历
注解生命周期选择
使用场景
选择
仅用于编译器提示,不需要运行时读取
RetentionPolicy.SOURCE
框架通过字节码增强读取(如 Spring AOP)
RetentionPolicy.CLASS
需要通过反射在运行时读取(最常用)
RetentionPolicy.RUNTIME
Lambda 表达式 · 函数式接口 · Stream 流 · 方法引用 · 重点总结 一、函数式接口 1.1 概念与前提
项目
说明
定义
接口中有且仅有一个 抽象方法需要重写,称为函数式接口
意义
专门为 Lambda 营造使用环境,遇到函数式接口可直接用 Lambda 简化代码
标识注解
@FunctionalInterface:编译器检测是否符合函数式接口规范
注意
只能是接口,不能是抽象类 ;可以有默认方法和静态方法
1.2 Java 提供的四大函数式接口
接口
类型
核心抽象方法
参数
返回值
用途
Supplier<T>
生产者
T get()
无
T
按自定义逻辑生产一个数据
Consumer<T>
消费者
void accept(T t)
T
无
接收数据并进行处理/消费
Function<T,R>
转换者
R apply(T t)
T
R
将 T 类型数据转换为 R 类型
Predicate<T>
断言者
boolean test(T t)
T
boolean
对数据进行判断,返回布尔值
代码示例:四大接口 Lambda 写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Supplier<Integer> s = () -> 666 ; System.out.println(s.get()); Consumer<String> c = str -> System.out.println(str.toUpperCase()); c.accept("hello" ); Function<String, Integer> f = str -> Integer.parseInt(str); System.out.println(f.apply("123" )); Predicate<Integer> p = num -> num % 2 == 0 ; System.out.println(p.test(4 ));
二、Lambda 表达式 2.1 核心概念
项目
说明
本质
对匿名内部类的简化书写,是 Java 8 新语法
前提
必须存在一个函数式接口 作为推导依据
核心思想
“凡是可以推导的,都可以省略”
2.2 标准语法 1 2 3 (参数类型 参数名) -> { 方法体代码; }
2.3 省略规则
省略项
规则
示例
参数类型
可省略
(Person p) → (p)
括号 ()
只有一个参数时可省略
(p) → p
花括号 {}、return、;
方法体只有一行时,三者同时 省略或保留
{ return a+b; } → a+b
箭头 ->
永远不能省略
—
无参数
() 不可省略
() -> {...}
代码示例:从匿名内部类到 Lambda 的演化
1 2 3 4 5 6 7 8 9 10 11 12 13 list.sort(new Comparator <Integer>() { @Override public int compare (Integer a, Integer b) { return b - a; } }); list.sort((Integer a, Integer b) -> { return b - a; }); list.sort((a, b) -> b - a);
三、Stream 流 3.1 核心思想
项目
说明
定义
Java 8 提供的流式处理数据的接口 java.util.stream.Stream<T>
特点
链式调用;中间操作返回新流,旧流不可复用 ;终结操作触发实际计算
操作分类
中间操作(返回新流)、终结操作(产生结果,流结束)
3.2 获取 Stream 流的方式
来源
方法
示例
单列集合(List/Set)
集合.stream()
list.stream()
双列集合(Map)
先转 entrySet() 再 .stream()
map.entrySet().stream()
数组
Arrays.stream(数组)
Arrays.stream(arr)
零散数据
Stream.of(元素...)
Stream.of("a","b","c")
合并两个流
Stream.concat(流1, 流2)
Stream.concat(s1, s2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 List<Integer> list = List.of(2 , 5 , 8 , 3 , 6 , 9 ); Stream<Integer> stream = list.stream(); System.out.println(stream); Map<Integer, Integer> map = Map.of(1 , 11 , 2 , 22 , 3 , 33 ); Set<Map.Entry<Integer, Integer>> set = map.entrySet(); Stream<Map.Entry<Integer, Integer>> stream1 = set.stream(); System.out.println(stream1); String[] arr = {"aa" ,"bb" }; Stream<String> stream2 = Arrays.stream(arr); System.out.println(stream2); Stream<String> a = Stream.of("a" , "b" ); System.out.println(a);
3.3 中间操作(返回新 Stream,可链式调用)
方法
参数接口
作用
filter(Predicate)
Predicate<T>
保留满足条件的元素,剔除不满足的
map(Function)
Function<T,R>
将每个元素按规则转换为另一类型
flatMap(Function)
Function<T, Stream<R>>
将嵌套集合/数组展开,合并为一个流
sorted()
无 / Comparator
自然排序 / 自定义排序
distinct()
无
去重(依赖 equals 和 hashCode)
limit(long n)
long
保留前 n 个元素
skip(long n)
long
跳过前 n 个元素
peek(Consumer)
Consumer<T>
遍历消费但不改变元素,用于调试
综合示例
1 2 3 4 5 6 7 8 9 List<Integer> list = List.of(2 , 5 , 4 , 10 , 2 , 4 , 12 , 88 , 8 , 3 , 6 , 9 ); list.stream() .filter(s -> s > 2 && s % 2 == 0 ) .distinct() .skip(1 ) .limit(3 ) .forEach(System.out::println);
3.4 终结操作(触发计算,流结束)
分类
方法
说明
遍历
forEach(Consumer)
对每个元素执行消费操作
统计
count()
返回元素总数(long)
最值
max(Comparator) / min(Comparator)
返回 Optional<T>
匹配
anyMatch(Predicate)
任一元素满足条件则返回 true
匹配
allMatch(Predicate)
所有元素满足条件则返回 true
匹配
noneMatch(Predicate)
没有元素满足条件则返回 true
查找
findFirst()
返回第一个元素(Optional<T>)
查找
findAny()
返回任意元素(并行流效率更高)
归约
reduce(BinaryOperator)
将所有元素累积合并为一个结果
收集
collect(Collector)
将流收集为集合/Map 等结构
转数组
toArray(IntFunction)
将流元素转为数组
3.5 数据收集(collect)
目标结构
写法
说明
List(可变)
collect(Collectors.toList())
返回可修改的 List
List(不可变)
.toList()(JDK 16+)
返回不可变 List
Set
collect(Collectors.toSet())
自动去重
Map
collect(Collectors.toMap(keyFn, valueFn))
指定 key 和 value 的生成规则
数组
toArray(n -> new String[n])
类型需与流元素一致
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 List<Integer> list = List.of(2 , 5 , 8 , 3 , 6 , 9 , 1 , 4 , 7 ); List<Integer> evenList = list.stream() .filter(s -> s % 2 == 0 ) .collect(Collectors.toList()); Map<Integer, Integer> map = list.stream() .collect(Collectors.toMap(s -> s, s -> s * 2 )); String[] arr = list.stream() .filter(s -> s % 2 == 1 ) .map(String::valueOf) .toArray(String[]::new );
综合练习(concat + map + collect)
1 2 3 4 5 6 7 8 9 10 11 12 List<String> men = List.of("张学友" , "周润发" , "黎明" , "伍佰" , "刘德华" ); List<String> women = List.of("杨紫" , "杨敏" , "杨毅杨" , "杨柳" , "鸡柳" ); Map<String, Actor> result = Stream.concat( men.stream().filter(s -> s.length() == 3 ).limit(2 ), women.stream().filter(s -> s.startsWith("杨" )).skip(1 ) ) .map(Actor::new ) .collect(Collectors.toMap(Actor::getName, a -> a)); System.out.println(result);
四、方法引用 4.1 核心概念
项目
说明
本质
Lambda 的进一步简化(语法糖),将已有方法直接引用为 Lambda 实现
符号
::
前提条件
① Lambda 体中只有一行 代码;② Lambda 中不对参数做任何额外处理 ,直接传递给引用方法
4.2 四种引用方式
方式
语法
适用场景
示例
静态方法引用
类名::静态方法名
Lambda 体只是调用某个静态方法
Integer::parseInt
实例方法引用
对象名::实例方法名
Lambda 体只是调用某个对象的方法
System.out::println
构造方法引用
类名::new
Lambda 体只是 new 某个对象
Person::new
数组构造引用
类型[]::new
Lambda 体只是创建某类型数组
String[]::new
4.3 代码对比示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Function<String, Integer> f1 = s -> Integer.parseInt(s); Function<String, Integer> f2 = Integer::parseInt; Consumer<String> c1 = s -> System.out.println(s); Consumer<String> c2 = System.out::println; Function<String, Person> fn1 = name -> new Person (name); Function<String, Person> fn2 = Person::new ; Function<Integer, int []> a1 = n -> new int [n]; Function<Integer, int []> a2 = int []::new ;
实际应用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 List<Integer> list = new ArrayList <>(List.of(1 , 3 , 5 , 7 , 2 , 5 , 8 , 4 )); list.removeIf(MyTools::isEven); MyTools tools = new MyTools ();list.removeIf(tools::isOdd); List<Person> persons = names.stream() .map(Person::new ) .collect(Collectors.toList()); .collect(Collectors.toMap(Person::getName, p -> p));
五、综合对比 Lambda vs 匿名内部类
对比项
匿名内部类
Lambda 表达式
接口限制
可有多个抽象方法
只能是函数式接口(1个抽象方法)
代码量
较多(需写类名、方法名、注解)
简洁
this 指向
指向匿名内部类自身
指向外层类
可读性
清晰但冗长
简洁但需熟悉语法
方法引用 vs Lambda
场景
推荐写法
Lambda 体有多行逻辑
使用 Lambda
Lambda 体只是调用一个已有方法(无额外处理)
使用方法引用
Lambda 体只是 new 一个对象
使用构造方法引用
Stream 中间操作 vs 终结操作
特征
中间操作
终结操作
返回值
Stream<T>(新流)
具体结果(集合、值、void)
是否触发计算
不触发 (惰性求值)
触发 计算
可否链式调用
可以
不可以(流已关闭)
典型方法
filter map sorted distinct limit skip
forEach collect count max min reduce
多线程 · 线程安全 · 重点总结
整理自:16-多线程.md + 进阶10-方法引用-多线程-线程安全.xmind
一、基础概念 1.1 核心名词
概念
说明
并行
同一时刻,多个指令在多个 CPU 上同时 执行
并发
同一时刻,多个指令在单个 CPU 上交替 执行
进程
计算机中正在运行的程序;是系统分配资源和调度 的独立单位
线程
进程内一条执行路径(任务通道);一个进程可包含多条线程
单线程程序
一个进程只有一条执行路径
多线程程序
一个进程有多条执行路径,可提升性能
二、创建线程的三种方式 2.1 三种方式总览对比
方式
关键步骤
优点
缺点
继承 Thread 类
继承 → 重写 run() → new 对象 → start()
编程简单,可直接使用 Thread 方法
单继承限制,扩展性差
实现 Runnable 接口
实现 → 重写 run() → 包装进 Thread → start()
扩展性强,可同时继承其他类
编程相对复杂,不能直接用 Thread 方法
实现 Callable 接口
实现 → 重写 call() → FutureTask 包装 → Thread 包装 → start() → get()
有返回值 ,可抛出异常
最复杂;FutureTask 只能执行一次
2.2 run() vs start() 区别(⭐ 常考)
方法
说明
run()
封装线程执行的任务代码;直接调用 = 普通方法调用,不开启新线程
start()
开启新线程 ;JVM 自动调用该线程的 run() 方法
2.3 方式一:继承 Thread 类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyThread extends Thread { @Override public void run () { for (int i = 0 ; i < 10 ; i++) { System.out.println(getName() + " 执行:" + i); } } } MyThread t = new MyThread ();t.setName("线程A" ); t.start();
2.4 方式二:实现 Runnable 接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MyRun implements Runnable { @Override public void run () { for (int i = 0 ; i < 10 ; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } MyRun task = new MyRun ();Thread t = new Thread (task, "线程B" );t.start();
2.5 方式三:实现 Callable 接口(有返回值)
关键点
说明
为什么需要 FutureTask
Thread 只接受 Runnable;FutureTask 实现了 Runnable,可”包装” Callable
get() 的阻塞性
子线程未执行完时,主线程会被阻塞 ,直到子线程返回结果
FutureTask 只执行一次
需要多个结果,必须创建多个 FutureTask 对象
异常处理
get() 会抛出 InterruptedException 和 ExecutionException,需 try-catch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MyCall implements Callable <Integer> { @Override public Integer call () throws Exception { int sum = 0 ; for (int i = 1 ; i <= 100 ; i++) sum += i; return sum; } } MyCall call = new MyCall ();FutureTask<Integer> task = new FutureTask <>(call); Thread t = new Thread (task);t.start(); Integer result = task.get();System.out.println("结果:" + result);
三、Thread 类常用方法 3.1 方法速查表
方法
说明
String getName()
获取线程名称(默认 Thread-0、Thread-1…)
void setName(String name)
设置线程名称
Thread(Runnable, String name)
创建线程时直接命名
static Thread currentThread()
返回当前正在执行 的线程对象(在任意位置均可调用)
static void sleep(long millis)
让当前线程 休眠指定毫秒(需处理 InterruptedException)
final int getPriority()
获取线程优先级(默认 5,范围 1~10)
final void setPriority(int p)
设置优先级(MIN_PRIORITY=1,NORM_PRIORITY=5,MAX_PRIORITY=10)
void setDaemon(boolean on)
设为守护线程(所有非守护线程结束后,守护线程随 JVM 一起退出)
void start()
启动线程
3.2 优先级常量
常量
值
说明
Thread.MIN_PRIORITY
1
最低优先级
Thread.NORM_PRIORITY
5
默认优先级
Thread.MAX_PRIORITY
10
最高优先级
⚠️ 优先级高只是获得 CPU 时间片的概率更大,不能保证一定先执行 (Java 使用抢占式调度)。
3.3 守护线程 vs 用户线程
类型
说明
用户线程(非守护)
默认类型;JVM 等待所有用户线程结束后才退出
守护线程
setDaemon(true) 设置;所有用户线程结束后,守护线程随 JVM 自动退出(适合做后台服务,如”主公”与”护卫”关系)
1 2 3 4 5 Thread guard = new Thread (() -> { while (true ) System.out.println("守护中..." ); }, "守护线程" ); guard.setDaemon(true ); guard.start();
四、线程调度 4.1 两种调度模型
模型
说明
分时调度
所有线程轮流使用 CPU,平均分配时间片
抢占式调度
优先级高的线程优先获得 CPU(Java 采用此模型 );优先级相同则随机选择
五、线程安全 5.1 问题产生的三个条件
条件
说明
多线程环境
存在多个线程并发运行
有共享数据
多个线程操作同一个变量/资源
多条语句操作共享数据
线程在任意位置都可能被抢占,导致数据逻辑错误
⚠️ 卖票典型问题:相同票号出现多次、出现负数票号(线程在 if 判断后、-- 执行前被切走)。
5.2 三种解决方案对比
方案
格式
锁对象
注意事项
同步代码块
synchronized(锁对象){ 代码 }
手动指定,推荐用 this 或共享的唯一对象
锁定代码越少越好;多线程必须使用同一个 锁对象
同步方法
方法签名前加 synchronized
非静态方法默认 this;静态方法默认 类名.class
无法手动指定锁对象;效率略低于同步代码块
Lock 锁
lock.lock() / lock.unlock()
ReentrantLock 实例
unlock() 必须放在 finally 中确保释放
5.3 synchronized vs Lock 对比
特性
synchronized
Lock(ReentrantLock)
实现层面
JVM 关键字
JDK 接口 + 实现类
锁的释放
自动 (代码块结束/异常)
手动 (必须调用 unlock())
公平性
只能非公平
可选公平(new ReentrantLock(true))或非公平
灵活性
低
高(可尝试获取、超时、可中断等)
5.4 三种方案代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 synchronized (this ) { if (tickets > 0 ) { System.out.println(Thread.currentThread().getName() + " 卖出:" + tickets--); } } private synchronized boolean sell () { if (tickets > 0 ) { System.out.println(Thread.currentThread().getName() + " 卖出:" + tickets--); return true ; } return false ; } private final Lock lock = new ReentrantLock (true ); public void run () { while (true ) { lock.lock(); try { if (tickets > 0 ) { System.out.println(Thread.currentThread().getName() + " 卖出:" + tickets--); } else break ; } finally { lock.unlock(); } } }
六、死锁 6.1 概念与成因
项目
说明
定义
两个或多个线程互相持有对方所需的锁资源,导致永远等待
产生条件
① 资源有限;② synchronized 嵌套 (A 持有锁1等锁2,B 持有锁2等锁1)
如何避免
避免嵌套加锁;按固定顺序获取锁;使用 Lock 的 tryLock() 设置超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Object lockA = new Object (), lockB = new Object ();new Thread (() -> { synchronized (lockA) { synchronized (lockB) { System.out.println("线程1" ); } } }).start(); new Thread (() -> { synchronized (lockB) { synchronized (lockA) { System.out.println("线程2" ); } } }).start();
七、线程生命周期(六种状态)
状态
说明
进入方式
NEW(新建)
线程对象已创建,未调用 start()
new Thread()
RUNNABLE(可运行)
调用 start() 后,等待 CPU 调度或正在执行
t.start()
BLOCKED(阻塞)
等待获取 synchronized 锁
争抢锁失败
WAITING(无限等待)
调用 wait() / join() 等,需被唤醒
obj.wait()
TIMED_WAITING(计时等待)
调用 sleep(ms) / wait(ms) 等,超时自动恢复
Thread.sleep(1000)
TERMINATED(终止)
run() / call() 执行完毕,线程结束
正常结束或异常退出
1 2 3 4 5 6 NEW → start() → RUNNABLE ←→ BLOCKED(抢锁失败) ↓ TIMED_WAITING(sleep/wait超时) WAITING(wait/join) ↓ RUNNABLE → TERMINATED
八、综合对比速查 三种创建方式选型
需求
推荐方式
简单任务,不需要返回值
继承 Thread 或 实现 Runnable
需要与其他类继承,扩展性要求高
实现 Runnable
需要返回值 或线程执行结果
实现 Callable + FutureTask
锁的选型
场景
推荐
简单同步,代码块少
synchronized 代码块
整个方法需要同步
synchronized 方法
需要公平锁、可中断、可超时
ReentrantLock
unlock 必须在哪写
finally 块中
线程安全问题排查步骤 1 2 3 4 5 ① 是否多线程环境? ② 是否有共享数据(成员变量/静态变量)? ③ 是否有多条语句一起操作共享数据? ↓ 三个条件同时满足 → 存在线程安全问题 ↓ 解决:选择同步代码块 / 同步方法 / Lock 锁
整理自:17-File类-IO-Hutool.md + 进阶11-File-IO.xmind
一、File 类 1.1 核心概念
项目
说明
作用
描述计算机中的文件、文件夹和路径
重要限制
File 对象只能操作文件本身 (创建/删除/判断/获取信息),不能操作文件内容
路径分隔符
File.separator(跨平台);Windows 是 \,Linux/Mac 是 /
多路径分隔符
File.pathSeparator;Windows 是 ;,Linux/Mac 是 :
1.2 绝对路径 vs 相对路径
类型
说明
示例
绝对路径
从盘符(或根目录)开始的完整路径
D:\projects\day10\io\a.txt
相对路径
相对于项目根路径 的路径(IDEA 默认)
day10\io\a.txt
1.3 三种构造方法
构造方法
说明
new File(String pathname)
直接传文件/文件夹路径(最常用)
new File(String parent, String child)
父路径(字符串)+ 子路径
new File(File parent, String child)
父路径(File 对象)+ 子路径
⚠️ 无论硬盘上是否真实存在,File 对象都能创建成功 ,exists() 才能判断是否真实存在。
1.4 获取信息方法
方法
说明
getAbsolutePath()
返回绝对路径 字符串
getPath()
返回构造方法传入的路径 字符串
getName()
返回文件/文件夹的名称 (路径最后一个分隔符后的内容)
length()
返回文件的字节数 (⚠️ 不能用于文件夹,结果不准确)
getParentFile()
返回父目录 的 File 对象
1.5 判断方法
方法
说明
exists()
文件/文件夹是否真实存在
isFile()
是否是文件
isDirectory()
是否是文件夹/目录
1.6 创建和删除方法
方法
说明
注意事项
createNewFile()
创建新文件 ,成功返回 true
文件已存在返回 false;文件夹不存在则抛异常
mkdir()
创建单级 文件夹
只能创建一级,多级失败
mkdirs()
创建多级 文件夹
包含一级,推荐永远用此方法
delete()
删除文件或空文件夹
删除不走回收站 ;非空文件夹无法直接删除
1.7 遍历方法
方法
返回值
说明
list()
String[]
获取当前目录下所有内容的名称数组
listFiles()
File[]
获取当前目录下所有内容的 File 对象数组 (常用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static void listAllFiles (File dir) { if (dir == null || !dir.exists()) return ; if (dir.isFile()) { System.out.println(dir.getAbsolutePath()); return ; } File[] files = dir.listFiles(); if (files != null ) { for (File f : files) listAllFiles(f); } } public static long getFolderSize (File dir) { if (dir == null || !dir.exists()) return 0 ; if (dir.isFile()) return dir.length(); long size = 0 ; File[] files = dir.listFiles(); if (files != null ) { for (File f : files) size += getFolderSize(f); } return size; }
二、IO 流 2.1 IO 流核心概念
项目
说明
方向判断原则
永远站在内存的角度 看待方向
输入流(读)
磁盘/网络 → 内存(程序)
输出流(写)
内存(程序) → 磁盘/网络
2.2 IO 流四大分类
分类维度
分类
顶层抽象类
常用实现类
按操作单元
字节流
InputStream / OutputStream
FileInputStream / FileOutputStream
按操作单元
字符流
Reader / Writer
FileReader / FileWriter
按方向
输入流(读)
InputStream / Reader
—
按方向
输出流(写)
OutputStream / Writer
—
命名规律 :所有 IO 流实现类名称 = 功能前缀 + 体系后缀 (如 FileInputStream = File + InputStream)
三、字节流 3.1 字节输出流(FileOutputStream)
项目
说明
父类
OutputStream(抽象类,不能直接 new)
作用
以字节为单位向文件写出 数据
构造方法:
构造方法
说明
new FileOutputStream(String/File path)
覆盖写入(文件存在则清空原内容 )
new FileOutputStream(String/File path, true)
追加 写入,保留原内容
⚠️ 文件不存在会自动创建 ;文件夹不存在会直接抛异常 。
写出方法:
方法
说明
write(int b)
写出一个字节
write(byte[] bs)
写出整个字节数组
write(byte[] bs, int start, int len)
写出字节数组的指定部分
close()
关闭流,释放资源
1 2 3 4 5 FileOutputStream fos = new FileOutputStream ("output.txt" , true );fos.write("Hello\r\n" .getBytes()); fos.write(new byte []{'J' ,'a' ,'v' ,'a' }); fos.close();
项目
说明
父类
InputStream(抽象类,不能直接 new)
作用
以字节为单位从文件读入 数据
结束标志
读到文件末尾返回 -1
⚠️ 文件不存在会直接抛 FileNotFoundException 。
读取方法对比:
方法
返回值含义
说明
read()
读到的字节码值 (0~255),末尾返回 -1
一次读一个字节,效率低
read(byte[] bs)
实际读取到的字节个数 ,末尾返回 -1
一次读一个数组,效率高(推荐 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 FileInputStream fis = new FileInputStream ("input.txt" );int b;while ((b = fis.read()) != -1 ) { System.out.print((char ) b); } fis.close(); FileInputStream fis2 = new FileInputStream ("input.txt" );byte [] arr = new byte [8192 ];int count;while ((count = fis2.read(arr)) != -1 ) { System.out.print(new String (arr, 0 , count)); } fis2.close();
3.3 文件复制(字节流核心应用) 1 2 3 4 5 6 7 8 9 10 11 12 public static void copyFile (File src, File dest) throws IOException { FileInputStream fis = new FileInputStream (src); FileOutputStream fos = new FileOutputStream (dest); byte [] buf = new byte [8192 ]; int count; while ((count = fis.read(buf)) != -1 ) { fos.write(buf, 0 , count); } fis.close(); fos.close(); }
✅ 字节流可以复制任何类型 的文件(图片、视频、文档等),因为操作的是最小单位——字节。
四、编码表 4.1 三种常用字符集对比
字符集
英文字母
中文汉字
说明
ASCII
1 字节
—
最早的编码标准,仅含英文
GBK
1 字节
2 字节
中国国标,第1位为0=字母,1=汉字
UTF-8
1 字节
3 字节
国际通用,推荐使用
⚠️ 乱码原因 :编码和解码使用的字符集不一致!
4.2 编码 & 解码 API
操作
方向
方法
编码
字符串 → 字节数组
str.getBytes() / str.getBytes("GBK")
解码
字节数组 → 字符串
new String(bytes) / new String(bytes, "GBK")
1 2 3 4 5 6 7 8 9 String s = "我爱Java" ;byte [] utf8Bytes = s.getBytes(); byte [] gbkBytes = s.getBytes("GBK" ); String s1 = new String (utf8Bytes); String s2 = new String (gbkBytes, "GBK" );
五、字符流 5.1 字符流 vs 字节流
对比项
字节流
字符流
操作单元
字节(1 byte)
字符(自动处理编码)
适合场景
二进制文件(图片/视频/音频)、任意复制
纯文本文件 (读写中文更方便)
中文处理
可能乱码(需拼接字节)
✅ 天然支持,自动处理
本质
—
字符流 = 字节流 + 编码表
5.2 字符输入流(FileReader)
构造方法
说明
new FileReader(String/File path)
使用平台默认编码 读取
new FileReader(File, Charset)
指定编码读取(JDK11+)
方法
返回值含义
read()
读到的字符的 Unicode 码值,末尾返回 -1
read(char[] cbuf)
实际读取的字符个数,末尾返回 -1
close()
关闭流
1 2 3 4 5 6 7 8 FileReader fr = new FileReader ("text.txt" );char [] cbuf = new char [1024 ];int count;while ((count = fr.read(cbuf)) != -1 ) { System.out.print(new String (cbuf, 0 , count)); } fr.close();
5.3 字符输出流(FileWriter)
构造方法
说明
new FileWriter(String/File path)
覆盖写入(默认编码)
new FileWriter(String/File path, true)
追加 写入
new FileWriter(File, Charset)
指定编码(JDK11+)
方法
说明
write(int c)
写一个字符
write(char[] cbuf)
写字符数组
write(String str)
直接写字符串 (比字节流方便)
flush()
强制将缓冲区数据刷到硬盘(循环中使用,防止丢数据)
close()
关闭流(自动 flush )
⚠️ FileWriter 写完数据必须调用 flush() 或 close() ,否则缓冲区数据不会写入硬盘!
1 2 3 4 FileWriter fw = new FileWriter ("log.txt" , true );fw.write("用户名,密码\r\n" ); fw.close();
六、异常处理(IO 流资源释放) 6.1 两种处理方式对比
方式
格式
优缺点
传统 try-catch-finally
finally { close(); }
代码冗长,close 本身也可能抛异常
try-with-resources(推荐)
try(流对象声明){ }
JDK7+,自动关闭 ,代码简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 try (FileInputStream fis = new FileInputStream ("src.txt" ); FileOutputStream fos = new FileOutputStream ("dest.txt" )) { byte [] buf = new byte [8192 ]; int count; while ((count = fis.read(buf)) != -1 ) { fos.write(buf, 0 , count); } } catch (IOException e) { e.printStackTrace(); } FileWriter fw = null ;try { fw = new FileWriter ("out.txt" ); fw.write("hello" ); } catch (IOException e) { e.printStackTrace(); } finally { if (fw != null ) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } }
7.1 概述
项目
说明
定位
小而全的 Java 工具库,用静态方法封装常用操作,降低学习成本
核心类
FileUtil(文件操作)/ IoUtil(IO 流操作)
引入方式
在项目中添加 Hutool 依赖(Maven/Gradle 或导入 jar 包)
7.2 FileUtil 常用方法
方法
说明
FileUtil.readUtf8Lines(path)
按行读取 UTF-8 文件,返回 List<String>
FileUtil.readLines(path, charset)
按行读取指定编码文件
FileUtil.writeUtf8String(content, file)
将字符串写入文件(UTF-8,覆盖)
FileUtil.appendUtf8String(content, file)
追加字符串到文件
FileUtil.copy(src, dest, override)
复制文件/文件夹(override=是否覆盖)
FileUtil.del(file)
删除文件或文件夹(含子内容)
FileUtil.exist(path)
判断文件是否存在
FileUtil.size(file)
获取文件/文件夹大小(字节)
1 2 3 4 5 6 7 8 List<String> lines = FileUtil.readUtf8Lines("D:/data/1.txt" ); System.out.println(lines); FileUtil.writeUtf8String("Hello Hutool!" , new File ("output.txt" )); FileUtil.copy(new File ("D:/src" ), new File ("D:/dest" ), true );
八、Properties 与 IO 流结合
操作
方法
说明
写入文件
properties.store(writer, "注释")
将 Properties 数据写入 .properties 文件
从文件读取
properties.load(reader)
将 .properties 文件内容加载到 Properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Properties p = new Properties ();p.setProperty("username" , "admin" ); p.setProperty("password" , "123456" ); FileWriter fw = new FileWriter ("config.properties" );p.store(fw, "配置文件" ); fw.close(); Properties p2 = new Properties ();FileReader fr = new FileReader ("config.properties" );p2.load(fr); fr.close(); System.out.println(p2.getProperty("username" ));
九、综合对比速查 字节流 vs 字符流选型
场景
推荐流
复制图片、视频、音频等二进制文件
字节流 (FileInputStream/FileOutputStream)
读写纯文本文件(含中文)
字符流 (FileReader/FileWriter)
需要按行读取文本
字符流 或 Hutool readUtf8Lines
操作任意文件(通用复制)
字节流
IO 流使用三步骤 1 2 3 4 ① 创建流对象(与文件建立连接) ② 调用 read/write 方法操作数据 ③ 调用 close 关闭流(释放资源) → 推荐用 try-with-resources 自动完成第③步
常见问题速查
问题
原因
解决
读取中文乱码
字节流逐字节读,破坏了多字节中文结构
改用字符流 ,或用字节数组一次性读够完整中文
写完数据文件为空
FileWriter 有缓冲区,未刷新
调用 flush() 或 close()
创建流时抛异常
文件路径中的文件夹不存在
先用 mkdirs() 创建文件夹
删除文件夹失败
文件夹非空
先递归删除内容,再删文件夹;或用 Hutool FileUtil.del()
类加载器 · 反射 · 注解 · 重点总结
整理自:18-类加载器-反射笔记.md + 进阶12-Properties-反射.xmind
一、类加载器 1.1 类加载时机(触发条件)
触发情况
示例
创建类的实例
new Student()
访问/修改类的静态变量
Student.count = 10
调用类的静态方法
Math.random()
使用反射方式操作类
Class.forName("...")
初始化某个类的子类
父类会随之加载
直接运行主类
java MainClass
1.2 三种类加载器 负责把.class文件加载到内存的方法区中.将class文件加载到内存生成Class对象
类加载器
别称
职责
获取结果
BootstrapClassLoader
根/引导类加载器
加载 Java 核心类库 (String、System 等),C++ 编写
null(Java 无法获取)
ExtClassLoader (JDK9+ PlatformClassLoader)
扩展类加载器
加载 JRE 扩展目录中的 jar 包(如 DNSNameService)
可获取对象
AppClassLoader
系统/应用类加载器
加载我们自定义的类 和第三方 jar 包
可获取对象
1.3 获取类加载器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ClassLoader app = ClassLoader.getSystemClassLoader();ClassLoader app2 = MyClass.class.getClassLoader(); ClassLoader ext = app.getParent();ClassLoader boot = String.class.getClassLoader(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("config.properties" );
1.4 双亲委派机制
项目
说明
目的
确保同一个类最多只被加载一次 ,保证安全性
过程
子加载器收到请求 → 先委托父加载器 处理 → 父加载器无法处理才由子加载器自己加载
示例
AppClassLoader 加载 Person 类时,String 字段委托 → ExtClassLoader → BootstrapClassLoader 处理
二、反射 2.1 核心概念
项目
说明
定义
在代码运行期间 ,动态获取类的各种成分 (构造方法、成员变量、成员方法)并执行的技术
核心价值
写通用框架 ;运行时动态加载类;绕过编译期限制(如泛型擦除)
.class 文件的唯一性
一个类只会被加载一次 ,对应的 Class 对象唯一
2.2 反射涉及的四个核心类
类
代表
说明
Class<T>
类的字节码文件
反射的入口 ,描述一个类的所有信息
Constructor<T>
构造方法
从 Class 中抽取的构造器对象
Field
成员变量(属性)
从 Class 中抽取的字段对象
Method
成员方法
从 Class 中抽取的方法对象
2.3 三种方式获取字节码文件(⭐ 常考)
方式
代码
特点
方式1 :getClass()
对象.getClass()
必须先有对象才能调用
方式2 :.class 属性
类名.class
编译期确定,需要导入类
方式3 :Class.forName()(推荐)
Class.forName("全限定类名")
类名可写在配置文件 中,最灵活
⚠️ 三种方式获取的是同一个 Class 对象(== 比较为 true),同一个类只有唯一一份字节码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Test06 { public static void main (String[] args) throws Exception { Class a1 = Class.forName("com.zxq._06_获取字节码文件对象.A" ); Class b1 = Class.forName("com.zxq._06_获取字节码文件对象.B" ); Class a2 = A.class; Class a3 = new A ().getClass(); Class a4 = new A ().getClass(); System.out.println(a1 == a2); System.out.println(a3 == a2); System.out.println(a3 == a4); System.out.println(a1 == b1); } } public class A {} public class B {}
三、反射构造方法 3.1 获取构造方法的方法
方法
说明
getConstructors()
获取所有 public 构造方法数组
getDeclaredConstructors()
获取所有权限 的构造方法数组(含 private)
getConstructor(Class... types)
按参数类型获取指定 public 构造方法
getDeclaredConstructor(Class... types)
按参数类型获取指定任意权限 构造方法
3.2 执行构造方法
操作
方法
说明
执行构造方法(new 对象)
constructor.newInstance(参数...)
相当于 new 类名(参数)
暴力反射(访问 private)
constructor.setAccessible(true)
执行前必须调用 ,忽略权限检查
快捷创建对象(有 public 空参构造时)
clazz.newInstance()
JDK9 后推荐用 clazz.getDeclaredConstructor().newInstance()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Class clazz = C.class;Constructor c1 = clazz.getConstructor();Object obj1 = c1.newInstance(); Constructor c2 = clazz.getConstructor(String.class, int .class);Object obj2 = c2.newInstance("张三" , 18 ); Constructor c3 = clazz.getDeclaredConstructor(int .class);c3.setAccessible(true ); Object obj3 = c3.newInstance(20 );
四、反射成员方法 4.1 获取方法的方法
方法
说明
getMethods()
获取所有 public 方法(含父类继承 的方法)
getDeclaredMethods()
获取本类所有权限 的方法(不含继承)
getMethod(String name, Class... types)
按名称+参数类型获取指定 public 方法
getDeclaredMethod(String name, Class... types)
按名称+参数类型获取指定任意权限 方法
4.2 执行方法
操作
方法
说明
执行方法
method.invoke(对象, 参数...)
返回方法的返回值 (void 方法返回 null)
暴力反射
method.setAccessible(true)
访问 private 方法前必须调用
获取方法信息
method.getName() / getReturnType() / getParameterTypes()
反向获取方法元信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Class clazz = D.class;Object obj = clazz.newInstance();Method m1 = clazz.getMethod("show2" , int .class);Object result = m1.invoke(obj, 123 ); System.out.println("返回值:" + result); Method m2 = clazz.getMethod("show2" , int .class, int .class);m2.invoke(obj, 2 , 5 ); Method m3 = clazz.getDeclaredMethod("show1" , double .class);m3.setAccessible(true ); m3.invoke(obj, 2.2 );
五、反射成员变量 5.1 获取字段的方法
方法
说明
getFields()
获取所有 public 字段(含父类)
getDeclaredFields()
获取本类所有权限 的字段
getField(String name)
按名称获取指定 public 字段
getDeclaredField(String name)
按名称获取指定任意权限 字段
5.2 操作字段值
操作
方法
说明
暴力反射
field.setAccessible(true)
访问 private 字段前必须调用
给字段赋值
field.set(对象, 值)
相当于 对象.字段 = 值
获取字段值
field.get(对象)
相当于 对象.字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Class clazz = E.class;Object obj = clazz.newInstance();Field nameField = clazz.getDeclaredField("name" );nameField.setAccessible(true ); nameField.set(obj, "张三" ); System.out.println(nameField.get(obj)); Field[] fields = clazz.getDeclaredFields(); for (Field f : fields) { f.setAccessible(true ); System.out.println(f.getName() + " = " + f.get(obj)); }
六、反射方法汇总速查 Class 类的核心方法一览
方法
说明
getSimpleName()
获取简单类名(不含包名)
getName()
获取完整类名(含包名)
newInstance()
快捷创建对象(需有 public 空参构造)
getConstructors() / getDeclaredConstructors()
获取所有构造方法
getConstructor(...) / getDeclaredConstructor(...)
获取指定构造方法
getMethods() / getDeclaredMethods()
获取所有成员方法
getMethod(...) / getDeclaredMethod(...)
获取指定成员方法
getFields() / getDeclaredFields()
获取所有成员变量
getField(...) / getDeclaredField(...)
获取指定成员变量
getAnnotation(注解.class)
获取类上的指定注解
三大成员操作规律对比
成员
获取全部(public)
获取全部(含 private)
执行/操作
暴力反射
构造方法
getConstructors()
getDeclaredConstructors()
c.newInstance(参数)
c.setAccessible(true)
成员方法
getMethods()
getDeclaredMethods()
m.invoke(对象, 参数)
m.setAccessible(true)
成员变量
getFields()
getDeclaredFields()
f.set(对象,值) / f.get(对象)
f.setAccessible(true)
七、反射实战案例(框架设计模式) 1 2 3 4 className =com.example.UserService methodName =sayHello param =灵星智能商城
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyFramework { private MyFramework () {} public static Object start () throws Exception { InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("my.conf" ); Properties p = new Properties (); p.load(new InputStreamReader (in, "UTF-8" )); Class clazz = Class.forName(p.getProperty("className" )); Method method = clazz.getMethod(p.getProperty("methodName" ), String.class); return method.invoke(clazz.newInstance(), p.getProperty("param" )); } } Object result = MyFramework.start();
框架设计思路 :把”要调用哪个类的哪个方法”写在配置文件 里,框架通过反射动态加载执行。用户只需修改配置,不需要重新编译代码。
八、反射的其他应用 8 绕过泛型检查 1 2 3 4 5 6 7 8 9 List<Integer> list = new ArrayList <>(); list.add(123 ); Method addMethod = list.getClass().getMethod("add" , Object.class);addMethod.invoke(list, "abc" ); System.out.println(list);
九、注解(Annotation) 9.1 核心概念
项目
说明
定义
Java 中一种特殊的引用数据类型 ,是一个标记,可以给类/方法/属性传递信息
本质
编译后会被解析成一个接口 ,属性会被当成抽象方法处理;所有注解默认继承 Annotation
关键字
@interface
用途
框架配置(Spring)、编译检查(@Override)、代码分析(反射读取)
9.2 自定义注解语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { String value () ; int age () default 18 ; String[] hobbies() default {}; } @MyAnnotation(value = "张三", age = 20, hobbies = {"Java", "篮球"}) public class User { }@MyAnnotation("李四") public void method () { }
9.3 两大元注解
元注解
作用
常用参数
@Target
限制注解可以标注的位置
TYPE(类/接口)METHOD(方法)FIELD(字段)PARAMETER(参数)
@Retention
限制注解的生命周期
SOURCE(源码)CLASS(字节码)RUNTIME(运行时,反射可读,最常用 )
9.4 注解解析(反射读取注解) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Class clazz = User.class;if (clazz.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation anno = clazz.getAnnotation(MyAnnotation.class); System.out.println(anno.value()); System.out.println(anno.age()); System.out.println(Arrays.toString(anno.hobbies())); } Method method = clazz.getMethod("doWork" );if (method.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation anno = method.getAnnotation(MyAnnotation.class); System.out.println(anno.value()); }
9.5 注解属性使用规则
规则
说明
属性类型限制
只支持:8 种基本类型、包装类、String、枚举、注解类型、Class,以及这些类型的一维数组
省略 value
当且仅当只有一个名为 value 的属性 需要赋值时,可省略属性名
数组单值简写
数组属性只有一个值时,可省略 {}
默认值
有 default 的属性,使用注解时可以不赋值
十、综合对比速查 反射三步骤口诀 1 2 3 4 ① 获取字节码文件对象(Class.forName("全限定类名") 最推荐) ② 面向 Class 对象获取成分(构造方法/成员方法/成员变量) ③ 面向成分对象执行(newInstance / invoke / set / get) → 访问 private 成员,第②步之后先调用 setAccessible(true)
getXxx vs getDeclaredXxx
方法前缀
访问范围
含父类继承
getXxx()
仅 public 成员
✅ 含父类
getDeclaredXxx()
所有权限 (含 private)
❌ 仅本类