Skip to content

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 程序的执行过程

执行流程

  1. 编写源代码:创建 .java 文件,编写 Java 代码
  2. 编译:使用 javac 编译器将源代码编译成字节码文件(.class)
  3. 运行:通过 JVM(Java 虚拟机)解释执行字节码

小贴士:Java 的跨平台性正是因为有了 JVM,不同平台有不同的 JVM 实现,但都能解释执行相同的字节码。

1.2 JDK、JRE、JVM 的关系

三者关系:JDK 包含 JRE,JRE 包含 JVM

组件全称包含内容用途
JVMJava Virtual Machine负责将字节码解释为具体平台的机器码并执行
JREJava Runtime EnvironmentJVM + 核心类库运行 Java 程序的必要环境
JDKJava Development KitJRE + 开发工具(编译器、调试器等)开发 Java 程序的必要工具

选择建议

  • 如果你只是运行 Java 程序,只需要安装 JRE
  • 如果你要开发 Java 程序,必须安装 JDK

1.3 环境变量配置

配置目的:让系统能够找到 Java 相关的可执行文件,如 javacjava

1.3.1 Windows 系统配置

步骤

  1. 下载并安装 JDK(推荐 JDK 8 或 JDK 11)
    • 访问 Oracle 官网 下载对应版本的 JDK
    • 按照安装向导完成安装,记住安装路径
  2. 打开环境变量设置
    • 右键点击「此电脑」→「属性」→「高级系统设置」→「环境变量」
  3. 配置 JAVA_HOME
    • 在「系统变量」中点击「新建」
    • 变量名:JAVA_HOME
    • 变量值:JDK 安装目录(如 C:\Program Files\Java\jdk1.8.0_202
  4. 配置 Path
    • 找到「Path」变量,点击「编辑」
    • 点击「新建」,添加:%JAVA_HOME%\bin
    • 点击「确定」保存
  5. 验证配置
    • 打开命令提示符(Win+R → 输入 cmd → 回车)
    • 执行 java -versionjavac -version
    • 如果显示版本信息则配置成功

1.3.2 Linux/Mac 系统配置

步骤

  1. 下载并安装 JDK
    • 访问 Oracle 官网 下载对应版本的 JDK
    • 按照安装向导完成安装,记住安装路径
  2. 编辑配置文件
    • 打开终端
    • 编辑 ~/.bashrc~/.bash_profile 文件:
      bash
      # 使用 vim 编辑
      vim ~/.bashrc
      # 或使用 nano 编辑
      nano ~/.bashrc
  3. 添加环境变量
    • 在文件末尾添加以下内容:
      bash
      export JAVA_HOME=/path/to/jdk  # 替换为实际的 JDK 安装路径
      export PATH=$JAVA_HOME/bin:$PATH
  4. 使配置生效
    • 执行:source ~/.bashrcsource ~/.bash_profile
  5. 验证配置
    • 执行 java -versionjavac -version
    • 如果显示版本信息则配置成功

小贴士:如何查看 JDK 安装路径?

  • Windows:在命令提示符中执行 where java
  • Linux/Mac:在终端中执行 which javaecho $JAVA_HOME

1.4 HelloWorld 程序

第一个 Java 程序:HelloWorld 是学习任何编程语言的传统入门程序

1.4.1 编写代码

步骤

  1. 创建文件:新建一个名为 HelloWorld.java 的文件
  2. 编写代码
Java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

1.4.2 编译和运行

操作步骤

  1. 打开命令提示符/终端,进入文件所在目录
  2. 编译:执行 javac HelloWorld.java
    • 编译成功后,会生成一个 HelloWorld.class 文件
  3. 运行:执行 java HelloWorld
  4. 输出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 是大小写敏感的,Mainmain 是不同的
  • 每个语句结束后必须加分号 ;
  • 类名通常使用驼峰命名法,首字母大写

2. 基础语法

2.1 变量与数据类型

2.1.1 变量定义

变量:变量是用来存储数据的容器,定义变量时需要指定数据类型和变量名

2.1.1.1 变量命名规则

  • 只能由字母、数字、下划线(_)和美元符号($)组成
  • 不能以数字开头
  • 不能使用 Java 关键字(如 intclasspublic 等)
  • 建议使用驼峰命名法(如 userNamestudentAge
  • 变量名应具有描述性,能够清晰表达变量的用途

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 种基本数据类型,用于存储简单的值,直接存值,占用固定内存

类型占用空间描述(范围/用途)示例
byte1 字节整数(-128 ~ 127),节省内存byte b = 100;
short2 字节整数(-32768 ~ 32767)short s = 1000;
int4 字节常用整数(-20 亿 ~ 20 亿),默认整数类型int i = 100000;
long8 字节大整数(需加 L 后缀,如 100Llong l = 100000L;
float4 字节单精度浮点数(加 F 后缀,如 3.14Ffloat f = 3.14f;
double8 字节双精度浮点数(默认小数类型,如 3.14double d = 3.14;
char2 字节单个字符(用单引号,如 'A',存 Unicode 码)char c = 'A';
boolean1 字节布尔值(truefalseboolean 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 引用数据类型

引用数据类型:引用数据类型存储的是对象的引用(内存地址),而不是对象本身

  • :如 StringIntegerArrayList
  • 接口:如 RunnableListMap
  • 数组:如 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 → doublechar → 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); // 输出 65
2.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 运算符优先级

运算符优先级从高到低:

  1. 括号 ()
  2. 一元运算符:++--!
  3. 算术运算符:*/% 高于 +-
  4. 关系运算符:><>=<= 高于 ==!=
  5. 逻辑运算符:&& 高于 ||
  6. 三元运算符
  7. 赋值运算符

建议: 当不确定优先级时,使用括号明确运算顺序。

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 4
2.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 9
2.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]; // 初始值为 null
2.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]); // 10
2.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); // 1
2.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); // 2

