Java学习笔记04
Java学习笔记04
Lynx FrostJava 反射 Reflection
一、什么是反射
反射机制:程序运行期间,可以获取类的完整信息、操作对象(构造方法、成员变量、成员方法)。
正常正向调用:new 对象() → 编译期确定类型
反射逆向操作:运行期才解析类信息,动态创建对象、调用方法。
包:
java.lang.reflect.*;入口:Class类
二、反射核心作用
- 运行时获取类名、父类、接口、属性、方法、构造器
- 运行时动态创建类实例
- 运行时调用任意对象的方法、读写成员变量(包括私有 private)
- 主流框架底层原理:Spring IoC、MyBatis、注解、JUnit 都大量使用反射
三、获取 Class 对象的 4 种方式
class Student{} |
一个类在 JVM 中只会加载一次,4 种方式拿到的是同一个 Class 对象。
四、Class 五大核心组件
通过 Class 可以获取:
Constructor:构造方法Method:成员方法Field:成员变量Annotation:注解Superclass/Interface:父类、实现接口
五、全套代码 Demo
准备实体类
public class Student { |
Demo1:反射通过构造器创建对象
import java.lang.reflect.Constructor; |
getConstructor()只能获取public 构造器
getDeclaredConstructor()可以获取所有权限(private/public)构造器
Demo2:反射操作成员变量 Field(突破私有)
import java.lang.reflect.Field; |
Demo3:反射调用方法 Method
import java.lang.reflect.Method; |
六、关键 API 区分
| 方法 | 作用 | 范围 |
|---|---|---|
| getConstructor / getMethod / getField | 获取public | 只能访问公共成员 |
| getDeclaredConstructor / getDeclaredMethod / getDeclaredField | 获取所有权限(public/private/protected) | 包含私有,但默认不能访问 |
setAccessible(true)
俗称:暴力反射;作用:取消 Java 访问权限检查,可以读写私有属性、调用私有方法。
安全管理器可以禁止暴力反射。
七、反射优缺点
优点:
- 动态性极高,框架实现核心;运行期灵活创建对象、调用方法
- 解耦,无需编译期硬编码 new 对象
缺点:
- 性能较差:绕过编译优化,需要解析 Class 元数据
- 破坏封装:暴力反射可以操作私有成员,违背面向对象封装
- 代码可读性差、复杂;编译期无法校验,异常只能运行时抛出
八、常见问题
Q1:Class.forName () 作用?
- 加载类,执行静态代码块;
- JDBC 经典用法:
Class.forName("com.mysql.cj.jdbc.Driver"),驱动类自动注册。
Q2:newInstance () 过时了怎么办?
JDK9 之后 clazz.newInstance() 被废弃;推荐获取无参 Constructor 再调用 newInstance ()
Q3:反射和注解怎么配合?
反射可以读取类 / 方法上注解信息,很多框架(Spring)依靠【反射 + 注解】实现功能。
Q4:什么是暴力反射?
setAccessible(true),跳过权限校验访问私有成员。
九、反射典型应用场景
- JDBC 加载数据库驱动
- Spring:IOC 容器反射创建 Bean 对象、依赖注入
- MyBatis:反射实例化实体类,封装数据库结果集
- JUnit:反射执行 @Test 标记的测试方法
- 通用工具:对象属性拷贝
JDBC 全套知识点
一、JDBC 概述
定义
JDBC(Java Database Connectivity):Java 访问数据库的统一规范接口,定义了一套访问数据库的标准 API。
- 接口:
java.sql.*、javax.sql.*,由 JDK 提供 - 实现:各大数据库厂商提供数据库驱动(MySQL、Oracle、SQLServer 等),实现 JDBC 接口
核心优势
- 一套 Java 代码,更换数据库仅需更换驱动与连接参数,无需大量改业务代码
- 统一屏蔽不同数据库底层差异
核心组件
- Driver 驱动:数据库厂商实现,桥接 Java 与数据库
- DriverManager:驱动管理器,注册驱动、获取数据库连接
- Connection:数据库连接对象(会话)
- Statement / PreparedStatement / CallableStatement:执行 SQL 语句
- ResultSet:查询结果集,封装查询返回的表格数据
二、JDBC 完整执行 6 大固定步骤
- 导入数据库驱动依赖
- 注册数据库驱动
- 获取数据库连接 Connection
- 获取 SQL 执行对象 Statement/PreparedStatement
- 执行 SQL,处理结果集 ResultSet
- 关闭所有资源(ResultSet、Statement、Connection)
三、环境准备(MySQL 为例)
Maven 依赖(MySQL8.0+)
<!-- MySQL驱动 8.x --> |
MySQL5.x 驱动类:
com.mysql.jdbc.DriverMySQL8.x 驱动类:
com.mysql.cj.jdbc.Driver
连接 URL 模板
jdbc:mysql://ip:端口/数据库名?参数 |
示例:
jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8 |
四、原生 JDBC 代码 Demo
Demo1:Statement(不推荐,存在 SQL 注入)
import java.sql.*; |
Statement 致命缺点:SQL 注入
拼接字符串 SQL,恶意输入可篡改 SQL 逻辑,项目禁止使用 Statement。
Demo2:PreparedStatement(标准推荐,预编译防注入)
原理:SQL 模板预编译,使用?占位符,参数单独赋值,SQL 结构固定,杜绝注入。
import java.sql.*; |
Demo3:增删改(executeUpdate)
//新增、修改、删除统一使用 executeUpdate(),返回受影响行数 |
五、三大执行对象对比
| 对象 | 特点 | 安全性 | 适用场景 |
|---|---|---|---|
| Statement | 字符串拼接 SQL,每次重新编译 | 极易 SQL 注入 | 几乎淘汰 |
| PreparedStatement | 预编译,? 占位符,可复用 SQL | 防注入,安全 | 日常 CRUD 首选 |
| CallableStatement | 专门调用存储过程 | 安全 | 数据库存储过程调用 |
六、核心 API 详解
- Connection
createStatement():创建 StatementprepareStatement(sql):创建预编译对象setAutoCommit(false):关闭自动提交,开启事务commit():事务提交rollback():事务回滚
- PreparedStatement 常用方法
setXxx(下标,值):给?赋值,下标从1开始executeQuery():执行查询,返回 ResultSetexecuteUpdate():执行增删改,返回 int(影响行数)
- ResultSet 结果集
next():指针下移,有数据返回 true,末尾 falsegetXxx(列名/列下标):获取值,列下标从 1 开始beforeFirst():指针回到最开头
七、JDBC 事务管理(核心考点)
JDBC 默认自动提交:单条 SQL 执行完成立刻提交。
多 SQL 原子操作必须手动事务:
try{ |
八、工具类封装(开发常用,简化重复代码)
把连接、关闭资源抽取为工具类,配合 properties 配置文件
db.properties
driver=com.mysql.cj.jdbc.Driver |
JdbcUtil.java
import java.io.InputStream; |
九、常见问题与优化
- SQL 注入原因与解决方案
原因:Statement 字符串拼接;解决:强制使用 PreparedStatement 占位符预编译。
原生 JDBC 痛点
频繁创建销毁连接,性能极差
代码冗余,大量重复模板代码
解决方案:
数据库连接池(Druid、HikariCP)、MyBatis/MyBatis-Plus、JPA
驱动自动注册原理
JDBC4.0+ 无需手动Class.forName():
驱动包META-INF/services/java.sql.Driver文件记录驱动类,JVM 自动加载。
十、高频知识点
PreparedStatement 为什么防注入?
SQL 结构提前预编译固化,? 仅做参数,不会解析为 SQL 关键字,恶意输入只会当成普通字符串。
占位符下标为什么从 1 开始?
JDBC 规范定义,和数据库列下标保持一致。
事务四大特性 ACID?
原子性、一致性、隔离性、持久性。
连接池作用?
复用数据库连接,减少创建销毁开销,控制最大连接数防止数据库崩溃。
Java 正则表达式
一、基础介绍
- 定义
正则表达式(Regex):一套字符串匹配规则,用来检索、替换、截取、校验文本。
Java 正则核心包:java.util.regex
核心两个类:
Pattern:正则表达式编译对象,封装匹配规则Matcher:匹配器,执行匹配、查找、分组提取
- 使用场景
手机号 / 邮箱 / 身份证校验、日志提取、敏感词过滤、文本截取、字符串替换。
二、正则语法速查表
- 普通元字符(单字符匹配)
| 符号 | 含义 |
|---|---|
. |
匹配任意单个字符(换行\n除外) |
\d |
数字 [0-9] |
\D |
非数字 |
\s |
空白字符(空格、制表符、换行) |
\S |
非空白字符 |
\w |
单词字符 [a-z,A-Z,0-9,_] |
\W |
非单词字符 |
[] |
字符集,匹配括号内任意一个字符,[abc]匹配 a/b/c |
[^ ] |
取反,[^0-9]匹配非数字 |
- 量词(限定前面字符出现次数)
| 符号 | 含义 |
|---|---|
? |
0 次 或 1 次 |
+ |
至少 1 次(1~ 无数) |
* |
0 次及以上 |
{n} |
固定 n 次 |
{n,} |
最少 n 次 |
{n,m} |
n~m 次 |
- 边界匹配
| 符号 | 含义 |
|---|---|
^ |
字符串开头 |
$ |
字符串结尾 |
\b |
单词边界(空格、标点、首尾) |
- 分组与或
():分组,可单独捕获内容、反向引用|:或,a|b匹配 a 或者 b(?:):非捕获分组,只分组不提取
- 转义说明(Java 独有重点)
正则里\是转义符,Java 字符串中\需要写成\\
例:正则\d → Java 代码必须写成"\\d"
- 贪婪 / 非贪婪
- 默认:贪婪匹配,尽可能多匹配;
?放在量词后开启非贪婪(懒惰匹配):.*?尽可能少匹配。
三、Java 三种常用使用方式
方式 1:String 自带简便方法(日常快速校验)
boolean matches(String regex):全串严格匹配(等价于^正则$)String replaceAll(String regex,String replacement):正则全局替换String[] split(String regex):按正则分割字符串
Demo:字符串自带方法
public class StringRegexDemo { |
方式 2:Pattern + Matcher 精准查找、分组提取(最常用)
核心方法:
Pattern.compile(reg):编译正则matcher.find():查找下一个匹配片段,找到返回 truematcher.group():获取匹配到的完整内容matcher.group(n):获取第 n 个分组内容(括号分组)
Demo1:提取所有数字
import java.util.regex.Matcher; |
Demo2:分组捕获(提取邮箱用户名、域名)
public class GroupDemo { |
方式 3:替换分组内容
//隐藏手机号中间4位 |
$1代表第 1 分组内容,$2代表第 2 分组内容
四、高频实用正则模板(可直接复制使用)
//1.手机号 |
五、贪婪匹配与非贪婪匹配案例
public static void main(String[] args) { |
六、重要知识点
- matches () 和 find () 区别
matches():整串完全匹配,默认带^$;find():片段查找,只要字符串存在匹配片段就返回 true。
- 正则性能问题
- 复杂正则易出现回溯爆炸,大量文本下卡顿;
- 能用普通字符串方法就不用复杂正则。
- 分组编号规则
从左到右,左括号(顺序依次为 1、2、3;group (0) 永远代表完整匹配内容。
- 非捕获分组
(?:xxx)
只用来分组限定范围,不会占用分组编号,适合不需要提取、只做规则划分的场景。
Lambda表达式
Lambda表达式的基础语法:Java8中引入了一个新的操作符”->“ 该操作符称为箭头操作符或Lambda操作符
基础介绍
概念
Lambda 表达式是Java 8 新增的语法,本质是简洁的匿名函数,用来简化函数式接口的实现,替代臃肿的匿名内部类,把代码当成参数传递。
核心定位:行为参数化,把一段逻辑当做方法入参。
作用
简化匿名内部类写法,代码更精简;
配合流式编程
Stream,实现集合便捷遍历、筛选、排序;用于线程、回调、Comparator 比较器等场景。
语法格式
(参数列表) -> {方法体} |
->:Lambda 运算符,分为左侧参数、右侧执行体
四种简写规则(重点)
- 参数只有 1 个:小括号
()可以省略
s -> System.out.println(s); |
- 无参数:必须保留空括号
()
() -> System.out.println("无参"); |
- 参数类型可省略:编译器自动类型推断,无需声明参数类型
// 简写 |
- 方法体只有一行代码:大括号
{}、return、分号可全部省略
// 单行有返回值,省略{}和return |
Lambda 使用前提:函数式接口
- 定义
函数式接口:有且仅有一个抽象方法的接口,可以有默认方法、静态方法、Object 重写方法。 Lambda 只能绑定函数式接口,用来实现那唯一的抽象方法。
- 注解标识
@FunctionalInterface:强制校验当前接口是否为函数式接口,编译报错提醒,推荐所有函数式接口都加。
|
- Java 内置四大核心函数式接口(高频使用)
| 接口 | 抽象方法 | 作用 | 适用场景 |
|---|---|---|---|
Consumer<T>消费型 |
void accept(T t) |
接收参数,无返回值 | 遍历、打印、修改对象 |
Supplier<T>供给型 |
T get() |
无参数,返回对象 | 生成对象、随机数 |
Predicate<T>断定型 |
boolean test(T t) |
入参,返回布尔值 | 集合筛选、条件判断 |
Function<T,R>函数型 |
R apply(T t) |
T 入参,R 返回值 | 类型转换、数据处理 |
示例
// Consumer 消费 |
Lambda 三大引用(方法引用、构造器引用、数组引用)
属于 Lambda 进一步简写,当 Lambda 只是调用已有方法时使用,格式更简洁。
方法引用 3 种形式
- 对象::实例方法:Lambda 调用某个对象的普通成员方法
Consumer<String> c = str->System.out.println(str); |
- 类::静态方法:调用类静态方法
Comparator<Integer> com = (a,b)->Integer.compare(a,b); |
- 类::实例方法:参数 1 作为调用者,参数 2 作为方法入参
BiPredicate<String,String> bp = (s1,s2)->s1.equals(s2); |
构造器引用
格式:类名::new,适配 Supplier/Function
Supplier<User> sup = ()->new User(); |
数组引用
格式:类型[]::new
Function<Integer,String[]> fun = len->new String[len]; |
Lambda 变量作用域
可访问范围
- 静态变量、成员变量:可读可修改;
- 局部变量:隐式 final,Java8 之后局部变量即使不加 final,也不允许二次修改;
int num = 10; |
禁止使用
Lambda 内部不能定义与外部重名的局部变量;不允许使用this指向外部类以外对象,Lambda 没有独立 this。
Lambda 与匿名内部类区别
| 对比项 | Lambda | 匿名内部类 |
|---|---|---|
| 结构 | 只能绑定函数式接口 | 普通接口、抽象类都能用 |
| this 指向 | 外层类对象 | 匿名内部类自身 |
| 变量 | 局部变量隐式 final | 局部变量必须显式 final (JDK7) |
| 编译 | 生成 invokedynamic 动态调用 | 生成独立 class 文件 |
| 可读性 | 极简,逻辑复杂可读性差 | 结构完整,复杂逻辑清晰 |
Lambda+Stream 流式编程(最常用场景)
Lambda 最大价值就是配合 Stream 处理集合,核心中间操作 + 终止操作
常用中间操作(返回 Stream,可链式调用)
- filter (Predicate):过滤元素
- map (Function):元素转换
- sorted (Comparator):排序
- distinct ():去重
- limit (n):截取前 n 个
- skip (n):跳过前 n 个
常用终止操作(关闭流,不可继续调用)
- collect ():转为 List、Set、Map
- forEach (Consumer):遍历
- count ():统计数量
- max/min:最值
- anyMatch/allMatch/noneMatch:匹配判断
示例
List<Integer> list = Arrays.asList(1,2,3,4,5,6); |
Lambda 优缺点
优点
- 代码极度精简,消除模板代码;
- 便于流式编程,集合处理优雅;
- 延迟执行,性能在大批量集合更优。
缺点
- 调试困难,无单独类文件,堆栈不直观;
- 复杂逻辑可读性差,多层嵌套难以理解;
- 无法重载,仅支持单抽象方法接口;
- 递归实现繁琐。
高频易错点总结
- 无抽象方法、多个抽象方法的接口不能用 Lambda;
- 多行方法体必须加大括号,有返回值必须写 return;
- 局部变量一旦被 Lambda 引用,就不可二次赋值;
- Lambda 不能抛出受检异常,需要手动 try-catch;
- 不要滥用 Lambda,超长复杂逻辑优先普通方法。
Java Stream API
Stream 是 Java8 引入的流式编程,用于集合、数组等数据源的遍历、过滤、映射、聚合、排序等操作,核心思想:把集合转为流式流水线,链式调用,延迟执行,大幅简化循环代码,配合 Lambda、方法引用使用。
一、Stream 核心定位
- 用途:对集合数据做复杂计算,替代传统 for/foreach 循环;
- 特点
- 单向一次性:流只能遍历一次,消费完毕就关闭,不可重复使用;
- 惰性求值:中间操作只记录逻辑,不执行,只有终止操作触发才真正计算;
- 不修改原数据源:所有操作产生新流 / 新结果,原集合不变;
- 支持串行、并行流:
stream()串行,parallelStream()多线程并行。
- 和集合区别
- 集合:侧重存储数据;
- Stream:侧重处理数据。
二、Stream 三要素
- 数据源:集合、数组、IO 流、生成器;
- 中间操作:链式调用,返回新 Stream,惰性执行;
- 终止操作:关闭流,执行计算,返回结果 /void。
1.如何创建 Stream(数据源)
1)集合创建(最常用)
List<String> list = new ArrayList<>(); |
2)数组创建
int[] arr = {1,2,3}; |
3)静态方法 Stream.of ()
Stream<Integer> stream = Stream.of(1,2,3,4,5); |
4)无限流(生成无限序列)
// 迭代生成:从0开始,每次+2 |
5)其他数据源
文件:Files.lines(Paths.get("xxx.txt"));
枚举:Enum.stream()。
2. 中间操作(Intermediate)
调用后返回 Stream,不执行计算,可链式拼接,常用分类:过滤、映射、排序、去重、截断、合并。
一.筛选与切片
| 方法 | 作用 |
|---|---|
filter(Predicate) |
过滤,保留满足条件元素 |
limit(n) |
截取前 n 个元素 |
skip(n) |
跳过前 n 个元素 |
distinct() |
去重(依赖 equals/hashCode) |
示例:
List<Integer> nums = Arrays.asList(1,2,2,3,4,5,6); |
二.映射 map 系列(元素类型转换)
- map(Function):一对一转换,每个元素映射成一个新元素
// 字符串转长度 |
- flatMap(Function):一对多,把流中每个元素拆解为流,扁平化
解决:集合嵌套、拆分字符串、拆分数组
// 拆分字符串 ["a,b","c,d"] → 平铺为 a,b,c,d |
基础类型映射:mapToInt/mapToLong/mapToDouble 作用:拆箱,避免频繁包装类,提升性能,对应IntStream/LongStream/DoubleStream。
- 排序 sorted
sorted():自然排序,元素实现 Comparable
sorted(Comparator):自定义比较器
Stream.of(3,1,2).sorted().forEach(System.out::println); |
- peek 调试
peek(Consumer):遍历查看元素,不改变流,多用于打印日志调试:
Stream.of(1,2,3).peek(System.out::println).map(x->x*2).collect(Collectors.toList()); |
3.终止操作(Terminal)
一旦调用,流关闭,触发所有中间操作执行,不可再使用流。 分为:遍历、查找匹配、规约、收集、统计。
- 遍历 forEach /forEachOrdered
forEach:无序遍历,并行流乱序;forEachOrdered:保证顺序遍历。
- 查找与匹配(返回布尔 / Optional)
| 方法 | 作用 |
|---|---|
allMatch(Predicate) |
全部匹配返回 true |
anyMatch(Predicate) |
任意一个匹配返回 true |
noneMatch(Predicate) |
全都不匹配返回 true |
findFirst() |
获取第一个元素 Optional |
findAny() |
随机获取一个,并行流效率高 |
boolean all = Stream.of(1,2,3).allMatch(x->x>0); |
- 规约 reduce(聚合计算,核心)
将流中元素反复结合,得到一个值,求和、最值、拼接。
- 单参数:
reduce(BinaryOperator)返回 Optional
Optional<Integer> sum = Stream.of(1,2,3).reduce((a,b)->a+b); |
- 双参数:
reduce(初始值, BinaryOperator)返回具体类型
Integer sum = Stream.of(1,2,3).reduce(0,Integer::sum); |
- 三参数:并行规约专用。
- 收集 collect(最常用,Collectors 工具类)
collect(Collector):把流转为集合、数组、字符串、分组、分区、统计。
(1)转为集合
List<Integer> list = stream.collect(Collectors.toList()); |
(2)转数组
Integer[] arr = stream.toArray(Integer[]::new); |
(3)拼接字符串 joining
String str = Stream.of("a","b","c").collect(Collectors.joining("-","[","]")); |
(4)统计类 Collectors
IntSummaryStatistics stat = Stream.of(1,2,3).collect(Collectors.summarizingInt(x->x)); |
快捷方法:count()、max()、min()。
(5)分组 groupingBy
List<User> users = ...; |
(6)分区 partitioningBy
按条件分为 true/false 两组
Map<Boolean,List<User>> part = users.stream() |
(7)聚合收集:求和、平均数、最值
Collectors.summingInt()、averagingInt()、maxBy()、minBy() |
三、并行流 parallelStream
开启方式
stream.parallel() // 串行流转并行 |
底层基于 Fork/Join 框架,多线程拆分合并。
注意事项
- 并行不一定更快:数据量小、简单计算,线程开销 > 计算耗时;
- 并行无序,需要顺序用
forEachOrdered; - 禁止在并行流操作非线程安全变量(ArrayList、普通成员变量会并发异常);
- 有状态操作(limit、sorted、distinct)并行效率会大幅下降。
四、Optional 配合 Stream(空指针防护)
findFirst、findAny、reduce 都会返回 Optional,避免 NPE:
Integer num = Stream.of(1,2,3).filter(x->x>10).findFirst().orElse(0); |
五、常用实操经典案例
案例 1:去重、过滤、求和
List<Integer> list = Arrays.asList(1,1,2,3,4,5); |
案例 2:对象集合分组统计
class Student{String name;Integer age;} |
案例 3:扁平化集合
List<List<Integer>> nested = Arrays.asList(Arrays.asList(1,2),Arrays.asList(3,4)); |
六、重要知识点
- 流只能用一次:中间操作后流消费完毕,重复调用抛
IllegalStateException; - 惰性求值:中间操作写了异常逻辑,不调用终止操作不会报错;
- map 与 flatMap 混淆:普通映射用 map,拆分层级用 flatMap;
- 并行流线程不安全:不要修改外部变量;
- distinct 依赖 equals,自定义对象重写 equals+hashCode;
- 基础流(IntStream)无装箱开销,大数据优先使用。
七、Java8~Java11 Stream 新增小特性
Stream.ofNullable():支持 null 数据源,null 会生成空流,防空指针;takeWhile(predicate):从头截取直到不满足条件;dropWhile(predicate):从头丢弃直到不满足条件。


