Appearance
JavaSE 基础知识学习文档
导航目录
1. Java 入门与环境搭建
1.1 Java 概述
Java 是什么:Java 是一种面向对象的编程语言,由 Sun Microsystems 公司于 1995 年推出,现在由 Oracle 公司维护。
1.1.1 Java 的核心特点
| 特点 | 说明 | 优势 |
|---|---|---|
| 跨平台性 | 一次编写,到处运行(Write Once, Run Anywhere) | 只需编写一次代码,可在任何支持 Java 的平台上运行 |
| 面向对象 | 支持封装、继承、多态等面向对象特性 | 代码结构清晰,易于维护和扩展 |
| 安全性 | 内置安全机制,如字节码验证 | 减少恶意代码的威胁 |
| 健壮性 | 强类型检查、自动内存管理(垃圾回收) | 减少运行时错误,提高程序稳定性 |
| 分布式 | 支持网络编程 | 便于开发网络应用和分布式系统 |
| 多线程 | 内置多线程支持 | 提高程序的执行效率和响应速度 |
1.1.2 Java 程序的执行过程
执行流程:
- 编写源代码:创建
.java文件,编写 Java 代码 - 编译:使用
javac编译器将源代码编译成字节码文件(.class) - 运行:通过 JVM(Java 虚拟机)解释执行字节码
小贴士:Java 的跨平台性正是因为有了 JVM,不同平台有不同的 JVM 实现,但都能解释执行相同的字节码。
1.2 JDK、JRE、JVM 的关系
三者关系:JDK 包含 JRE,JRE 包含 JVM
| 组件 | 全称 | 包含内容 | 用途 |
|---|---|---|---|
| JVM | Java Virtual Machine | 无 | 负责将字节码解释为具体平台的机器码并执行 |
| JRE | Java Runtime Environment | JVM + 核心类库 | 运行 Java 程序的必要环境 |
| JDK | Java Development Kit | JRE + 开发工具(编译器、调试器等) | 开发 Java 程序的必要工具 |
选择建议:
- 如果你只是运行 Java 程序,只需要安装 JRE
- 如果你要开发 Java 程序,必须安装 JDK
1.3 环境变量配置
配置目的:让系统能够找到 Java 相关的可执行文件,如
javac、java等
1.3.1 Windows 系统配置
步骤:
- 下载并安装 JDK(推荐 JDK 8 或 JDK 11)
- 访问 Oracle 官网 下载对应版本的 JDK
- 按照安装向导完成安装,记住安装路径
- 打开环境变量设置
- 右键点击「此电脑」→「属性」→「高级系统设置」→「环境变量」
- 配置 JAVA_HOME
- 在「系统变量」中点击「新建」
- 变量名:
JAVA_HOME - 变量值:JDK 安装目录(如
C:\Program Files\Java\jdk1.8.0_202)
- 配置 Path
- 找到「Path」变量,点击「编辑」
- 点击「新建」,添加:
%JAVA_HOME%\bin - 点击「确定」保存
- 验证配置
- 打开命令提示符(Win+R → 输入
cmd→ 回车) - 执行
java -version和javac -version - 如果显示版本信息则配置成功
- 打开命令提示符(Win+R → 输入
1.3.2 Linux/Mac 系统配置
步骤:
- 下载并安装 JDK
- 访问 Oracle 官网 下载对应版本的 JDK
- 按照安装向导完成安装,记住安装路径
- 编辑配置文件
- 打开终端
- 编辑
~/.bashrc或~/.bash_profile文件:bash# 使用 vim 编辑 vim ~/.bashrc # 或使用 nano 编辑 nano ~/.bashrc
- 添加环境变量
- 在文件末尾添加以下内容:bash
export JAVA_HOME=/path/to/jdk # 替换为实际的 JDK 安装路径 export PATH=$JAVA_HOME/bin:$PATH
- 在文件末尾添加以下内容:
- 使配置生效
- 执行:
source ~/.bashrc或source ~/.bash_profile
- 执行:
- 验证配置
- 执行
java -version和javac -version - 如果显示版本信息则配置成功
- 执行
小贴士:如何查看 JDK 安装路径?
- Windows:在命令提示符中执行
where java- Linux/Mac:在终端中执行
which java或echo $JAVA_HOME
1.4 HelloWorld 程序
第一个 Java 程序:HelloWorld 是学习任何编程语言的传统入门程序
1.4.1 编写代码
步骤:
- 创建文件:新建一个名为
HelloWorld.java的文件 - 编写代码:
Java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}1.4.2 编译和运行
操作步骤:
- 打开命令提示符/终端,进入文件所在目录
- 编译:执行
javac HelloWorld.java- 编译成功后,会生成一个
HelloWorld.class文件
- 编译成功后,会生成一个
- 运行:执行
java HelloWorld - 输出:
Hello, World!
1.4.3 代码解析
代码详解:
public class HelloWorld:定义一个公共类,类名必须与文件名一致public static void main(String[] args):主方法,是程序的入口点public:公共访问修饰符,确保方法可以被 JVM 访问static:静态方法,不需要创建对象即可调用void:无返回值main:方法名,JVM 会寻找这个方法作为程序入口String[] args:命令行参数数组,用于接收命令行传入的参数
System.out.println("Hello, World!"):输出字符串到控制台System:系统类out:标准输出流println:打印并换行的方法
小贴士:
- Java 是大小写敏感的,
Main和main是不同的- 每个语句结束后必须加分号
;- 类名通常使用驼峰命名法,首字母大写
2. 基础语法
2.1 变量与数据类型
2.1.1 变量定义
变量:变量是用来存储数据的容器,定义变量时需要指定数据类型和变量名
2.1.1.1 变量命名规则
- 只能由字母、数字、下划线(_)和美元符号($)组成
- 不能以数字开头
- 不能使用 Java 关键字(如
int、class、public等) - 建议使用驼峰命名法(如
userName、studentAge) - 变量名应具有描述性,能够清晰表达变量的用途
2.1.1.2 变量类型
Java 中的变量分为两大类:基本数据类型和引用数据类型
示例:
java
// 基本数据类型
int age = 18; // 年龄 - 整数类型
double salary = 5000.50; // 薪资 - 浮点数类型
char gender = '男'; // 性别 - 字符类型
boolean isStudent = true; // 是否是学生 - 布尔类型
// 引用数据类型
String name = "张三"; // 姓名 - 字符串类型
int[] scores = {95, 88, 92}; // 成绩数组 - 数组类型小贴士:变量在使用前必须先声明和初始化,否则会编译错误
2.1.2 数据类型
数据类型分类:Java 数据类型分为两大类:基本数据类型和引用数据类型
2.1.2.1 基本数据类型
基本数据类型:Java 有 8 种基本数据类型,用于存储简单的值,直接存值,占用固定内存
| 类型 | 占用空间 | 描述(范围/用途) | 示例 |
|---|---|---|---|
byte | 1 字节 | 整数(-128 ~ 127),节省内存 | byte b = 100; |
short | 2 字节 | 整数(-32768 ~ 32767) | short s = 1000; |
int | 4 字节 | 常用整数(-20 亿 ~ 20 亿),默认整数类型 | int i = 100000; |
long | 8 字节 | 大整数(需加 L 后缀,如 100L) | long l = 100000L; |
float | 4 字节 | 单精度浮点数(加 F 后缀,如 3.14F) | float f = 3.14f; |
double | 8 字节 | 双精度浮点数(默认小数类型,如 3.14) | double d = 3.14; |
char | 2 字节 | 单个字符(用单引号,如 'A',存 Unicode 码) | char c = 'A'; |
boolean | 1 字节 | 布尔值(true 或 false) | boolean b = true; |
示例:
java
// 整数类型
byte smallNum = 100;
short mediumNum = 1000;
int normalNum = 100000;
long bigNum = 1000000000L; // 注意加 L 后缀
// 浮点数类型
float piFloat = 3.14f; // 注意加 f 后缀
double piDouble = 3.1415926535; // 更精确
// 字符类型
char grade = 'A';
char chineseChar = '中';
// 布尔类型
boolean isPass = true;
boolean isEmpty = false;2.1.2.2 引用数据类型
引用数据类型:引用数据类型存储的是对象的引用(内存地址),而不是对象本身
- 类:如
String、Integer、ArrayList等 - 接口:如
Runnable、List、Map等 - 数组:如
int[]、String[]、Person[]等 - 枚举:如
enum类型
示例:
java
// 字符串
String message = "Hello, Java!";
// 数组
int[] numbers = {1, 2, 3, 4, 5};
String[] names = {"张三", "李四", "王五"};
// 集合(后面会详细讲解)
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();核心区别:基本类型直接存值,引用类型存对象的内存地址
2.1.3 类型转换
类型转换:在 Java 中,不同类型的数据之间可以进行转换,但需要遵循一定的规则
2.1.3.1 自动类型转换
自动类型转换:小范围类型向大范围类型转换,由编译器自动完成,也称为隐式转换
转换顺序: byte → short → int → long → float → double;char → int
示例:
java
// 自动转换
byte b = 10;
int i = b; // byte → int(自动)
double d = 3; // int → double(自动)
// char → int
char c = 'A';
int ascii = c; // 自动转换,获取字符的 ASCII 值
System.out.println(ascii); // 输出 652.1.3.2 强制类型转换
强制类型转换:大范围类型向小范围类型转换,需要显式指定转换类型,也称为显式转换
语法:目标类型 变量名 = (目标类型) 源变量;
示例:
java
// 强制转换
double pi = 3.14;
int num = (int) pi; // double → int(强制,结果3)
long l = 10000000000L;
int x = (int) l; // long → int(可能溢出)
// int → byte
int num2 = 150;
byte b = (byte) num2; // 强制转换,可能导致数据溢出
System.out.println(b); // 输出 -106(因为 byte 的取值范围是 -128~127)注意:强制类型转换可能会导致数据溢出或精度丢失,使用时需要谨慎
避免数据溢出的方法:
java
// 检查是否在目标类型范围内
int num = 150;
if (num >= Byte.MIN_VALUE && num <= Byte.MAX_VALUE) {
byte b = (byte) num;
System.out.println(b);
} else {
System.out.println("数值超出 byte 范围");
}2.2 运算符
运算符:运算符是用于执行特定操作的符号,Java 提供了多种类型的运算符
2.2.1 算术运算符
算术运算符:用于执行基本的数学运算
| 运算符 | 含义 | 示例 |
|---|---|---|
+ | 加法 | a + b |
- | 减法 | a - b |
* | 乘法 | a * b |
/ | 除法 | a / b(整数除法会截断小数) |
% | 取模(余数) | a % b |
++ | 自增 | a++(后自增)或 ++a(前自增) |
-- | 自减 | a--(后自减)或 --a(前自减) |
示例:
java
int a = 10;
int b = 3;
System.out.println(a + b); // 13
System.out.println(a - b); // 7
System.out.println(a * b); // 30
System.out.println(a / b); // 3(整数除法,小数部分被截断)
System.out.println(a % b); // 1(取模,即余数)
// 自增自减
int c = 5;
System.out.println(c++); // 5(后自增,先使用后自增)
System.out.println(++c); // 7(前自增,先自增后使用)小贴士:
- 整数除法会截断小数部分,结果仍然是整数
- 自增自减运算符既可以作为前缀(++a),也可以作为后缀(a++),它们的执行时机不同
2.2.2 赋值运算符
赋值运算符:用于给变量赋值,包括基本赋值和复合赋值
| 运算符 | 含义 | 示例 |
|---|---|---|
= | 基本赋值 | a = 10 |
+= | 加后赋值 | a += b 等价于 a = a + b |
-= | 减后赋值 | a -= b 等价于 a = a - b |
*= | 乘后赋值 | a *= b 等价于 a = a * b |
/= | 除后赋值 | a /= b 等价于 a = a / b |
%= | 取模后赋值 | a %= b 等价于 a = a % b |
示例:
java
int a = 10;
a += 5; // 等价于 a = a + 5
System.out.println(a); // 15
a -= 3; // 等价于 a = a - 3
System.out.println(a); // 12
a *= 2; // 等价于 a = a * 2
System.out.println(a); // 24
a /= 4; // 等价于 a = a / 4
System.out.println(a); // 6
a %= 5; // 等价于 a = a % 5
System.out.println(a); // 1小贴士:复合赋值运算符(如
+=、-=等)会自动处理类型转换,比单独使用运算符更简洁
2.2.3 关系运算符
关系运算符:用于比较两个值的关系,返回 boolean 类型(true 或 false)
| 运算符 | 含义 | 示例 |
|---|---|---|
== | 等于 | a == b |
!= | 不等于 | a != b |
> | 大于 | a > b |
< | 小于 | a < b |
>= | 大于等于 | a >= b |
<= | 小于等于 | a <= b |
示例:
java
int a = 10;
int b = 5;
System.out.println(a == b); // false
System.out.println(a != b); // true
System.out.println(a > b); // true
System.out.println(a < b); // false
System.out.println(a >= b); // true
System.out.println(a <= b); // false
// 关系运算符常用于条件判断
if (a > b) {
System.out.println("a 大于 b");
} else {
System.out.println("a 小于或等于 b");
}小贴士:
==用于比较值是否相等,而=用于赋值- 对于引用数据类型,
==比较的是内存地址,而不是对象内容
2.2.4 逻辑运算符
逻辑运算符:用于连接多个条件,返回 boolean 类型(true 或 false)
| 运算符 | 含义 | 特点 | 示例 |
|---|---|---|---|
&& | 短路与 | 左侧为 false 时,右侧不执行 | a > 5 && b < 10 |
|| | 短路或 | 左侧为 true 时,右侧不执行 | a > 5 || b < 10 |
! | 非 | 取反 | !a |
示例:
java
int a = 10;
int b = 5;
// 短路与:两个条件都为 true 才返回 true
System.out.println(a > 5 && b < 10); // true
System.out.println(a > 5 && b > 10); // false
// 短路或:只要有一个条件为 true 就返回 true
System.out.println(a > 5 || b > 10); // true
System.out.println(a < 5 || b > 10); // false
// 非:取反
System.out.println(!(a > 5)); // false
System.out.println(!(a < 5)); // true
// 逻辑运算符常用于复杂条件判断
if (a > 5 && b > 0 && a < 20) {
System.out.println("满足所有条件");
}小贴士:
- 短路运算符(&& 和 ||)会根据左侧条件的结果决定是否执行右侧表达式,提高效率
- 逻辑运算符的优先级:! > && > ||
2.2.5 三元运算符
三元运算符:三元运算符(条件运算符)是 Java 中唯一的三目运算符,用于简化条件判断
语法: 条件表达式 ? 表达式1 : 表达式2
含义: 如果条件表达式为 true,则执行表达式 1,否则执行表达式 2。
示例:
java
int a = 10;
int b = 5;
// 求最大值
int max = a > b ? a : b;
System.out.println("最大值:" + max); // 10
// 求最小值
int min = a < b ? a : b;
System.out.println("最小值:" + min); // 5
// 条件判断
String result = a > b ? "a 大于 b" : "a 小于等于 b";
System.out.println(result); // a 大于 b
// 嵌套使用
int x = 5;
int y = 10;
int z = 15;
int largest = x > y ? (x > z ? x : z) : (y > z ? y : z);
System.out.println("最大值:" + largest); // 15小贴士:三元运算符可以使代码更简洁,但嵌套使用时要注意可读性
2.2.6 运算符优先级
运算符优先级从高到低:
- 括号
() - 一元运算符:
++、--、! - 算术运算符:
*、/、%高于+、- - 关系运算符:
>、<、>=、<=高于==、!= - 逻辑运算符:
&&高于|| - 三元运算符
- 赋值运算符
建议: 当不确定优先级时,使用括号明确运算顺序。
2.3 流程控制
2.3.1 分支结构
分支结构用于根据条件执行不同的代码块。
2.3.1.1 if-else 语句
语法:
java
if (条件表达式) {
// 条件为 true 时执行的代码
} else {
// 条件为 false 时执行的代码
}示例:
java
int score = 75;
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 80) {
System.out.println("良好");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}2.3.1.2 switch 语句
语法:
java
switch (表达式) {
case 值1:
// 执行代码
break;
case 值2:
// 执行代码
break;
// 更多 case
default:
// 所有 case 都不匹配时执行的代码
}Java 12+ 箭头语法:
java
switch (表达式) {
case 值1 -> 执行代码;
case 值2 -> 执行代码;
// 更多 case
default -> 执行代码;
}示例:
java
int day = 3;
// 传统语法
switch (day) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
default:
System.out.println("其他");
}
// 箭头语法(Java 12+)
switch (day) {
case 1 -> System.out.println("星期一");
case 2 -> System.out.println("星期二");
case 3 -> System.out.println("星期三");
default -> System.out.println("其他");
}注意: switch 语句的表达式可以是 byte、short、int、char、String 或枚举类型。
2.3.2 循环结构
循环结构用于重复执行一段代码。
2.3.2.1 for 循环
语法:
java
for (初始化语句; 条件表达式; 步进语句) {
// 循环体
}示例:
java
// 打印 0 到 9
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
// 遍历数组
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 增强 for 循环(foreach)
for (int num : arr) {
System.out.println(num);
}2.3.2.2 while 循环
语法:
java
while (条件表达式) {
// 循环体
}示例:
java
// 打印 0 到 9
int i = 0;
while (i < 10) {
System.out.println(i);
i++;
}2.3.2.3 do-while 循环
语法:
java
do {
// 循环体
} while (条件表达式);特点: 至少执行一次循环体。
示例:
java
// 打印 0 到 9
int j = 0;
do {
System.out.println(j);
j++;
} while (j < 10);2.3.3 跳转语句
跳转语句用于控制程序的执行流程。
2.3.3.1 break 语句
作用: 跳出当前循环或 switch 语句。
示例:
java
// 跳出循环
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // 跳出循环
}
System.out.println(i);
}
// 输出:0 1 2 3 42.3.3.2 continue 语句
作用: 跳过本次循环,继续下次循环。
示例:
java
// 跳过偶数
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过本次循环
}
System.out.println(i);
}
// 输出:1 3 5 7 92.3.3.3 return 语句
作用: 结束方法的执行,并返回结果(如果有)。
示例:
java
public int add(int a, int b) {
return a + b; // 结束方法并返回结果
}2.4 数组
数组是一种用于存储相同类型数据的容器,具有固定的长度。
2.4.1 数组定义与初始化
2.4.1.1 动态初始化
动态初始化:指定数组长度,由系统分配初始值。
语法:
java
数据类型[] 数组名 = new 数据类型[长度];示例:
java
// 动态初始化
int[] arr1 = new int[5]; // 初始值为 0
double[] arr2 = new double[3]; // 初始值为 0.0
boolean[] arr3 = new boolean[2]; // 初始值为 false
String[] arr4 = new String[4]; // 初始值为 null2.4.1.2 静态初始化
静态初始化:直接指定数组元素值,长度由系统自动计算。
语法:
java
数据类型[] 数组名 = {元素1, 元素2, ..., 元素n};
// 或
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, ..., 元素n};示例:
java
// 静态初始化
int[] arr1 = {1, 2, 3, 4, 5};
String[] arr2 = new String[]{"张三", "李四", "王五"};2.4.2 数组操作
2.4.2.1 基本操作
- 获取长度:
arr.length - 访问元素:
arr[index](索引从 0 开始) - 修改元素:
arr[index] = 值
示例:
java
int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr.length); // 5
System.out.println(arr[0]); // 1
arr[0] = 10; // 修改第一个元素
System.out.println(arr[0]); // 102.4.2.2 遍历数组
- 普通 for 循环:可以获取索引
- 增强 for 循环:更简洁,无法获取索引
示例:
java
int[] arr = {1, 2, 3, 4, 5};
// 普通 for 循环
for (int i = 0; i < arr.length; i++) {
System.out.println("索引 " + i + ": " + arr[i]);
}
// 增强 for 循环
for (int num : arr) {
System.out.println(num);
}2.4.2.3 数组内存模型
数组在内存中是连续存储的,分为栈内存和堆内存:
- 栈内存:存储数组引用(变量名)
- 堆内存:存储数组元素
示例:
java
int[] arr = new int[3];arr变量存储在栈内存中,指向堆内存中的数组- 堆内存中分配了 3 个 int 类型的空间,初始值为 0
2.4.3 二维数组
二维数组是数组的数组,可以看作是表格或矩阵。
2.4.3.1 定义与初始化
语法:
java
// 动态初始化
数据类型[][] 数组名 = new 数据类型[行数][列数];
// 静态初始化
数据类型[][] 数组名 = {
{元素1, 元素2, ...},
{元素1, 元素2, ...},
...
};示例:
java
// 动态初始化
int[][] arr1 = new int[2][3]; // 2行3列
// 静态初始化
int[][] arr2 = {
{1, 2, 3},
{4, 5, 6}
};2.4.3.2 遍历二维数组
示例:
java
int[][] arr = {
{1, 2, 3},
{4, 5, 6}
};
// 双重 for 循环
for (int i = 0; i < arr.length; i++) { // 遍历行
for (int j = 0; j < arr[i].length; j++) { // 遍历列
System.out.print(arr[i][j] + " ");
}
System.out.println();
}2.4.4 数组常用算法
2.4.4.1 最大值/最小值
示例:
java
int[] arr = {5, 3, 9, 1, 7};
int max = arr[0];
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
if (arr[i] < min) {
min = arr[i];
}
}
System.out.println("最大值:" + max); // 9
System.out.println("最小值:" + min); // 12.4.4.2 数组反转
示例:
java
int[] arr = {1, 2, 3, 4, 5};
int left = 0;
int right = arr.length - 1;
while (left < right) {
// 交换元素
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
// 输出:5 4 3 2 1
for (int num : arr) {
System.out.print(num + " ");
}2.4.4.3 冒泡排序
思路: 相邻元素比较,大的往后移。
示例:
java
int[] arr = {5, 3, 9, 1, 7};
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
// 输出:1 3 5 7 9
for (int num : arr) {
System.out.print(num + " ");
}2.4.4.4 二分查找
前提: 数组必须有序。 思路: 每次取中间元素与目标值比较,缩小查找范围。
示例:
java
int[] arr = {1, 3, 5, 7, 9};
int target = 5;
int left = 0;
int right = arr.length - 1;
int index = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
index = mid;
break;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
System.out.println("目标值索引:" + index); // 22.4.5 数组工具类
Java 提供了 Arrays 类来操作数组:
常用方法:
Arrays.toString(arr):将数组转换为字符串Arrays.sort(arr):对数组排序Arrays.copyOf(arr, length):复制数组Arrays.binarySearch(arr, target):二分查找(数组必须有序)
示例:
java
import java.util.Arrays;
int[] arr = {5, 3, 9, 1, 7};
// 排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // [1, 3, 5, 7, 9]
// 复制
int[] copy = Arrays.copyOf(arr, 3);
System.out.println(Arrays.toString(copy)); // [1, 3, 5]
// 二分查找
int index = Arrays.binarySearch(arr, 5);
System.out.println(index); // 23. 方法
方法是一段可重复执行的代码块,用于实现特定功能。
3.1 方法定义与调用
3.1.1 方法定义
语法:
java
修饰符 返回值类型 方法名(参数列表) {
// 方法体
return 返回值;
}各部分说明:
- 修饰符:如 public、private、static 等
- 返回值类型:方法执行后返回的数据类型,无返回值时使用 void
- 方法名:方法的名称,遵循驼峰命名法
- 参数列表:方法接收的参数,格式为
数据类型 参数名,多个参数用逗号分隔 - 方法体:方法的具体实现
- return 语句:返回结果并结束方法执行
示例:
java
// 定义一个计算两数之和的方法
public int add(int a, int b) {
int sum = a + b;
return sum;
}
// 定义一个无返回值的方法
public void printHello() {
System.out.println("Hello, World!");
}3.1.2 方法调用
语法:
java
// 调用有返回值的方法
返回值类型 变量名 = 方法名(参数列表);
// 调用无返回值的方法
方法名(参数列表);示例:
java
// 调用有返回值的方法
int result = add(10, 20);
System.out.println(result); // 30
// 调用无返回值的方法
printHello(); // 输出 Hello, World!3.2 方法重载
方法重载是指在同一个类中,方法名相同但参数列表不同的多个方法。
重载条件:
- 方法名相同
- 参数列表不同(参数个数、类型或顺序不同)
- 返回值类型可以不同
- 修饰符可以不同
示例:
java
// 重载示例
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
public String add(String a, String b) {
return a + b;
}注意: 方法重载与返回值类型无关,只与方法名和参数列表有关。
3.3 可变参数
可变参数允许方法接收任意数量的相同类型参数。
语法:
java
修饰符 返回值类型 方法名(数据类型... 参数名) {
// 方法体
}特点:
- 可变参数本质上是一个数组
- 可变参数必须是方法的最后一个参数
- 一个方法只能有一个可变参数
示例:
java
// 计算任意个数的整数之和
public int sum(int... nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
return sum;
}
// 调用可变参数方法
sum(1, 2); // 3
sum(1, 2, 3, 4, 5); // 15
sum(); // 03.4 递归
递归是指方法调用自身的过程,用于解决具有递归结构的问题。
递归条件:
- 递归基例:递归的终止条件
- 递归步骤:将问题分解为更小的子问题
示例:
3.4.1 计算阶乘
java
/**
* 计算阶乘
* @param n 非负整数
* @return n 的阶乘
*/
public int factorial(int n) {
// 递归基例
if (n == 0 || n == 1) {
return 1;
}
// 递归步骤
return n * factorial(n - 1);
}
// 调用
int result = factorial(5); // 1203.4.2 斐波那契数列
java
/**
* 计算斐波那契数列的第 n 项
* @param n 项数
* @return 第 n 项的值
*/
public int fibonacci(int n) {
// 递归基例
if (n == 1 || n == 2) {
return 1;
}
// 递归步骤
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 调用
int result = fibonacci(5); // 5注意: 递归可能会导致栈溢出,对于大规模问题,建议使用迭代或其他算法。
3.5 方法参数传递
Java 中方法参数传递的规则:
- 基本数据类型:传递的是值的副本,修改参数不会影响原变量
- 引用数据类型:传递的是引用的副本,修改对象的属性会影响原对象
示例:
java
// 基本数据类型参数
public void modify(int a) {
a = 100;
System.out.println("方法内:" + a); // 100
}
// 引用数据类型参数
public void modifyArray(int[] arr) {
arr[0] = 100;
System.out.println("方法内:" + arr[0]); // 100
}
// 调用
int x = 10;
modify(x);
System.out.println("方法外:" + x); // 10(未改变)
int[] arr = {1, 2, 3};
modifyArray(arr);
System.out.println("方法外:" + arr[0]); // 100(已改变)3.6 方法的返回值
- 有返回值的方法:必须使用 return 语句返回一个与返回值类型匹配的值
- 无返回值的方法:可以使用 return 语句结束方法执行,也可以省略 return 语句
示例:
java
// 有返回值的方法
public int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
// 无返回值的方法
public void printArray(int[] arr) {
if (arr == null) {
return; // 提前结束方法
}
for (int num : arr) {
System.out.print(num + " ");
}
System.out.println();
}4. 面向对象编程
4.1 类与对象
- 类:是对象的模板,定义了对象的属性和行为
- 对象:是类的实例,具有类定义的属性和行为
java
// 类定义
public class Person {
// 属性
private String name;
private int age;
// 方法
public void eat() {
System.out.println(name + "在吃饭");
}
}
// 创建对象
Person person = new Person();4.2 封装
封装是指将对象的属性和行为封装起来,只对外提供公共访问方式。
- 使用
private修饰属性 - 提供
getter和setter方法访问属性
java
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 0) {
this.age = age;
}
}
}4.3 继承
继承是指子类继承父类的属性和方法,实现代码复用。
- 使用
extends关键字 - 子类可以重写父类的方法(使用
@Override注解) - 子类可以通过
super关键字调用父类的方法和构造器
java
// 父类
public class Animal {
public void eat() {
System.out.println("动物在吃东西");
}
}
// 子类
public class Dog extends Animal {
@Override
public void eat() {
super.eat(); // 调用父类方法
System.out.println("狗在吃骨头");
}
}4.4 多态
多态是指同一类型的对象在不同情况下表现出不同的行为。
- 向上转型:子类对象赋值给父类引用
- 向下转型:父类引用强制转换为子类引用(需要使用
instanceof检查)
java
Animal animal = new Dog(); // 向上转型
animal.eat(); // 调用子类的方法
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型
dog.bark(); // 调用子类特有的方法
}4.5 抽象类与接口
4.5.1 抽象类
抽象类是不能实例化的类,用于定义子类的共同行为。
- 使用
abstract关键字 - 可以包含抽象方法(没有实现)和具体方法
- 子类必须实现抽象方法
java
public abstract class Animal {
public abstract void eat(); // 抽象方法
public void sleep() { // 具体方法
System.out.println("动物在睡觉");
}
}4.5.2 接口
接口是一种特殊的抽象类,只包含抽象方法(Java 8+ 支持默认方法和静态方法)。
- 使用
interface关键字 - 类通过
implements实现接口 - 一个类可以实现多个接口
java
public interface USB {
void connect(); // 抽象方法
default void disconnect() { // 默认方法
System.out.println("断开连接");
}
static void showInfo() { // 静态方法
System.out.println("USB 设备");
}
}4.6 静态关键字
静态成员属于类,而不是对象,可以通过类名直接访问。
- 静态变量:类的所有对象共享
- 静态方法:不能访问非静态成员
- 静态代码块:类加载时执行,只执行一次
java
public class Student {
static String schoolName = "北京大学"; // 静态变量
static { // 静态代码块
System.out.println("静态代码块执行");
}
public static void showSchoolName() { // 静态方法
System.out.println(schoolName);
}
}4.7 代码块
- 静态代码块:类加载时执行,只执行一次
- 实例代码块:对象创建时执行,每次创建对象都会执行
java
public class Person {
static { // 静态代码块
System.out.println("静态代码块");
}
{
System.out.println("实例代码块");
}
public Person() {
System.out.println("构造方法");
}
}4.8 内部类
内部类是定义在另一个类内部的类,提供了更好的封装性和代码组织。
4.8.1 成员内部类(非静态内部类)
成员内部类定义在类的成员位置,可访问外部类的所有成员。
示例:
java
class Outer {
private int num = 10;
// 成员内部类
class Inner {
void print() {
System.out.println("外部类属性:" + num); // 访问外部类private成员
}
}
}
// 使用
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.print(); // 输出:外部类属性:104.8.2 静态内部类
静态内部类定义在类的成员位置,使用 static 修饰,只能访问外部类的静态成员。
示例:
java
class Outer {
static int staticNum = 20;
private int num = 10;
// 静态内部类
static class StaticInner {
void print() {
System.out.println("外部类静态属性:" + staticNum); // 只能访问静态成员
// System.out.println(num); // 报错:不能访问非静态成员
}
}
}
// 使用
Outer.StaticInner inner = new Outer.StaticInner();
inner.print(); // 输出:外部类静态属性:204.8.3 局部内部类
局部内部类定义在方法内部,只能在方法内部使用。
示例:
java
class Outer {
private int outerNum = 30;
void method() {
final int localNum = 40; // 局部变量(需final或隐式final)
// 局部内部类
class LocalInner {
void print() {
System.out.println("外部类属性:" + outerNum);
System.out.println("局部变量:" + localNum);
}
}
// 仅在当前方法内使用
LocalInner inner = new LocalInner();
inner.print();
}
}
// 使用
new Outer().method(); // 输出:外部类属性:30 局部变量:404.8.4 匿名内部类(最常用)
匿名内部类是没有类名的局部内部类,通常用于快速实现接口或继承类。
特性:
- 只能使用一次,创建对象时直接定义类体
- 隐式继承父类或实现接口
示例:
java
// 接口
interface Fly {
void fly();
}
class Bird {
// 匿名内部类实现Fly接口
Fly fly = () -> System.out.println("鸟在飞");
// 传统写法
// Fly fly = new Fly() {
// @Override
// public void fly() {
// System.out.println("鸟在飞");
// }
// };
}
// 使用
new Bird().fly.fly(); // 输出:鸟在飞5. 反射
5.1 反射概述
反射是什么:反射是一种用于解剖 class 对象的技术,通过反射可以在运行时获取类的信息并操作类的成员。
反射的核心概念:
- 万物皆对象:
- class 文件有对象 -> class 对象 -> 描述 class 对象的类叫做 Class 类
- 构造有对象 -> Constructor 对象 -> 描述 Constructor 对象的类叫做 Constructor 类
- 属性有对象 -> Field 对象 -> 描述 Field 对象的类叫做 Field 类
- 方法有对象 -> Method 对象 -> 描述 Method 对象的类叫做 Method 类
反射能做什么:
- 解剖出属性 -> 赋值取值
- 解剖出构造 -> new 对象
- 解剖出方法 -> 调用执行
5.2 获取 Class 对象
获取 Class 对象的三种方式:
- 方式 1:调用 Object 中的 getClass() 方法
- 方式 2:使用类的静态属性 .class
- 方式 3:使用 Class 类的静态方法 forName()
示例代码:
java
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 私有构造
private Person(String name){
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return name + "," + age;
}
// 私有方法
private void show(){
System.out.println("show...");
}
}
// 获取 Class 对象的三种方式
@Test
public void test01() throws Exception {
// 方式 1:调用 getClass()
Person person = new Person();
Class<? extends Person> aclass1 = person.getClass();
System.out.println(aclass1);
// 方式 2:使用 .class 属性
Class<Person> aclass2 = Person.class;
System.out.println(aclass2);
// 方式 3:使用 forName()
Class<?> aclass3 = Class.forName("com.at.b_reflect.Person");
System.out.println(aclass3);
// class 对象在内存中只有一个
System.out.println(aclass1 == aclass2); // true
}开发中最常用的方式:
Class.forName("类的全限定名"):最通用,可通过配置文件动态加载类类名.class:开发中最常用,简单直接
5.3 反射构造方法
反射构造方法:通过反射获取并使用类的构造方法创建对象
相关方法:
Constructor<?>[] getDeclaredConstructors():获取所有构造方法(包括 private)Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取指定构造方法(包括 private)T newInstance(Object... initargs):根据构造方法创建对象void setAccessible(boolean flag):解除私有权限(暴力反射)
示例代码:
java
// 获取所有构造方法
@Test
public void test01() throws Exception {
Class<Person> pClass = Person.class;
Constructor<?>[] dc = pClass.getDeclaredConstructors();
for (Constructor<?> constructor : dc) {
System.out.println(constructor);
}
}
// 使用无参构造创建对象
@Test
public void test02() throws Exception {
Class<Person> pClass = Person.class;
Constructor<Person> dc = pClass.getDeclaredConstructor();
Person person = dc.newInstance();
System.out.println(person);
}
// 使用有参构造创建对象
@Test
public void test03() throws Exception {
Class<Person> pClass = Person.class;
Constructor<Person> dc = pClass.getDeclaredConstructor(String.class, int.class);
Person person = dc.newInstance("张三", 18);
System.out.println(person);
}
// 使用私有构造创建对象(暴力反射)
@Test
public void test04() throws Exception {
Class<Person> pClass = Person.class;
Constructor<Person> dc = pClass.getDeclaredConstructor(String.class);
dc.setAccessible(true); // 解除私有权限
Person person = dc.newInstance("张三");
System.out.println(person);
}5.4 反射方法
反射方法:通过反射获取并调用类的方法
相关方法:
Method[] getDeclaredMethods():获取所有方法(包括 private)Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取指定方法Object invoke(Object obj, Object... args):执行方法
示例代码:
java
// 获取所有方法
@Test
public void test01() throws Exception {
Class<Person> personClass = Person.class;
Method[] dm = personClass.getDeclaredMethods();
for (Method method : dm) {
System.out.println(method);
}
}
// 调用公共方法
@Test
public void test02() throws Exception {
Class<Person> personClass = Person.class;
Method setName = personClass.getDeclaredMethod("setName", String.class);
// 创建对象(使用 Class 类的 newInstance() 方法,要求类有空参构造)
Person person = personClass.newInstance();
setName.invoke(person, "张三");
// 调用 getName 方法
Method getName = personClass.getDeclaredMethod("getName");
Object o = getName.invoke(person);
System.out.println(o); // 输出:张三
}
// 调用私有方法(暴力反射)
@Test
public void test03() throws Exception {
Class<Person> personClass = Person.class;
Method show = personClass.getDeclaredMethod("show");
show.setAccessible(true); // 解除私有权限
Person person = personClass.newInstance();
show.invoke(person); // 输出:show...
}5.5 反射成员变量
反射成员变量:通过反射获取并操作类的成员变量
相关方法:
Field[] getDeclaredFields():获取所有成员变量(包括 private)Field getDeclaredField(String name):获取指定成员变量Object get(Object obj):获取成员变量的值void set(Object obj, Object value):设置成员变量的值
示例代码:
java
// 获取所有成员变量
@Test
public void test01() throws Exception {
Class<Person> personClass = Person.class;
Field[] df = personClass.getDeclaredFields();
for (Field field : df) {
System.out.println(field);
}
}
// 操作私有成员变量(暴力反射)
@Test
public void test02() throws Exception {
Class<Person> personClass = Person.class;
Field name = personClass.getDeclaredField("name");
name.setAccessible(true); // 解除私有权限
Person person = personClass.newInstance();
name.set(person, "张三");
System.out.println(name.get(person)); // 输出:张三
}5.6 反射练习:编写一个小框架
反射的应用:通过反射实现一个简单的框架,根据配置文件动态加载类并执行方法
步骤:
- 创建 properties 配置文件,配置类的全限定名和方法名
- 解析配置文件,获取类名和方法名
- 使用反射加载类,获取方法并执行
示例代码:
properties
# reflect.properties
classname=com.at.b_reflect.Student
methodname=eatjava
@Test
public void test01() throws Exception {
// 1. 解析配置文件
Properties properties = new Properties();
InputStream in = Demo05Reflect.class.getClassLoader().getResourceAsStream("reflect.properties");
properties.load(in);
// 2. 获取类的全限定名和方法名
String className = properties.getProperty("classname");
String methodName = properties.getProperty("methodname");
// 3. 根据类名创建 class 对象
Class<?> aClass = Class.forName(className);
// 4. 根据 class 对象获取 method 对象
Method method = aClass.getMethod(methodName);
// 5. 创建对象并执行方法
Object obj = aClass.newInstance();
method.invoke(obj);
}反射的优势:
- 灵活性:可以在运行时动态加载类和执行方法
- 通用性:适用于各种类,无需硬编码
- 解耦:减少代码间的依赖关系
6. 注解
6.1 注解概述
注解是什么:注解是 JDK 1.5 版本引入的一种引用数据类型,与类、接口、枚举处于同一层次。
注解的作用:
- 说明:对代码进行说明,生成 API 文档
- 检查:检查代码是否符合条件,如
@Override、@FunctionalInterface - 分析:对代码进行分析,起到代替配置文件的作用
JDK 中的常用注解:
@Override:检测此方法是否为重写方法@Deprecated:标记方法已经过时,不推荐使用@SuppressWarnings:消除警告,如@SuppressWarnings("all")
示例代码:
java
public class Person {
@Deprecated
public void eat(){
System.out.println("吃饭");
}
}
@SuppressWarnings("all")
public class Demo01Annotation {
@Test
public void test01() throws Exception {
ArrayList list = new ArrayList();
}
}6.2 注解的定义与属性
定义注解:使用
@interface关键字定义注解
定义格式:
java
public @interface 注解名 {
// 属性定义
}注解属性的定义:
- 数据类型 属性名():没有默认值,使用时必须赋值
- 数据类型 属性名() default 值:有默认值,使用时可赋值可不赋值
注解属性的类型:
- 8 种基本类型
- String 类型、class 类型、枚举类型、注解类型
- 以上类型的一维数组
示例代码:
java
public @interface Book {
String bookName();
String[] author();
int price();
int count() default 10;
}6.3 注解的使用
使用注解:使用
@注解名的形式,为注解的属性赋值
使用位置:类上、属性上、构造上、方法上、参数上都可以使用
使用方式:
java
@注解名(属性名 = 属性值, 属性名 = 属性值, 属性名 = {元素1, 元素2})示例代码:
java
@Book(bookName = "金瓶梅", author = {"涛哥", "金莲"}, price = 19)
public class BookShelf {
}注解使用注意事项:
- 空注解可以直接使用
- 不同位置可以使用相同的注解,但同一位置不能使用相同的注解
- 注解中有属性时,必须为属性赋值;多个属性用逗号隔开;数组属性使用 {}
- 注解属性有默认值时,可不用重新赋值
- 注解只有一个属性且名为 value 时,使用时可以省略属性名
示例代码:
java
public @interface Book1 {
String value();
}
@Book1("金瓶梅")
public class BookShelf {
@Book1("金瓶梅")
public void show() {
}
}6.4 注解的解析
解析注解:通过反射获取注解信息并使用
相关接口:AnnotatedElement 接口,实现类包括 Class、Constructor、Field、Method 等
常用方法:
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断是否存在指定注解T getAnnotation(Class<T> annotationClass):获取指定注解
示例代码:
java
@Book(bookName = "金瓶梅", author = {"涛哥", "金莲"}, price = 19)
public class BookShelf {
}
public class Demo01Annotation {
@Test
public void test01() throws Exception {
Class<BookShelf> bookShelfClass = BookShelf.class;
boolean b = bookShelfClass.isAnnotationPresent(Book.class);
System.out.println(b);
if (b) {
Book book = bookShelfClass.getAnnotation(Book.class);
System.out.println(book.bookName());
System.out.println(Arrays.toString(book.author()));
System.out.println(book.price());
}
}
}6.5 元注解
元注解:管理注解的注解,用于控制注解的使用位置和生命周期
常用元注解:
@Target:管理注解的使用位置
ElementType.TYPE:控制注解能在类上使用ElementType.FIELD:控制注解能在属性上使用ElementType.METHOD:控制注解能在方法上使用ElementType.PARAMETER:控制注解能在参数上使用ElementType.CONSTRUCTOR:控制注解能在构造上使用
@Retention:管理注解的生命周期
RetentionPolicy.SOURCE:控制注解能在源码中出现RetentionPolicy.CLASS:控制注解能在 class 文件中出现RetentionPolicy.RUNTIME:控制注解能在内存中出现
示例代码:
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String bookName();
String[] author();
int price();
int count() default 10;
}
@Book(bookName = "金瓶梅", author = {"涛哥", "金莲"}, price = 19)
public class BookShelf {
}注意:要使注解在运行时可解析,必须使用
@Retention(RetentionPolicy.RUNTIME)
7. 异常处理
异常是程序运行过程中出现的错误,Java 提供了异常处理机制来优雅地处理这些错误。
7.1 异常类型
Java 异常体系以 Throwable 为根类,分为两大分支:
7.1.1 Error(错误)
- 表示系统级错误(如虚拟机崩溃、内存溢出),由 JVM 抛出,程序无法处理,通常不需要捕获。
- 示例:
StackOverflowError(栈溢出)、OutOfMemoryError(内存溢出)。
7.1.2 Exception(异常)
表示程序运行中可预测的错误,可通过代码处理。分为两类:
受检异常(Checked Exception)
- 编译期强制检查,必须通过
try-catch捕获或throws声明抛出,否则编译报错。 - 示例:
IOException(IO 操作错误)、ClassNotFoundException(类未找到)。
- 编译期强制检查,必须通过
非受检异常(Unchecked Exception)
- 继承自
RuntimeException,编译期不强制检查,通常由程序逻辑错误导致(如空指针、数组越界)。 - 示例:
NullPointerException(空指针)、IndexOutOfBoundsException(索引越界)。
- 继承自
7.2 异常处理方式
7.2.1 try-catch 语句
语法:
java
try {
// 可能发生异常的代码
} catch (异常类型1 变量名1) {
// 处理异常1
} catch (异常类型2 变量名2) {
// 处理异常2
} finally {
// 无论是否发生异常,都会执行的代码
}示例:
java
try {
// 可能发生异常的代码
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // 数组越界异常
} catch (ArrayIndexOutOfBoundsException e) {
// 处理异常
System.out.println("数组索引越界:" + e.getMessage());
} catch (Exception e) {
// 捕获其他异常
System.out.println("发生异常:" + e.getMessage());
} finally {
// 清理资源
System.out.println("finally 块执行");
}7.2.2 throws 关键字
语法:
java
[修饰符] 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
// 方法体
}示例:
java
public void readFile() throws IOException {
FileReader reader = new FileReader("test.txt");
// 读取文件操作
reader.close();
}7.2.3 throw 关键字
作用: 手动抛出异常。
示例:
java
public void checkAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
System.out.println("年龄是:" + age);
}7.3 自定义异常
当内置异常无法满足业务需求时,可自定义异常,通常继承 Exception(受检)或 RuntimeException(非受检)。
7.3.1 自定义步骤:
- 继承
Exception或RuntimeException; - 实现构造方法(通常重写无参和带消息的构造方法)。
7.3.2 代码示例
1. 自定义受检异常(继承 Exception)
java
// 自定义受检异常:订单金额非法
class InvalidOrderAmountException extends Exception {
// 无参构造
public InvalidOrderAmountException() {
super();
}
// 带错误消息的构造
public InvalidOrderAmountException(String message) {
super(message); // 调用父类构造传递消息
}
}2. 自定义非受检异常(继承 RuntimeException)
java
// 自定义非受检异常:用户未登录
class UserNotLoginException extends RuntimeException {
public UserNotLoginException() {
super();
}
public UserNotLoginException(String message) {
super(message);
}
}3. 使用自定义异常
java
public class OrderService {
// 检查订单金额(抛受检异常,必须声明)
public void checkAmount(double amount) throws InvalidOrderAmountException {
if (amount <= 0) {
throw new InvalidOrderAmountException("订单金额必须大于0:" + amount);
}
}
// 检查用户登录状态(抛非受检异常,无需声明)
public void checkLoginStatus(boolean isLogin) {
if (!isLogin) {
throw new UserNotLoginException("用户未登录,无法下单");
}
}
public static void main(String[] args) {
OrderService service = new OrderService();
// 处理受检异常(必须try-catch)
try {
service.checkAmount(-100);
} catch (InvalidOrderAmountException e) {
System.out.println("错误:" + e.getMessage());
}
// 处理非受检异常(可选try-catch)
try {
service.checkLoginStatus(false);
} catch (UserNotLoginException e) {
System.out.println("错误:" + e.getMessage());
}
}
}7.4 异常处理最佳实践
- 尽量捕获具体的异常,而不是捕获所有异常(
Exception) - 不要吞噬异常,至少要记录异常信息
- 使用 finally 块清理资源,或使用 try-with-resources 语句
- 对于运行时异常,应该在代码中预防,而不是依赖异常处理
- 合理使用自定义异常,使异常信息更具语义
try-with-resources 语句(Java 7+):
java
// 自动关闭资源
try (FileReader reader = new FileReader("test.txt")) {
// 读取文件操作
} catch (IOException e) {
System.out.println("IO 异常:" + e.getMessage());
}
// 不需要手动关闭资源,try-with-resources 会自动处理8. API 类
API(Application Programming Interface)是 Java 提供的预定义类和接口,用于简化开发。
8.1 泛型
泛型是 Java 1.5 引入的特性,用于在编译时检查类型安全,避免运行时类型转换错误。
8.1.1 泛型通配符
Java 泛型通配符(?)用于表示未知类型,解决泛型中类型参数的灵活性问题,主要用于泛型类/接口的引用、方法参数等场景,常见形式有:无界通配符(?)、上界通配符(? extends T)、下界通配符(? super T)。
| 通配符形式 | 含义 | 典型用途 | 读写限制 |
|---|---|---|---|
? | 任意类型 | 仅读取(通用操作) | 可读(Object),几乎不可写 |
? extends T | T 或 T 的子类 | 读取元素 | 可读(T),不可写(除 null) |
? super T | T 或 T 的父类 | 添加元素 | 可写(T 及其子类),读取为 Object |
8.2 Object 类
Object 是所有类的父类,提供了一些通用方法:
8.2.1 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
toString() | 返回对象的字符串表示 | System.out.println(obj.toString()) |
equals(Object obj) | 比较对象是否相等 | obj1.equals(obj2) |
hashCode() | 返回对象的哈希值 | obj.hashCode() |
getClass() | 获取对象的运行时类 | obj.getClass() |
finalize() | 对象被垃圾回收前调用 | 不推荐使用 |
wait() | 使线程等待 | 多线程中使用 |
notify() | 唤醒等待的线程 | 多线程中使用 |
notifyAll() | 唤醒所有等待的线程 | 多线程中使用 |
示例:
java
// 重写 toString() 方法
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
// 重写 equals() 方法
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
// 重写 hashCode() 方法
@Override
public int hashCode() {
return Objects.hash(name, age);
}8.3 String 类
String 是不可变的字符序列,位于 java.lang 包中。
8.3.1 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
equals(String str) | 比较字符串内容 | "abc".equals("def") |
length() | 返回字符串长度 | "abc".length() |
substring(int beginIndex) | 从指定位置截取子字符串 | "Hello".substring(2) |
substring(int beginIndex, int endIndex) | 截取指定范围的子字符串 | "Hello".substring(1, 4) |
split(String regex) | 根据正则表达式分割字符串 | "a,b,c".split(",") |
trim() | 去除首尾空白字符 | " Hello ".trim() |
toLowerCase() | 转换为小写 | "HELLO".toLowerCase() |
toUpperCase() | 转换为大写 | "hello".toUpperCase() |
startsWith(String prefix) | 判断是否以指定前缀开头 | "Hello".startsWith("He") |
endsWith(String suffix) | 判断是否以指定后缀结尾 | "Hello".endsWith("lo") |
indexOf(String str) | 查找子字符串首次出现的位置 | "Hello".indexOf("l") |
lastIndexOf(String str) | 查找子字符串最后出现的位置 | "Hello".lastIndexOf("l") |
replace(String oldStr, String newStr) | 替换子字符串 | "Hello".replace("l", "L") |
示例:
java
String str = "Hello, World!";
System.out.println(str.length()); // 13
System.out.println(str.substring(0, 5)); // Hello
System.out.println(str.split(", ")[1]); // World!
System.out.println(str.startsWith("Hello")); // true
System.out.println(str.toUpperCase()); // HELLO, WORLD!8.3.2 String 的不可变性
不可变性:String 对象创建后内容无法修改,任何修改操作(如拼接、替换)都会生成新的 String 对象,原对象不变。
示例:
java
String s = "a";
s += "b"; // 实际生成新对象 "ab",原 "a" 仍存在8.4 StringBuffer 类
StringBuffer 是可变的字符序列,用于高效拼接字符串,位于 java.lang 包中,线程安全。
8.4.1 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
append(String str) | 追加字符串 | sb.append("World") |
append(char c) | 追加字符 | sb.append('!') |
append(int i) | 追加整数 | sb.append(123) |
insert(int offset, String str) | 在指定位置插入字符串 | sb.insert(5, ", ") |
delete(int start, int end) | 删除指定范围的字符 | sb.delete(5, 7) |
reverse() | 反转字符串 | sb.reverse() |
toString() | 转换为 String | sb.toString() |
示例:
java
StringBuffer sb = new StringBuffer("a");
sb.append("b"); // 直接在原对象上追加,结果为 "ab"
System.out.println(sb.toString()); // ab8.5 StringBuilder 类
StringBuilder 是可变的字符序列,用于高效拼接字符串,位于 java.lang 包中,非线程安全。
8.5.1 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
append(String str) | 追加字符串 | sb.append("World") |
append(char c) | 追加字符 | sb.append('!') |
append(int i) | 追加整数 | sb.append(123) |
insert(int offset, String str) | 在指定位置插入字符串 | sb.insert(5, ", ") |
delete(int start, int end) | 删除指定范围的字符 | sb.delete(5, 7) |
reverse() | 反转字符串 | sb.reverse() |
toString() | 转换为 String | sb.toString() |
示例:
java
StringBuilder sb = new StringBuilder("Hello");
sb.append(", ");
sb.append("World");
sb.append("!");
System.out.println(sb.toString()); // Hello, World!
sb.reverse();
System.out.println(sb.toString()); // !dlroW ,olleH8.6 BigDecimal 类
BigDecimal 用于精确的小数计算,位于 java.math 包中。
8.6.1 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
add(BigDecimal augend) | 加法 | bd1.add(bd2) |
subtract(BigDecimal subtrahend) | 减法 | bd1.subtract(bd2) |
multiply(BigDecimal multiplicand) | 乘法 | bd1.multiply(bd2) |
divide(BigDecimal divisor, int scale, RoundingMode roundingMode) | 除法 | bd1.divide(bd2, 2, RoundingMode.HALF_UP) |
compareTo(BigDecimal val) | 比较大小 | bd1.compareTo(bd2) |
toString() | 转换为字符串 | bd.toString() |
示例:
java
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
// 加法
BigDecimal sum = bd1.add(bd2);
System.out.println(sum); // 0.3
// 除法
BigDecimal divide = bd1.divide(bd2, 2, RoundingMode.HALF_UP);
System.out.println(divide); // 0.506.5 日期时间类
6.5.1 传统日期时间类
- Date:表示特定的瞬间,位于
java.util包中 - Calendar:日历类,用于操作日期时间,位于
java.util包中
示例:
java
// Date
Date date = new Date();
System.out.println(date); // 当前时间
// Calendar
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1; // 月份从 0 开始
int day = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(year + "-" + month + "-" + day);6.5.2 Java 8+ 日期时间类
Java 8 引入了新的日期时间 API,位于 java.time 包中:
- LocalDate:日期(年、月、日)
- LocalTime:时间(时、分、秒)
- LocalDateTime:日期时间
- DateTimeFormatter:日期时间格式化
- Duration:时间间隔
- Period:日期间隔
示例:
java
// LocalDate
LocalDate today = LocalDate.now();
System.out.println(today); // 2024-01-01
// LocalTime
LocalTime time = LocalTime.now();
System.out.println(time); // 12:34:56
// LocalDateTime
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime); // 2024-01-01T12:34:56
// 格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = dateTime.format(formatter);
System.out.println(formatted); // 2024-01-01 12:34:56
// 解析
LocalDateTime parsed = LocalDateTime.parse("2024-01-01 12:34:56", formatter);
System.out.println(parsed); // 2024-01-01T12:34:566.6 包装类
包装类是基本数据类型的引用类型版本,位于 java.lang 包中:
| 基本类型 | 包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
6.6.1 自动装箱与拆箱
- 自动装箱:基本类型 → 包装类
- 自动拆箱:包装类 → 基本类型
示例:
java
// 自动装箱
Integer i = 100; // 相当于 Integer i = Integer.valueOf(100);
// 自动拆箱
int j = i; // 相当于 int j = i.intValue();
// 比较
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true(缓存)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false(超出缓存范围)
System.out.println(c.equals(d)); // true(推荐使用 equals 比较)6.6.2 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
parseXxx(String s) | 解析字符串为基本类型 | Integer.parseInt("123") |
valueOf(String s) | 解析字符串为包装类 | Integer.valueOf("123") |
toString() | 转换为字符串 | i.toString() |
compareTo(Xxx other) | 比较大小 | i.compareTo(j) |
示例:
java
// 解析字符串
int num = Integer.parseInt("123");
System.out.println(num); // 123
// 转换为字符串
String str = Integer.toString(456);
System.out.println(str); // "456"
// 比较大小
Integer a = 100;
Integer b = 200;
System.out.println(a.compareTo(b)); // -1(a < b)9. 集合
集合是 Java 中用于存储和管理对象的容器,位于 java.util 包中。
9.1 集合框架概述
Java 集合框架分为两大类:
9.1.1 单列集合(Collection 接口)
- 特点:存储单个元素(如
String、Integer等),元素之间是独立的。 - 核心子接口:
List、Set
9.1.2 多列集合(Map 接口)
- 特点:存储键值对(
key-value),通过key映射value,key不可重复(重复则覆盖),value可重复。
9.2 Collection 接口
Collection 是单列集合的顶级接口,定义了所有单列集合的通用方法:
9.2.1 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
add(E e) | 添加元素 | collection.add("张三") |
remove(Object o) | 删除元素 | collection.remove("张三") |
size() | 返回元素个数 | collection.size() |
isEmpty() | 判断是否为空 | collection.isEmpty() |
contains(Object o) | 判断是否包含指定元素 | collection.contains("张三") |
clear() | 清空集合 | collection.clear() |
toArray() | 转换为数组 | collection.toArray() |
iterator() | 获取迭代器 | collection.iterator() |
9.3 List 集合
List 是有序的、可重复的集合,实现了 Collection 接口。
9.3.1 ArrayList
- 底层数据结构:数组
- 特点:查询快,增删慢
- 扩容机制:初始容量 10,每次扩容 1.5 倍
- 线程安全性:非线程安全
示例:
java
// 创建 ArrayList
List<String> list = new ArrayList<>();
// 添加元素
list.add("张三");
list.add("李四");
list.add("王五");
// 访问元素
System.out.println(list.get(0)); // 张三
// 修改元素
list.set(0, "赵六");
// 删除元素
list.remove(1); // 删除索引为 1 的元素
// 遍历元素
for (String name : list) {
System.out.println(name);
}9.3.2 LinkedList
- 底层数据结构:双向链表
- 特点:查询慢,增删快
- 线程安全性:非线程安全
- 特有方法:
addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()
示例:
java
// 创建 LinkedList
LinkedList<String> list = new LinkedList<>();
// 添加元素
list.add("张三");
list.addFirst("李四"); // 添加到开头
list.addLast("王五"); // 添加到末尾
// 访问元素
System.out.println(list.getFirst()); // 李四
System.out.println(list.getLast()); // 王五
// 删除元素
list.removeFirst(); // 删除第一个元素
list.removeLast(); // 删除最后一个元素9.4 Set 集合
Set 是无序的、不可重复的集合,实现了 Collection 接口。
9.4.1 HashSet
- 底层数据结构:哈希表(数组 + 链表/红黑树)
- 特点:无序,不可重复
- 去重原理:先比较哈希值,再比较内容(使用
equals()方法) - 线程安全性:非线程安全
- 允许 null 值:是
示例:
java
// 创建 HashSet
Set<String> set = new HashSet<>();
// 添加元素
set.add("张三");
set.add("李四");
set.add("张三"); // 重复元素,不会添加
// 遍历元素
for (String name : set) {
System.out.println(name);
}
// 判断是否包含
System.out.println(set.contains("李四")); // true9.4.2 LinkedHashSet
- 底层数据结构:哈希表 + 双向链表
- 特点:有序(插入顺序),不可重复
- 线程安全性:非线程安全
示例:
java
// 创建 LinkedHashSet
Set<String> set = new LinkedHashSet<>();
// 添加元素
set.add("张三");
set.add("李四");
set.add("王五");
// 遍历元素(保持插入顺序)
for (String name : set) {
System.out.println(name); // 张三、李四、王五
}9.4.3 TreeSet
- 底层数据结构:红黑树
- 特点:可排序,不可重复
- 排序方式:自然排序或自定义排序(实现
Comparable接口或使用Comparator) - 线程安全性:非线程安全
示例:
java
// 创建 TreeSet(自然排序)
Set<Integer> set = new TreeSet<>();
// 添加元素
set.add(5);
set.add(1);
set.add(3);
set.add(2);
// 遍历元素(自动排序)
for (Integer num : set) {
System.out.println(num); // 1、2、3、5
}
// 自定义排序
Set<String> set2 = new TreeSet<>((s1, s2) -> s2.compareTo(s1)); // 降序
set2.add("张三");
set2.add("李四");
set2.add("王五");
for (String name : set2) {
System.out.println(name); // 王五、李四、张三
}9.5 Map 集合
Map 是双列集合,存储键值对(key-value),键唯一,值可重复。
9.5.1 HashMap
- 底层数据结构:哈希表(数组 + 链表/红黑树)
- 特点:无序,键唯一,值可重复
- 线程安全性:非线程安全
- 允许 null 键和 null 值:是
- 扩容机制:初始容量 16,负载因子 0.75,每次扩容 2 倍
示例:
java
// 创建 HashMap
Map<String, Integer> map = new HashMap<>();
// 添加元素
map.put("张三", 18);
map.put("李四", 20);
map.put("王五", 22);
// 访问元素
System.out.println(map.get("张三")); // 18
// 修改元素
map.put("张三", 19); // 覆盖原值
// 删除元素
map.remove("李四");
// 遍历元素
// 方式1:遍历键
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
// 方式2:遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}9.5.2 LinkedHashMap
- 底层数据结构:哈希表 + 双向链表
- 特点:有序(插入顺序),键唯一,值可重复
- 线程安全性:非线程安全
示例:
java
// 创建 LinkedHashMap
Map<String, Integer> map = new LinkedHashMap<>();
// 添加元素
map.put("张三", 18);
map.put("李四", 20);
map.put("王五", 22);
// 遍历元素(保持插入顺序)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue()); // 张三、李四、王五
}9.4.3 TreeMap
- 底层数据结构:红黑树
- 特点:可排序,键唯一,值可重复
- 排序方式:自然排序或自定义排序
- 线程安全性:非线程安全
示例:
java
// 创建 TreeMap(自然排序)
Map<String, Integer> map = new TreeMap<>();
// 添加元素
map.put("张三", 18);
map.put("李四", 20);
map.put("王五", 22);
// 遍历元素(按键排序)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue()); // 李四、王五、张三(按字符串自然排序)
}9.4.4 Hashtable
- 底层数据结构:哈希表
- 特点:无序,键唯一,值可重复
- 线程安全性:线程安全(同步)
- 允许 null 键和 null 值:否
- 性能:比 HashMap 慢
示例:
java
// 创建 Hashtable
Hashtable<String, Integer> table = new Hashtable<>();
// 添加元素
table.put("张三", 18);
table.put("李四", 20);
// 遍历元素
for (Map.Entry<String, Integer> entry : table.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}9.4.5 Properties
- 底层数据结构:哈希表(继承自 Hashtable)
- 特点:键和值都是 String 类型
- 线程安全性:线程安全
- 用途:用于解析配置文件
示例:
java
// 创建 Properties
Properties prop = new Properties();
// 添加属性
prop.setProperty("name", "张三");
prop.setProperty("age", "18");
// 获取属性
System.out.println(prop.getProperty("name")); // 张三
// 加载配置文件
// prop.load(new FileInputStream("config.properties"));
// 保存配置文件
// prop.store(new FileOutputStream("config.properties"), "Configuration");9.5 迭代器
迭代器(Iterator)用于遍历集合,是一种设计模式。
9.5.1 常用方法
| 方法 | 说明 |
|---|---|
hasNext() | 判断是否有下一个元素 |
next() | 获取下一个元素 |
remove() | 删除当前元素 |
示例:
java
// 获取迭代器
Iterator<String> iterator = list.iterator();
// 遍历集合
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
// 删除元素(如果需要)
if (element.equals("李四")) {
iterator.remove();
}
}9.6 Stream 流
Stream 流是 Java 8 引入的,用于高效处理集合数据的 API。
9.6.1 常用方法
| 方法 | 说明 | 示例 |
|---|---|---|
stream() | 获取流 | list.stream() |
filter(Predicate<T> predicate) | 过滤元素 | stream.filter(s -> s.length() > 3) |
map(Function<T, R> mapper) | 转换元素 | stream.map(String::toUpperCase) |
forEach(Consumer<T> action) | 遍历元素 | stream.forEach(System.out::println) |
count() | 统计元素个数 | stream.count() |
limit(long maxSize) | 限制元素个数 | stream.limit(5) |
skip(long n) | 跳过元素 | stream.skip(2) |
sorted() | 排序 | stream.sorted() |
distinct() | 去重 | stream.distinct() |
collect(Collector<T, A, R> collector) | 收集流到集合 | stream.collect(Collectors.toList()) |
concat(Stream<T> a, Stream<T> b) | 合并流 | Stream.concat(stream1, stream2) |
示例:
java
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六", "钱七");
// 过滤长度大于 2 的名字,转换为大写,然后遍历
names.stream()
.filter(name -> name.length() > 2)
.map(String::toUpperCase)
.forEach(System.out::println);
// 统计符合条件的元素个数
long count = names.stream()
.filter(name -> name.startsWith("张"))
.count();
System.out.println("姓张的人数:" + count);
// 收集到新的集合
List<String> result = names.stream()
.filter(name -> name.length() == 2)
.collect(Collectors.toList());
System.out.println(result);7.7 方法引用
方法引用是 Lambda 表达式的简化形式,用于引用已存在的方法。
7.7.1 类型
| 类型 | 语法 | 示例 |
|---|---|---|
| 静态方法引用 | 类名::方法名 | Math::abs |
| 实例方法引用 | 对象::方法名 | System.out::println |
| 构造方法引用 | 类名::new | ArrayList::new |
| 数组引用 | 类型[]::new | String[]::new |
| 类的任意对象的实例方法引用 | 类名::方法名 | String::length |
示例:
java
// 静态方法引用
List<Integer> numbers = Arrays.asList(1, -2, 3, -4, 5);
numbers.stream()
.map(Math::abs) // 引用 Math 类的静态方法 abs
.forEach(System.out::println);
// 实例方法引用
List<String> names = Arrays.asList("张三", "李四", "王五");
names.stream()
.forEach(System.out::println); // 引用 System.out 对象的 println 方法
// 构造方法引用
List<String> list = Arrays.asList("a", "b", "c");
Set<String> set = list.stream()
.collect(Collectors.toCollection(HashSet::new)); // 引用 HashSet 的构造方法
// 类的任意对象的实例方法引用
List<String> strings = Arrays.asList("apple", "banana", "cherry");
strings.stream()
.map(String::length) // 引用 String 类的 length 方法
.forEach(System.out::println);10. IO 流
IO(Input/Output)流用于处理输入输出操作,是 Java 中用于读写数据的核心 API。
10.1 流的分类
10.1.1 按数据类型分类
- 字节流:处理字节数据(如图片、音频、视频等)
InputStream:字节输入流OutputStream:字节输出流
- 字符流:处理字符数据(如文本文件)
Reader:字符输入流Writer:字符输出流
10.1.2 按流的方向分类
- 输入流:从外部设备读取数据到程序
- 输出流:从程序写入数据到外部设备
10.1.3 按流的功能分类
- 节点流:直接与数据源或目标连接的流
- 处理流:对节点流进行包装,增强功能
10.2 字节流
10.2.1 InputStream
InputStream 是字节输入流的抽象基类,常用方法:
| 方法 | 说明 |
|---|---|
read() | 读取一个字节,返回 -1 表示到达文件末尾 |
read(byte[] b) | 读取多个字节到数组,返回实际读取的字节数 |
close() | 关闭流,释放资源 |
常用实现类:
FileInputStream:从文件读取字节ByteArrayInputStream:从字节数组读取字节BufferedInputStream:带缓冲的字节输入流
示例:使用 FileInputStream 读取文件
java
// 读取文件
try (FileInputStream fis = new FileInputStream("test.txt")) {
int byteRead;
while ((byteRead = fis.read()) != -1) {
System.out.print((char) byteRead);
}
} catch (IOException e) {
e.printStackTrace();
}10.2.2 OutputStream
OutputStream 是字节输出流的抽象基类,常用方法:
| 方法 | 说明 |
|---|---|
write(int b) | 写入一个字节 |
write(byte[] b) | 写入字节数组 |
flush() | 刷新缓冲区,将数据写入目标 |
close() | 关闭流,释放资源 |
常用实现类:
FileOutputStream:向文件写入字节ByteArrayOutputStream:向字节数组写入字节BufferedOutputStream:带缓冲的字节输出流
示例:使用 FileOutputStream 写入文件
java
// 写入文件
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String content = "Hello, IO Stream!";
fos.write(content.getBytes());
fos.flush();
} catch (IOException e) {
e.printStackTrace();
}10.3 字符流
10.3.1 Reader
Reader 是字符输入流的抽象基类,常用方法:
| 方法 | 说明 |
|---|---|
read() | 读取一个字符,返回 -1 表示到达文件末尾 |
read(char[] cbuf) | 读取多个字符到数组,返回实际读取的字符数 |
close() | 关闭流,释放资源 |
常用实现类:
FileReader:从文件读取字符BufferedReader:带缓冲的字符输入流,提供readLine()方法InputStreamReader:字节流转换为字符流的桥梁
示例:使用 BufferedReader 读取文件
java
// 读取文件
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}10.3.2 Writer
Writer 是字符输出流的抽象基类,常用方法:
| 方法 | 说明 |
|---|---|
write(int c) | 写入一个字符 |
write(char[] cbuf) | 写入字符数组 |
write(String str) | 写入字符串 |
flush() | 刷新缓冲区,将数据写入目标 |
close() | 关闭流,释放资源 |
常用实现类:
FileWriter:向文件写入字符BufferedWriter:带缓冲的字符输出流,提供newLine()方法OutputStreamWriter:字符流转换为字节流的桥梁
示例:使用 BufferedWriter 写入文件
java
// 写入文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("Hello, Character Stream!");
bw.newLine();
bw.write("This is a new line.");
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}10.4 缓冲流
缓冲流是对节点流的包装,提供缓冲功能,提高读写效率。
10.4.1 BufferedInputStream 和 BufferedOutputStream
示例:使用缓冲字节流
java
// 复制文件
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("target.txt"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}10.4.2 BufferedReader 和 BufferedWriter
示例:使用缓冲字符流
java
// 复制文件
try (BufferedReader br = new BufferedReader(new FileReader("source.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("target.txt"))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}10.5 对象流
对象流用于序列化和反序列化对象,将对象转换为字节流存储或传输,然后再恢复为对象。
10.5.1 ObjectOutputStream(序列化)
示例:序列化对象
java
// 序列化对象
class Person implements Serializable {
private String name;
private int age;
// 构造方法、getter、setter 省略
}
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
Person person = new Person("张三", 18);
oos.writeObject(person);
oos.flush();
System.out.println("对象序列化成功");
} catch (IOException e) {
e.printStackTrace();
}10.5.2 ObjectInputStream(反序列化)
示例:反序列化对象
java
// 反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person = (Person) ois.readObject();
System.out.println("姓名:" + person.getName());
System.out.println("年龄:" + person.getAge());
System.out.println("对象反序列化成功");
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}注意: 要序列化的类必须实现 Serializable 接口。
10.6 打印流
打印流用于方便地输出数据,提供了多种打印方法。
10.6.1 PrintStream
示例:使用 PrintStream
java
// 使用 PrintStream
try (PrintStream ps = new PrintStream(new FileOutputStream("output.txt"))) {
ps.println("Hello, PrintStream!");
ps.print("Age: ");
ps.println(18);
ps.printf("Name: %s, Age: %d\n", "张三", 18);
} catch (FileNotFoundException e) {
e.printStackTrace();
}10.6.2 PrintWriter
示例:使用 PrintWriter
java
// 使用 PrintWriter
try (PrintWriter pw = new PrintWriter(new FileWriter("output.txt"))) {
pw.println("Hello, PrintWriter!");
pw.print("Age: ");
pw.println(18);
pw.printf("Name: %s, Age: %d\n", "张三", 18);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}10.7 标准输入输出流
Java 提供了三个标准流:
System.in:标准输入流(字节流)System.out:标准输出流(打印流)System.err:标准错误流(打印流)
示例:从控制台读取输入
java
// 从控制台读取输入
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
System.out.print("请输入姓名:");
String name = br.readLine();
System.out.print("请输入年龄:");
int age = Integer.parseInt(br.readLine());
System.out.println("你好," + name + ",今年" + age + "岁");
} catch (IOException e) {
e.printStackTrace();
}8.8 IO 流的使用建议
- 使用 try-with-resources 语句:自动关闭流,避免资源泄漏
- 选择合适的流:根据数据类型和操作选择对应的流
- 使用缓冲流:提高读写效率
- 注意异常处理:捕获并处理 IOException
- 及时关闭流:释放系统资源
示例:综合使用 IO 流
java
// 复制文本文件
try (BufferedReader br = new BufferedReader(new FileReader("source.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("target.txt"))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
bw.flush();
System.out.println("文件复制成功");
} catch (IOException e) {
e.printStackTrace();
}11. 多线程
多线程是指在一个程序中同时运行多个线程,提高程序的执行效率和响应速度。
11.1 多线程概述
11.1.1 进程与线程
- 进程:是程序的一次执行过程,是系统进行资源分配和调度的独立单位,每个进程有自己的内存空间和系统资源。
- 线程:是进程中的一个执行单元,是 CPU 调度和执行的最小单位,一个进程可以包含多个线程,线程共享进程的内存空间和系统资源。
11.1.2 多线程的优势
- 提高程序执行效率:多个线程可以并行执行,充分利用 CPU 资源
- 改善程序响应性:主线程负责 UI 交互,子线程处理耗时操作
- 便于实现并发操作:如网络请求、文件 IO 等
11.2 线程创建方式
11.2.1 继承 Thread 类
步骤:
- 继承
Thread类 - 重写
run()方法 - 创建线程对象并调用
start()方法
示例:
java
// 继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(100); // 休眠 100 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 创建并启动线程
MyThread thread1 = new MyThread();
thread1.setName("线程1");
thread1.start();
MyThread thread2 = new MyThread();
thread2.setName("线程2");
thread2.start();11.2.2 实现 Runnable 接口
步骤:
- 实现
Runnable接口 - 重写
run()方法 - 创建
Thread对象,传入Runnable实例 - 调用
start()方法
示例:
java
// 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 创建并启动线程
Runnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable, "线程1");
thread1.start();
Thread thread2 = new Thread(runnable, "线程2");
thread2.start();11.2.3 实现 Callable 接口
步骤:
- 实现
Callable<V>接口,指定返回值类型 - 重写
call()方法 - 创建
FutureTask对象,传入Callable实例 - 创建
Thread对象,传入FutureTask实例 - 调用
start()方法 - 通过
FutureTask的get()方法获取返回值
示例:
java
// 实现 Callable 接口
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
// 创建并启动线程
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask, "计算线程");
thread.start();
// 获取返回值
try {
Integer result = futureTask.get();
System.out.println("1-100 的和:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}11.3 线程安全
线程安全是指多个线程同时访问共享资源时,不会导致数据不一致或其他异常情况。
11.3.1 同步代码块
语法:
java
synchronized (锁对象) {
// 同步代码
}示例:
java
// 共享资源
class Counter {
private int count = 0;
private Object lock = new Object(); // 锁对象
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}11.3.2 同步方法
语法:
java
synchronized 返回值类型 方法名(参数列表) {
// 同步代码
}示例:
java
class Counter {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}11.3.3 Lock 锁
Lock 是 Java 5 引入的接口,提供了比 synchronized 更灵活的锁机制。
常用实现类: ReentrantLock
示例:
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock(); // 创建锁对象
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 释放锁(必须在 finally 中执行)
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}11.4 线程状态
线程在生命周期中有以下状态:
| 状态 | 说明 |
|---|---|
| 新建(New) | 线程对象已创建,但尚未启动 |
| 就绪(Runnable) | 线程已启动,等待 CPU 调度 |
| 运行(Running) | 线程正在执行 |
| 阻塞(Blocked) | 线程等待锁或其他资源 |
| 等待(Waiting) | 线程无限期等待 |
| 超时等待(Timed Waiting) | 线程在指定时间内等待 |
| 终止(Terminated) | 线程执行完毕 |
示例:
java
Thread thread = new Thread(() -> {
try {
System.out.println("线程状态:" + Thread.currentThread().getState()); // RUNNABLE
Thread.sleep(1000); // 进入 TIMED_WAITING 状态
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("线程状态:" + thread.getState()); // NEW
thread.start();
System.out.println("线程状态:" + thread.getState()); // RUNNABLE
try {
Thread.sleep(500);
System.out.println("线程状态:" + thread.getState()); // TIMED_WAITING
thread.join(); // 等待线程结束
System.out.println("线程状态:" + thread.getState()); // TERMINATED
} catch (InterruptedException e) {
e.printStackTrace();
}11.5 线程通信
线程通信是指多个线程之间的协调与协作。
11.5.1 wait()、notify()、notifyAll() 方法
这些方法是 Object 类的方法,用于线程间的等待和唤醒。
示例:生产者-消费者模式
java
class SharedResource {
private int count = 0;
private final int MAX_COUNT = 5;
public synchronized void produce() throws InterruptedException {
while (count >= MAX_COUNT) {
wait(); // 生产满了,等待
}
count++;
System.out.println("生产了一个,当前数量:" + count);
notifyAll(); // 唤醒消费者
}
public synchronized void consume() throws InterruptedException {
while (count <= 0) {
wait(); // 没有产品,等待
}
count--;
System.out.println("消费了一个,当前数量:" + count);
notifyAll(); // 唤醒生产者
}
}
// 生产者
class Producer implements Runnable {
private SharedResource resource;
public Producer(SharedResource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
resource.produce();
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者
class Consumer implements Runnable {
private SharedResource resource;
public Consumer(SharedResource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
resource.consume();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试
SharedResource resource = new SharedResource();
Thread producerThread = new Thread(new Producer(resource));
Thread consumerThread = new Thread(new Consumer(resource));
producerThread.start();
consumerThread.start();11.6 线程池
线程池是一种线程管理机制,用于复用线程,减少线程创建和销毁的开销。
11.6.1 Executor 框架
Java 提供了 Executor 框架来管理线程池,主要接口和类:
Executor:执行任务的接口ExecutorService:扩展了Executor,提供了更多方法ThreadPoolExecutor:线程池的核心实现Executors:工具类,提供了创建线程池的静态方法
常用线程池类型:
| 线程池类型 | 说明 | 适用场景 |
|---|---|---|
FixedThreadPool | 固定大小的线程池 | 适用于负载较重的服务器 |
CachedThreadPool | 可缓存的线程池 | 适用于执行大量短期异步任务 |
SingleThreadExecutor | 单线程的线程池 | 适用于需要保证顺序执行的任务 |
ScheduledThreadPool | 定时任务线程池 | 适用于需要定期执行的任务 |
示例:使用线程池
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 创建固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("任务 " + taskId + " 由 " + Thread.currentThread().getName() + " 执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executorService.shutdown();11.6 线程安全的集合
Java 提供了线程安全的集合类,位于 java.util.concurrent 包中:
ConcurrentHashMap:线程安全的 HashMapCopyOnWriteArrayList:线程安全的 ArrayListCopyOnWriteArraySet:线程安全的 SetBlockingQueue:阻塞队列,如ArrayBlockingQueue、LinkedBlockingQueue
示例:使用 ConcurrentHashMap
java
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 多线程操作
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
map.put(Thread.currentThread().getName() + "-" + i, i);
}
};
Thread thread1 = new Thread(task, "线程1");
Thread thread2 = new Thread(task, "线程2");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Map 大小:" + map.size()); // 200011.7 多线程最佳实践
- 使用线程池:避免频繁创建和销毁线程
- 使用线程安全的集合:避免手动同步
- 最小化同步范围:只同步必要的代码块
- 使用 Lock 替代 synchronized:提供更灵活的锁机制
- 避免死锁:合理设计锁的获取顺序
- 使用 volatile 关键字:保证变量的可见性
- 使用原子类:如
AtomicInteger,提供原子操作 - 合理处理 InterruptedException:避免忽略中断异常
示例:使用原子类
java
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子操作
}
public int getCount() {
return count.get();
}
}
// 多线程测试
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终计数:" + counter.getCount()); // 200012. Lambda 表达式
Lambda 表达式是 Java 8 引入的一种函数式编程特性,用于简化代码。
12.1 Lambda 表达式语法
语法:
java
(参数列表) -> { 方法体 }特点:
- 不需要声明参数类型(类型推断)
- 当只有一个参数时,可以省略括号
- 当方法体只有一条语句时,可以省略大括号和 return 关键字
示例:
java
// 无参数,无返回值
Runnable r = () -> System.out.println("Hello, Lambda!");
// 单个参数,无返回值
Consumer<String> c = s -> System.out.println(s);
// 多个参数,有返回值
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
// 方法体有多条语句
Supplier<Integer> random = () -> {
Random rnd = new Random();
return rnd.nextInt(100);
};12.2 函数式接口
函数式接口是只包含一个抽象方法的接口,用于配合 Lambda 表达式使用。
常用函数式接口:
| 接口 | 方法 | 说明 |
|---|---|---|
Runnable | void run() | 无参数,无返回值 |
Consumer<T> | void accept(T t) | 一个参数,无返回值 |
Supplier<T> | T get() | 无参数,有返回值 |
Function<T, R> | R apply(T t) | 一个参数,有返回值 |
BiFunction<T, U, R> | R apply(T t, U u) | 两个参数,有返回值 |
Predicate<T> | boolean test(T t) | 一个参数,返回布尔值 |
示例:
java
// 使用 Predicate
Predicate<String> isLongerThan5 = s -> s.length() > 5;
System.out.println(isLongerThan5.test("Hello")); // false
System.out.println(isLongerThan5.test("Hello World")); // true
// 使用 Function
Function<Integer, String> intToString = i -> "Number: " + i;
System.out.println(intToString.apply(42)); // Number: 4212.3 Lambda 表达式的使用场景
12.3.1 线程创建
java
// 使用 Lambda 表达式创建线程
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
});
thread.start();12.3.2 集合操作
java
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六");
// 排序
names.sort((s1, s2) -> s1.compareTo(s2));
// 遍历
names.forEach(s -> System.out.println(s));
// 过滤
List<String> longNames = names.stream()
.filter(s -> s.length() > 2)
.collect(Collectors.toList());10.3.3 事件处理
java
// 假设 button 是一个按钮对象
button.addActionListener(e -> System.out.println("按钮被点击了"));10.4 方法引用
方法引用是 Lambda 表达式的简化形式,用于引用已存在的方法。
示例:
java
// 静态方法引用
List<Integer> numbers = Arrays.asList(1, -2, 3, -4, 5);
numbers.stream()
.map(Math::abs)
.forEach(System.out::println);
// 实例方法引用
List<String> names = Arrays.asList("张三", "李四", "王五");
names.forEach(System.out::println);
// 构造方法引用
List<String> list = Arrays.asList("a", "b", "c");
Set<String> set = list.stream()
.collect(Collectors.toCollection(HashSet::new));