2.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); // 2

3. 方法

方法是一段可重复执行的代码块,用于实现特定功能。

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(); // 0

3.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); // 120

3.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 修饰属性
  • 提供 gettersetter 方法访问属性
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(); // 输出:外部类属性:10

4.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(); // 输出:外部类静态属性:20

4.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  局部变量:40

4.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. 方式 1:调用 Object 中的 getClass() 方法
  2. 方式 2:使用类的静态属性 .class
  3. 方式 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 反射练习:编写一个小框架

反射的应用:通过反射实现一个简单的框架,根据配置文件动态加载类并执行方法

步骤

  1. 创建 properties 配置文件,配置类的全限定名和方法名
  2. 解析配置文件,获取类名和方法名
  3. 使用反射加载类,获取方法并执行

示例代码

properties
# reflect.properties

classname=com.at.b_reflect.Student
methodname=eat
java
@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 {
}

注解使用注意事项

  1. 空注解可以直接使用
  2. 不同位置可以使用相同的注解,但同一位置不能使用相同的注解
  3. 注解中有属性时,必须为属性赋值;多个属性用逗号隔开;数组属性使用 {}
  4. 注解属性有默认值时,可不用重新赋值
  5. 注解只有一个属性且名为 value 时,使用时可以省略属性名

示例代码

java
public @interface Book1 {
    String value();
}

@Book1("金瓶梅")
public class BookShelf {
    @Book1("金瓶梅")
    public void show() {

    }
}

6.4 注解的解析

解析注解:通过反射获取注解信息并使用

相关接口AnnotatedElement 接口,实现类包括 ClassConstructorFieldMethod

常用方法

  • 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 元注解

元注解:管理注解的注解,用于控制注解的使用位置和生命周期

常用元注解

  1. @Target:管理注解的使用位置

    • ElementType.TYPE:控制注解能在类上使用
    • ElementType.FIELD:控制注解能在属性上使用
    • ElementType.METHOD:控制注解能在方法上使用
    • ElementType.PARAMETER:控制注解能在参数上使用
    • ElementType.CONSTRUCTOR:控制注解能在构造上使用
  2. @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 自定义步骤:

  1. 继承 ExceptionRuntimeException
  2. 实现构造方法(通常重写无参和带消息的构造方法)。

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 异常处理最佳实践

  1. 尽量捕获具体的异常,而不是捕获所有异常(Exception
  2. 不要吞噬异常,至少要记录异常信息
  3. 使用 finally 块清理资源,或使用 try-with-resources 语句
  4. 对于运行时异常,应该在代码中预防,而不是依赖异常处理
  5. 合理使用自定义异常,使异常信息更具语义

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 TT 或 T 的子类读取元素可读(T),不可写(除 null)
? super TT 或 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()转换为 Stringsb.toString()

示例:

java
StringBuffer sb = new StringBuffer("a");
sb.append("b"); // 直接在原对象上追加,结果为 "ab"
System.out.println(sb.toString()); // ab

8.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()转换为 Stringsb.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 ,olleH

8.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.50

6.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:56

6.6 包装类

包装类是基本数据类型的引用类型版本,位于 java.lang 包中:

基本类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

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 接口)

  • 特点:存储单个元素(如 StringInteger 等),元素之间是独立的。
  • 核心子接口ListSet

9.1.2 多列集合(Map 接口)

  • 特点:存储键值对(key-value),通过 key 映射 valuekey 不可重复(重复则覆盖),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("李四")); // true

9.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
构造方法引用类名::newArrayList::new
数组引用类型[]::newString[]::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 流的使用建议

  1. 使用 try-with-resources 语句:自动关闭流,避免资源泄漏
  2. 选择合适的流:根据数据类型和操作选择对应的流
  3. 使用缓冲流:提高读写效率
  4. 注意异常处理:捕获并处理 IOException
  5. 及时关闭流:释放系统资源

示例:综合使用 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 类

步骤:

  1. 继承 Thread
  2. 重写 run() 方法
  3. 创建线程对象并调用 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 接口

步骤:

  1. 实现 Runnable 接口
  2. 重写 run() 方法
  3. 创建 Thread 对象,传入 Runnable 实例
  4. 调用 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 接口

步骤:

  1. 实现 Callable<V> 接口,指定返回值类型
  2. 重写 call() 方法
  3. 创建 FutureTask 对象,传入 Callable 实例
  4. 创建 Thread 对象,传入 FutureTask 实例
  5. 调用 start() 方法
  6. 通过 FutureTaskget() 方法获取返回值

示例:

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:线程安全的 HashMap
  • CopyOnWriteArrayList:线程安全的 ArrayList
  • CopyOnWriteArraySet:线程安全的 Set
  • BlockingQueue:阻塞队列,如 ArrayBlockingQueueLinkedBlockingQueue

示例:使用 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()); // 2000

11.7 多线程最佳实践

  1. 使用线程池:避免频繁创建和销毁线程
  2. 使用线程安全的集合:避免手动同步
  3. 最小化同步范围:只同步必要的代码块
  4. 使用 Lock 替代 synchronized:提供更灵活的锁机制
  5. 避免死锁:合理设计锁的获取顺序
  6. 使用 volatile 关键字:保证变量的可见性
  7. 使用原子类:如 AtomicInteger,提供原子操作
  8. 合理处理 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()); // 2000

12. 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 表达式使用。

常用函数式接口:

接口方法说明
Runnablevoid 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: 42

12.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));