JavaSE基础合集
一、Java 基础
1. 注释
平时我们编写代码,在代码量少的时候,还可以看得懂自己写的代码;但是当项目结构一旦复杂起来,我们就需要用到注释。
注释并不会被执行,是给我们写代码的人看的。
书写注释是一个非常好的习惯。
平时写代码一定要注意规范。
1.1 单行注释
// 单行注释 |
1.2 多行注释
/* |
1.3 文档注释
JavaDoc 文档注释:
/** |
1.4 有趣的代码注释
/*** |
/*** |
/*** |
/*** |
/*** |
/*** |
2. 关键字
Java关键字 是 Java 中预先定义的、具有特殊含义的 单词。这些单词不能用作变量名、方法名、类名等标识符,因为它们已经被编译器用于控制程序的结构、数据类型、访问权限、异常处理等功能。
常见的 Java 关键字包括:
- 基本数据类型:
int,double,boolean,char等 - 控制流程:
if,else,for,while,switch,case,break,continue - 面向对象:
class,interface,extends,implements,abstract,final,static,new,this,super - 访问修饰符:
public,protected,private - 异常处理:
try,catch,finally,throw,throws - 其他:
return,void,import,package,enum,synchronized,volatile,transient,native
true、false、null虽然在语法上看似关键字,但严格来说是字面量(literal /ˈlɪtərəl/),不过同样不能用作标识符
3. 保留字
定义:Java 语言中预留的、目前尚未使用的单词,留作未来扩展用。
特点:也不能 用作标识符,但当前没有实际功能。
例子:goto、const(这两个在 C/C++中是关键字,Java 保留了但未使用)。
4. 标识符
标识符(Identifier) 是程序中用来给变量、方法、类、接口、包等元素起名字的符号。简单说,就是你自定义的名字。
标识符的命名规则(必须遵守)
- 组成字符:只能包含字母(A-Z, a-z)、数字(0-9)、下划线(_)和美元符号($)。
- 首字符限制:不能以数字开头。
- 大小写敏感:
myVar和MyVar是两个不同的标识符。 - 无长度限制,但建议简洁有意义。
- 不能是关键字或保留字(如
class,if,int等)。
可以使用中文命名,但是一般不建议这样使用,也不建议使用拼音,很 low
public static void main(String[] args) {
String 游戏 = "王者荣耀";
System.out.println(游戏); // low
}
合法与非法示例
| 合法 | 非法 |
|---|---|
myVariable |
123abc(数字开头) |
_count |
my-var(连字符不允许) |
$value |
class(关键字) |
命名惯例(非强制,但推荐)
- 类名:大驼峰(PascalCase),如
StudentInfo - 方法名、变量名:小驼峰(camelCase),如
getAge()、studentName - 常量名:全大写加下划线,如
MAX_SIZE - 包名:全小写,如
com.example.utils
阿里规范






5. 数据类型
5.1 基本数据类型(primitive type)
整型: byte, short, int, long 1 字节,2 字节,4 字节,8 字节
浮点: float, double 4 字节,8 字节
字符: char 2 字节
布尔: boolean 1 字节

什么是字节?
位(bit): 是计算机内部 数据存储 的最小单位,11001100 是一个 8 位二进制数
字节: 是计算机中 数据处理 的最小单位,习惯上用 B 来表示
1B(byte, 字节)= 8bit(位)
字符: 是计算机中使用的字母、数字、字、符号

电脑中的 32 位和 64 位是什么?
32 位和 64 位指的是 CPU(中央处理器)一次能处理的数据最大长度,就像一条马路一次能并排通过多少辆车。
- 核心区别:一次能处理多少数据
- 32 位:CPU 一次能处理 32 位(4 字节) 数据。就像一条只有 4 车道的路,一次最多并行通过 4 辆车。
- 64 位:CPU 一次能处理 64 位(8 字节) 数据。相当于一条 8 车道的路,一次能并行通过 8 辆车,通行能力翻倍。
- 最重要的实际影响:内存支持上限
这是对普通用户最直观的区别:
- 32 位操作系统:最大只能使用 约 4GB 内存(实际可用通常只有 3.25GB 左右)。因为 32 位地址总线最多能寻址 $2^{32}$ 个内存地址,换算过来就是 4GB。
- 64 位操作系统:理论上最大支持 16EB(Exabyte,艾字节) 内存,目前主流消费级主板和 Windows 系统支持 128GB~2TB 不等。所以如果你的电脑内存大于 4GB,必须装 64 位系统才能全部用上。
- 性能和软件兼容性
- 性能:64 位 CPU 在处理大量数据(如视频渲染、大型游戏、科学计算)时明显更快,因为一次能搬运更多数据。但对于日常办公、看网页,差距不大。
- 软件兼容:
- 64 位系统 可以同时运行 64 位软件 和 32 位软件(通过模拟兼容模式)。
- 32 位系统 只能运行 32 位软件,无法运行 64 位软件。
5.2 引用数据类型(reference type)
类,接口,数组
5.3 强类型语言
强类型语言 是指一种编程语言,它对变量的数据类型有严格的约束:每个变量在使用前必须明确声明其类型(或由编译器推断出确定的类型)
要求变量的使用要严格符合规定,所有变量必须先定义后才能使用。常见的强类型语言有:Java
5.4 弱类型语言
弱类型语言 是指对数据类型约束较为宽松的语言,它允许不同类型之间进行大量的隐式转换,甚至在不相关的类型之间也能自动转换,而不会报错或抛出异常。
如:JavaScript
5.5 进制
int i = 10; // 十进制 |
5.6 类型转换
强制类型转换:(类型)变量名 高-> 低
自动类型转换:低-> 高
byte, short-> int
char-> int
布尔类型 boolean 不能转换
转换的时候可能存在内存溢出,或者精度问题
5.7 字符编码
ASCII 码:最早的字符编码,只包含英文字母、数字、标点和控制字符,共 128($2^7)$ 个字符,用 7 位(实际存为 1 字节)表示。
Unicode:一个庞大的 字符集,为世界上几乎所有的字符分配了一个唯一的数字编号(码点),范围从 U+0000 到 U+10FFFF,包含中文、日文、emoji 等。但它只规定了编号,没规定如何存储。
UTF-8:是 Unicode 的一种 具体存储/编码方式,用 1 到 4 个字节变长表示一个字符。它向后兼容 ASCII(前 128 个字符用 1 字节表示,与 ASCII 完全相同),同时能高效表示其他字符。
UTF-8 编码下的字节数
英文字母:占 1 字节
中文汉字:占 3 字节
- GBK 是中国国家标准的中文编码扩展,全称《汉字内码扩展规范》,主要用于简体中文环境。
GBK 编码下的字节数
- 英文字母(ASCII):占 1 字节(与 ASCII 完全兼容)。
- 中文字符(含繁体):占 2 字节。
6. 变量
变量是什么:就是可以变化的量。
Java 是一种强类型语言,每个变量都必须声明其类型。
Java 变量是程序中最基本的存储单元,其要素包括:变量名,变量类型,作用域。
type varName [=value][{,varName[=value]}]; |
注意事项:
- 每个变量都有类型,可以是基本类型,也可以是引用类型。
- 变量名必须是合法的标识符。
- 变量声明是一条完整的语句,因此每一个声明都必须一分号结束。
变量作用域
类变量,实例变量,局部变量
public class Variable { |
实例变量:从属于对象,如果不自行初始化,默认 0 或 0.0
布尔类型默认 false
除了基本类型,其余的默认值都是 null
局部变量:必须声明和初始化值
常量
常量:初始化之后不能再改变值!不会变动的值。在程序运行过程中不允许被改变。
final 常量名=值; |
常量名一般使用大写字符
阿里规范:

变量的命名规范
- 所有变量、方法、类名:见名知意
- 类成员变量:首字母小写和驼峰原则:monthSalary
- 局部变量:首字母小写和驼峰原则
- 常量:大写字母和下划线:MAX_VALUE
- 类名:首字母大写和驼峰原则:Man, GoodMan
- 方法名:首字母小写和驼峰原则:run(), sendMsg()
7.运算符
算术运算符:+,-,*,/,%,++,–
赋值运算符:=
关系运算符:>, <,> =,<=,==,!=, instanceof
逻辑运算符:&&,||,!
三元运算符:? :
扩展赋值运算符:+=,-=,*=,/=
位运算符:&,|,^,~,>>, <<,> >>
// 自增自减运算符 |
幂运算:Math.pow(a, b) => $a^b$
求根:Math.sqrt(2) => $\sqrt{2}$
使用 LaTeX 数学公式(推荐,需要渲染器支持)
许多 Markdown 编辑器(如 Typora、Obsidian、GitHub、VSCode 等)支持通过
$...$或$$...$$嵌入 LaTeX 公式。
- 平方根:
$\sqrt{x}$→ $\sqrt{x}$- n 次方根:
$\sqrt[n]{x}$→ $\sqrt[n]{x}$
位运算
<< *2
>> /2
研究位运算:《计算机组成原理》
8. 包机制
为了更好的组织类,Java 提高了包机制,用于区分类名的命名空间。
包语句语法格式:
package pkg1[.pkg2[.pkg3...]]; |
一般利用公司域名倒置作为包名;
为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 “import” 语句
import packge1[.packge2...].(classname|*);
9. JavaDoc
javadoc 是用来生成自己的 API 文档的
参数信息:
- @author 作者名
- @version 版本号
- @since 指明需要最早使用的 jdk 版本
- @param 参数名
- @return 返回值情况
- @throws 异常抛出情况
命令行形式
javadoc -encoding UTF-8 -charset UTF-8 -docencoding UTF-8 -d 输出目录 DocTest.java |

IDEA 中生成 Javadoc


Command line arguments:
-J-Dstdout.encoding=UTF-8 -J-Dstderr.encoding=UTF-8 -encoding UTF-8 -charset UTF-8 -docencoding UTF-8 |
-encoding UTF-8
- 作用:告诉
javadoc工具,源代码文件(.java) 是用什么编码保存的。- 为什么重要:如果你的
.java文件中有中文注释,而文件实际是用 UTF-8 保存的,但 javadoc 默认按系统编码(Windows 下是 GBK)去读,就会读到乱码,进而报错或生成乱码文档。- 示例:
-encoding UTF-8表示“请用 UTF-8 解读我的源码”。
-charset UTF-8
- 作用:指定 生成的 HTML 文档 所使用的字符集(写在
<meta charset="UTF-8">中)。- 为什么重要:浏览器打开文档时,会根据这个声明的字符集来解码页面。如果不指定,可能默认使用 ISO-8859-1 等,导致中文显示乱码。
- 示例:生成的
index.html头部会包含<meta charset="UTF-8">。
-docencoding UTF-8
- 作用:指定生成的 HTML 文件 实际保存时的编码。
- 与
-charset的关系:通常两者设为相同值(UTF-8),-docencoding负责文件存储,-charset负责声明。如果只设-charset不设-docencoding,文件可能以其他编码保存,导致声明与实际不符。- 示例:生成的
.html文件以 UTF-8 字节写入磁盘。
-J-Dstdout.encoding=UTF-8和-J-Dstderr.encoding=UTF-8
-J前缀:表示后面的参数要传递给javadoc底层运行的 JVM,而不是javadoc工具本身。-Dstdout.encoding=UTF-8:强制 JVM 的标准输出流(stdout)使用 UTF-8 编码输出字符。-Dstderr.encoding=UTF-8:强制标准错误流(stderr)使用 UTF-8 编码。- 为什么需要:JDK 18+ 之后,控制台输出的编码不再随
file.encoding变化,而是由stdout.encoding和stderr.encoding独立控制。如果不加,Windows 下默认用 GBK 输出日志,而 IDEA 控制台按 UTF-8 解码,导致乱码。加上这两个参数后,javadoc 打印的进度、错误信息都以 UTF-8 输出,与 IDEA 控制台一致,乱码消失。二、locale(区域设置)参数
在
Generate JavaDoc对话框中,有一个 Locale 输入框(你截图里填的是zh_CN)。作用
- 控制生成的 HTML 文档的导航栏、标题、月份名称等界面文字的语言。
- 例如,文档顶部的“Overview”、“Package”、“Class”等文字,以及“Since”、“Version”等标签的显示语言。
常用值
Locale 值 效果 zh_CN导航栏显示中文(如“概述”、“包”、“类”) en_US导航栏显示英文(默认) ja_JP日语 留空 使用系统默认语言(通常是英文)
生成结果:

二、Java 流程控制
1. 用户交互 Scanner
Scanner 对象:用了获取用户的输入,java.util.Scanner 是 java5 新特征。
基本语法:
next()方式
package com.it.day01; |
运行结果:

nextLine()方式
package com.it.day01; |
运行结果:

next()和 nextLine()区别

其他
Scanner sc = new Scanner(System.in); |
hasNextxxx(), nextxxx()相互配合
实战案例:
package com.it.day01; |
运行结果:

2. 顺序结构
Java 的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行。
顺序结构是最简单的算法结构。

语句与语句之间,框与框之间是按从上到下的顺序进行的,它是由若干个依次执行的处理步骤组成的,它是任何一个算法都离不开的一种基本算法结构。
3. 选择结构
if 单选择
if (布尔表达式) { |
if 双选择
if (布尔表达式) { |
if 多选择
if (布尔表达式1) { |
嵌套 if
if (布尔表达式1) { |
switch 多选择结构
多选择结构还有一个实现方式就是 switch case 语句
switch case 语句判断一个变量与一系列值中的某个值是否相等,每个值称为一个分支。
switch(expression) { |
switch 语句中的变量类型可以是:
- byte, short, int, char 及其包装类
- enum 枚举类型
- 从 JavaSE7 开始, 支持 String 类型了
case 标签的要求
必须是编译时常量(compile-time constant),包括:
- 字面量:
1、'A'、"hello"
- 字面量:
final常量:final int MONDAY = 1;- 枚举常量:
Day.MONDAY
- 枚举常量:
字符串常量(final 且初始化值为字面量或常量表达式)
枚举
package com.it.day01; |
字符串
package com.it.day01; |
新特性
- JDK 14 引入
switch (obj) { case ... -> ... }这种箭头写法 - JDK 21 正式使用 类型模式
case String s/case Integer i/case null - JDK 21 正式使用 when 守卫条件
case Integer i when i > 0 ->
Object obj = getSomeObject(); |
用了箭头
->之后:
- 不需要写
break:箭头分支执行完后自动结束,不会继续执行后续分支。- 不会穿透(fall-through):传统
case:不加break会继续执行下一个case的代码,而箭头语法天然避免了这种行为。示例对比
// 传统冒号语法(需 break,否则穿透)
switch (x) {
case 1:
System.out.println("one");
// 忘记 break → 会继续输出 "two"
case 2:
System.out.println("two");
break;
}
// 箭头语法(无需 break,无穿透)
switch (x) {
case 1 -> System.out.println("one"); // 执行完立即退出
case 2 -> System.out.println("two");
}
4. 循环结构
while 循环
while (布尔表达式) { |
- 只要布尔表达式为 true,循环就会一直执行下去
- 我们大多数情况是会让循环停止下来的,我们需要一个让表达式失效的方式来结束循环
- 少部分情况需要循环一直执行,比如服务器的请求响应监听等
- 循环条件一直为 true 就会造成无限循环(死循环),我们正常的业务编程中一个尽量避免死循环。会影响程序性能或者造成程序卡死崩溃!
示例:计算 1+2+3+…+100 =?
public class WhileDemo { |
运行结果:
sum = 5050
do…while 循环
对于 while 语句而言,如果不满足条件,则不能进入循环。但有时候我们需要及时不满足条件,也至少执行一次。
do…while 至少执行一次
do { |
while 和 do…while 区别:
while 先判断后执行,do…while 先执行后判断
do…while 总是保证循环体至少被执行一次!
for 循环
for 循环语句是支持迭代的一种通用结构,是最有效、最灵活的循环结构
for 循环执行的次数是在执行前就确定的。
语法格式:
for(初始化;布尔表达式;更新) { |
示例:
- 计算 0 到 100 之间的奇数(odd)和偶数(even)的和
public static void main(String[] args) { |
运行结果:
oddSum = 2500
evenSum = 2550
- 用 while 或 for 循环输出 1-1000 之间能被 5 整除的数,并且每行输出 3 个。
// 方法一 |
- 打印九九乘法表

public class ForDemo4 { |
运行结果:

增强 for 循环
在 java5 引入了主要用于数组或集合的增强 for 循环
语法格式:
for(声明语句 : 表达式) { |
声明语句:声明新的局部变量,该变量的类型必须和数组元素(或集合元素)的类型匹配。其作用域限定在循环代码块,其值与此时数组或集合元素的值相等。
表达式:表达式是要访问的数组名或集合名,或者是返回值为数组或集合的方法。
public class ForDemo5 { |
IDEA 快捷代码:
10.fori -> for(int i = 0; i < 10; i++){}
itit -> 增强 for
5. break, continue, goto
break 在任何循环语句的主体部分,均可以用 break 控制循环的流程。break 用于强行退出循环,不执行循环中剩余的语句(break 也在 swtich 语句中使用)
continue 语句用在循环语句体中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判断。
goto:
- goto 在 Java 中是一个保留字
标签使用【了解】

示例:要打印 101 到 150 之间的所有质数(质数是大于 1 的自然数,只能被 1 和自身整除。)
求质数的数学原理:

public static void main(String[] args) { |
运行结果:
101 103 107 109 113 127 131 137 139 149
6. 练习
打印三角形
public static void main(String[] args) { |
运行结果:

三、Java 方法
1. 方法
Java 方法时语句的集合,他们在一起执行一个功能。
- 方法是解决一类问题的步骤的有序组合
- 方法包含与类或对象中
- 方法在程序中被创建,在其他地方被引用。
设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的原子性,就是一个方法只完成一个功能,这样利于我们后期的扩展
// 加法 |
2. 方法的定义
Java 的方法类似于其他语言的函数,是一段 用来完成特点功能的代码片段,一般情况下,定义一个方法包含以下语法:
方法包含一个方法头和方法体。 下面是一个方法的所有部分:
修饰符:修饰符,这是可选的,告诉编译器如何调用该方法。定义类该方法的访问类型。返回值类型:方法可能 UI 返回值。returnValueType 是方法返回值的数据类型。有些方法执行所需的操作,但没有返回值。在这种情况下,returnValueType 是关键字 void。方法名:是方法的实际名称。方法名和参数列表共同构成方法签名。参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。- 形式参数:在方法被调用时用于接收外界输入的数据。如
public int add(int a,int b); - 实参:调用方法时实际传给方法的数据。如
int sum = add(1,2);
- 形式参数:在方法被调用时用于接收外界输入的数据。如
方法体:方法体包含具体的语句,定义该方法的功能。
语法格式:
修饰符 返回值类型 方法名(参数类型 参数名) { |
3. 方法调用
调用方法:对象名.方法名(实参列表)
Java 支持两种调用方法的方式,根据方法是否返回值来选择。
- 当方法返回一个值的时候,方法调用通常被当做一个值。例如:
int larger = max(30, 40); |
- 如果方法返回值是 void,方法调用一定是一条语句.
System.out.println("Hello world"); |
拓展:值传递和引用传递
4. 方法的重载
方法重载就是在一个类中,有相同的方法名称,但形式不同的方法。
方法重载的规则:
- 方法名必须相同。
- 参数列表必须不同(个数不同,或类型不同,参数顺序不同)
方法的返回类型可以相同也可以不同。仅仅返回类型不同不足以成为方法的重载。
实现理论:
方法名称相同时,编译器会根据调用方法的参数个数、参数类型等去逐个匹配,以选择对应的方法,如果匹配失败,则编译器报错。
5. 命令行传递参数
有时候你希望运行一个程序时候再传递给它消息。这要靠传递命令行参数给 main()函数实现。
public class CommandLine { |
IDEA 中配置命令行参数:

cmd 命令行方式:
java com.it.day01.CommandLine 1 2 3 4 5 |

6. 可变参数
JDK1.5 开始,Java 支持传递同类型的可变参数给一个方法。
在方法声明中,在指定参数类型后加一个省略号(...)。
一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。 任何普通的参数必须在它之前声明。
package com.it.day01; |
运行结果:
d = 3.14
可变参数最大值:5
7. 递归
recursion /rɪˈkɜːʃn/ n.(数学、计算机科学)递归,循环(函数、过程或定义直接或间接引用自身);(数学)递归式,递推式
A 方法调用 B 方法,我们很容易理解。
递归就是:A 方法调用 A 方法!即自己调用自己。
利用递归可以用简单的程序来解决一些复杂的问题。它通常把一个大型复杂的问题层层转化为一个与原向题相似的规模较小的向题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。
递归结构包括两个部分:
递归头:什么时候不调用自身方法。如果没有头,将陷入死循环。
递归体:什么时候需要调用自身方法。
实例:求 5 的阶乘 5!
public class RecursionDemo { |
运行结果:
120
8. 作业
写一个计算器,要求实现加减乘除功能,并且能够循环接收新的数据,通过用户交互实现
calculator /ˈkælkjuleɪtə(r)/ n.计算器
思路推荐:
- 写 4 个方法:加减乘除(add /æd/, subtract /səbˈtrækt/, multiply /ˈmʌltɪplaɪ/, divide /dɪˈvaɪd/)
- 利用循环和 switch 进行用户交互
- 传递需要操作的两个数
- 输出结果
package com.it.day01; |
使用了正则表达式
在这个计算器项目中,正则表达式经历了从 基础版 到 增强版 的演进,核心作用是 校验输入格式 并 提取计算要素。以下是该项目中正则表达式使用的全面总结:
1. 核心演进对比
维度 基础版正则 增强版正则 表达式 (\\d+)\\s*([+\\-*/])\\s*(\\d+)(\\d+(?:\\.\\d+)?)\\s*([+\\-*/])\\s*(\\d+(?:\\.\\d+)?)数字匹配 仅支持整数(如 1)支持整数和小数(如 1.5)小数校验 不支持 强制小数点后至少有 1 位数字 适用场景 简单整数计算器 实用的浮点数计算器 2. 增强版正则结构拆解
最终使用的正则:
(\\d+(?:\\.\\d+)?)\\s*([+\\-*/])\\s*(\\d+(?:\\.\\d+)?)
(\\d+(?:\\.\\d+)?)(捕获组 1 和 3):匹配操作数。\\d+匹配整数部分;(?:\\.\\d+)?为非捕获组,匹配可选的小数部分。\\s\*:匹配运算符与数字之间的任意数量空格,提升输入容错率。([+\\-\*/])(捕获组 2):匹配并提取运算符。\\-必须转义以避免被识别为范围符号。3. 在代码中的关键配合
全串匹配校验:配合
matcher.matches()使用,要求整个输入字符串严格符合“数字 运算符 数字”的结构,防止误匹配多余字符。数据提取与转换:通过
matcher.group(1/2/3)精准提取两个操作数和运算符,并交由Double.parseDouble()转换为浮点数。前置预处理:结合
sc.nextLine().trim()去除输入首尾空格,与正则内部的\\s*形成双重容错。
方法 匹配范围 行为描述 matches()整个输入字符串 尝试将整个输入序列与模式完全匹配,只有从头到尾完全一致时才返回 true。find()输入字符串的子串 扫描输入序列,查找下一个能与模式匹配的子序列,只要找到任意一处就返回 true。4. 设计优势与避坑点
- 严谨的小数匹配:使用
(?:\\.\\d+)?而非\\.?\\d*,有效拦截了123.这类不合法的数字格式。- 异常防御:正则前置校验拦截了大部分格式错误,使得后续的
Double.parseDouble()极难触发NumberFormatException。- Java 转义规范:在 Java 字符串中正确使用了双反斜杠(如
\\d、\\.、\\-)。- 减号转义:在字符类
[...]中对减号进行了转义,避免了正则语法错误。5. 项目收获
通过本项目,你掌握了如何利用正则表达式快速实现一个 文本解析器,理解了 捕获组 与 非捕获组 的实际区别,并体会到了“正则校验 + 异常分层处理”在提升程序健壮性上的重要作用。
扩展思考:当前正则仅支持两个操作数的二元运算。若未来要支持连续运算(如
1+2 * 3)或负数开头(如-5+3),只需在正开头增加-?并对剩余部分进行调整即可。
四、Java 数组
1. 数组的定义
数组是 相同类型数据 的 有序集合
数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。
其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。
2. 数组的声明和创建
首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:
dataType[] arrayRefVar; // 首选方法,阿里推荐 |
Java 语言使用 new 操作符来创建数组,语法如下:
dataType[] arrayRefVar = new dataType[arraySize]; |
数组的元素是通过索引访问的,数组索引从 0 开始
获取数组长度:arrays.length
3. 内存分析
Java 内存分析

上图只是简单说明,Java 内存不止这三个。
数组内存结构:

4. 数组的三种初始化
静态初始化
int[] a = {1,2,3}; |
动态初始化
int[] a = new int[2]; |
数组的默认初始化
数组是引用类型,它的元素相指当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被 隐式初始化
5. 数组的四个基本特点
其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
其完素必须是相同类型, 不充许出现混合类型。
数组中的元素可以是任向数据类型,包活基本类型和引用类型
数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。
数组本身就是对象,Java 中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型数组对象本身是在堆中的。
6. 数组边界
下标的合法区间:[0,length-1],如果越界就会报错
public static void main(String[] args) { |
ArrayIndexOutOfBoundsExcepton: 数组下标越界异常
小结:
数组是相同数据类型(数据类型可以为任意类型)的有序集合
数组也是对象。数组元素相当于对象的成员变量
数组长度是确定的,不可变的。如果越界,则报:ArrayIndexOutOfBoundsException
7. 数组的使用
普通 for 循环
打印数组
// 使用 StringBuilder 拼接更高效 |
求最大值,求和,反转
// 求数组最大值 |
增强 for
// forEach |
测试
public static void main(String[] args) { |
8. 二维数组
多维数组可以看成是数组的数组,比如 二维数组 就是一个特殊的一维数组,其 每一个元素都是个一维数组。

二维数组
int[][] a = new int[2][5]; |
使用
public static void main(String[] args) { |
运行结果:
1 2 3
4 5
6 7 8 9 10
9. Arrays 类
数组的工具类 java.util.Arrays
由于数组对象本身并没有什么方法可以供我们调用,但 API 中提供了一个工具类 Arrays 供我们使用, 从而可以对数据对象进行一些基本的操作。
Arrays 类中的方法都是 static 修饰的静态方法,在使用的时候可以直接使用类名进行调用,而 “ 不用”使用对象来调用(注意: 是“不用而不是“不能”)
具有以下常用功能:
给数组赋值:通过 fill()方法。
int[] arr = new int[10]; |
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
对数组排序:通过 sort()方法, 按升序。
// 排序 |
对于 Integer 数组,可以传入比较器自定义排序方式
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; |
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
比较数组:通过 equals()方法查找数组中元素值是否相等
查找数组元素:通过 binarySearch()方法能对排序好的数组进行二分查找。(注意:二分查找的数组必须保证有序从小到大)
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
8
Arrays.asList() 方法详解
Arrays.asList(T... a) 是 java.util.Arrays 类中的一个静态方法,用于将数组快速转换为一个固定大小的 List。
1. 基本用法
String[] arr = {"A", "B", "C"}; |
2. 返回的 List 的特点
| 特点 | 说明 |
|---|---|
| 固定大小 | 返回的 List 长度与原数组相同,不能增加或删除元素(调用 add、remove 会抛出 UnsupportedOperationException) |
| 视图(View) | 返回的 List 是对原数组的视图,修改 List 中的元素会同步影响原数组,反之亦然 |
| 序列化支持 | 返回的 List 实现了 Serializable 和 RandomAccess |
| 泛型限制 | 如果传入的是基本类型数组(如 int[]),会被当作一个元素处理(见下文坑点) |
3. 常见坑点与注意事项
坑点一:不能增删元素
List<String> list = Arrays.asList("A", "B"); |
原因:返回的是 java.util.Arrays 内部类 ArrayList(不是 java.util.ArrayList),它底层直接使用传入的数组,不支持结构性修改。
坑点二:基本类型数组被当作单个元素
int[] arr = {1, 2, 3}; |
原因:泛型不支持基本类型,int[] 被当作一个 Object 元素。解决方案:使用包装类型 Integer[] 或 Java 8+ 的 Stream API。
坑点三:修改 List 会影响原数组
String[] arr = {"A", "B"}; |
原因:List 是数组的视图,操作直接作用于底层数组。
4. 如何得到一个真正的可变 List?
如果需要可增删的 List,通常这样处理:
// 方式一:传给 ArrayList 构造器 |
10. 冒泡排序
冒泡排序思路:
- 比较数组中两个相邻元素,如果第一个数比第二个数大,交换
- 每一次比较,都会产生出一个最大或者最小数
- 下一轮则可以少一次比较
- 依次循环,直到结束

// n 个元素,内部比较 n-1 次, |
arr.length-1 : 数组有 n 个数,只需要比较 n-1 轮得到 n-1 个数的位置,最后一个自然就有序
内层
arr.length-1-i: -i 就是已经有 i 个元素有序了,不需要比较。只需要比较剩下的 arr.length-1-i 个元素。
11. 稀疏数组
需求:编写五子棋游戏中,有存盘退出和续上盘的功能

分析问题:因为该二维数组的很多值是默认值 0,因此记录了很多没有意义的数据
解决:稀疏数组
稀疏数组
当一个数组中大部分元素为 0,或者为同一值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方式:
- 记录数组一共有几行几列,有多少个不同值
- 把具有不同值的元素和行列及值记录在一个小规模的数组中,从而缩小程序的规模

示例:将五子棋棋盘存储为稀疏数组,再还原回来
package com.it.day02; |
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
11 11 2
1 2 1
2 3 2
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
五、面向对象
1. 什么是面向对象
面向过程思想
步骤清晰简单,第一步做什么,第二步做什么..
面向过程适合处理一些较为简单的问题
面向对象思想
物以类聚,
分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。
最后,才对某个分类下的细节进行面向过程的思索面向对象适合处理复杂的问题,适合处理需要多人协作的问题!
对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,我们需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,仍然需要面向过程的思路去处理。
面向对象编程(Object-Oriented Programming, OOP)
面向对象编程的本质就是:以类的方式组织代码,以对象的方式组织(封装)数据。
抽象:编程思想
三大特性:
封装继承多态
从认识论角度考虑是先有对象后有类。对象,是具体的事物。类,是抽象的,是对
对象的抽象从代码运行角度考虑是先有类后有对象。类是对象的
模板
2. 回顾方法及加深
方法的定义
修饰符
返回类型
break:跳出switch,结束循环和return的区别方法名:注意规范就 OK 见名知意
参数列表:(参数类型,参数名)…
异常抛出:throws
方法的调用
静态方法
非静态方法
形参和实参
值传递和引用传递
this 关键字
3. 类与对象的关系
类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。
- 动物,植物,手机,电脑…
- Person 类,Pet 类,Car 类等,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为。
对象是抽象概念的具体实例
张三就是人的一个具体实例,张三家里的旺财就是狗的一个具体实例
能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念
4. 创建与初始化对象
使用 new 关键字创建对象
使用 new 关键学创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下俩个特点:
- 必须和类的名字相同
- 必须没有返回类型,也不能写 void
5. 构造器
使用 new 关键字,本质是在调用构造器
默认会有一个无参构造
有参构造:一且定义了有参构造,无参构造就必须显示定义
public class Person { |
6. 创建对象内存分析

7. 封装
该露的露,该藏的藏
我们程序设计要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自已完成,不允许外部干涉;
低耦合:仅暴露少量的方法给外部使用。
封装(数据的隐藏)
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
记住这句话就够了:属性私有,get/set
public class Person { |
通过 get/set 方法操作对象的字段
8. 继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模
extands 的意思是“扩展”。子类是父类的扩展。
JAVA 中类只有单继承,没有多继承!
继承是类和类之间的一种关系。除此之外, 类和类之间的关系还有依赖、组合、聚合等。
继承关系的俩个类,一个为子类(派生类,一个为父类(基类)。子类继承交类,使用关键字 extends 来表示。
子类和父类之间, 从意义上讲应该具有 “is-a” 的关系。
所有类的父类是 Object 类
super
super 注意点:
- super 调用父类的构造方法,必须在构造方法的第一个
- super 必须只能出现在子类的方法或者构造方法中!
- super 和 this 不能同时调用构造方法!
Vs this:
代表的对象不同:
this:
super: 代表父类对象的应用
前提
this:没有继承也可以使用
super:只能在继承条件才可以使用
构造方法
this();本类的构造
super():父类的构造!
方法重写
重写:需要有继承关系,子类重写父类的方法
- 方法名必须相同
- 参数列表必须相同
- 修饰符:范围可以扩大,不能缩小。public > protected > default > private
- 抛出的异常:范围可以缩小,不能扩大。ClassNotFoundException –> Exception(扩大)
为什么需要重写:
- 父类的功能,子类不一定需要,或者不一定满足!
Alt + Insert: override
9. 多态
即同一方法可以根据发送对象的不同而采用多种不同的行为方式。
一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多
多态存在的条件
- 有继承关系
- 子类重写父类方法
- 父类引用指向子类对象
注意:多态是方法的多态,属性没有多态性。
static 方法,属于类,不属于实例
final 常量
private 方法
都不能重写
instanceof

10. static 关键字
好的,我们来系统地总结 Java 中 static 关键字的全部用法。
1. 静态变量(类变量)
定义:用
static修饰的成员变量,属于类本身,不属于某个对象实例。特点:
- 所有实例共享同一份静态变量(只有一个副本)。
- 在类加载时初始化,生命周期与类相同。
- 可以通过类名直接访问(推荐),也可以通过实例访问(不推荐,易混淆)。
用途:常量(
public static final)、计数器、全局配置等。
class Counter { |
2. 静态方法
定义:用
static修饰的方法,属于类而非实例。特点:
- 可以直接通过类名调用,无需创建对象。
- 不能直接访问非静态成员(变量和方法),因为没有隐式的
this引用。 - 可以访问其他静态成员。
- 不能被重写(override),但可以被隐藏(子类定义同名静态方法)。
用途:工具方法(如
Math.max()、Arrays.sort())、工厂方法、单例模式的getInstance()。
class Utils { |
3. 静态代码块
定义:用
static {}包裹的代码块,在类加载时执行一次。特点:
- 用于初始化静态变量或执行一次性资源加载(如读取配置文件)。
- 可以有多个静态代码块,按书写顺序执行。
- 在类第一次被使用时触发(加载、实例化、访问静态成员等)。
执行顺序:父类静态块 → 子类静态块 → main 方法 → 实例初始化块 → 构造器。
class Database { |
4. 静态内部类(嵌套类)
定义:用
static修饰的内部类,不依赖于外部类的实例。特点:
- 可以独立创建对象:
new Outer.Inner(),无需外部类实例。 - 不能直接访问外部类的非静态成员(因为没有隐式的外部类引用)。
- 可以拥有静态成员(普通内部类不可以)。
- 可以独立创建对象:
用途:辅助类、构建器模式(Builder)、单例(静态内部类方式)。
class Outer { |
5. 静态导入(import static)
- 语法:
import static 包名.类名.静态成员;或import static 包名.类名.*; - 作用:直接使用静态成员名称,无需写类名前缀。
- 适用场景:频繁使用的静态常量或工具方法(如
Math.PI、System.out)。 - 注意:过度使用会降低代码可读性,建议适度。
import static java.lang.Math.PI; |
6. 静态与非静态的区别总结
| 特性 | 静态成员 | 非静态成员 |
|---|---|---|
| 归属 | 类本身 | 实例对象 |
| 内存分配 | 类加载时分配,方法区(或堆中元空间) | 对象创建时分配,堆 |
| 访问方式 | 类名.成员(推荐) | 对象引用.成员 |
| this 引用 | 无 | 有 |
| 能否直接访问非静态 | 否(需通过对象引用) | 能 |
| 生命周期 | 随类加载而生,随类卸载而亡 | 随对象创建而生,随 GC 回收而亡 |
| 重写 | 不可重写(可隐藏) | 可重写 |
7. 注意事项
- 静态方法不能使用
super和this,因为它们依赖实例上下文。 - 静态变量在多线程环境下存在线程安全问题,需要自行同步。
- 静态方法不能被抽象(abstract),也不能被接口中的默认方法使用。
- 静态内部类中不能有静态变量? 错!静态内部类可以有静态成员,而普通内部类不能。
main方法必须是public static void,因为 JVM 在启动时需要调用它而不创建对象。
8. 经典应用场景
| 场景 | 示例 |
|---|---|
| 常量池 | public static final String VERSION = "1.0"; |
| 工具类 | Collections.sort(list) |
| 单例模式(饿汉/静态内部类) | public static Singleton getInstance() |
| 工厂方法 | Calendar.getInstance() |
| 缓存/全局状态 | public static Map<String, Cache> cacheMap |
| 静态代理 | Thread.setDefaultUncaughtExceptionHandler(...) |
11. 抽象类
abstract 修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
抽象类, 不能使用 new 关键字来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明没有方法的实现,它是用来让子类实现的
子类继承抽象类那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
抽象类
- 不能直接创建实例(不能
new抽象类)。 - 可以包含抽象方法(只有声明,没有方法体),也可以包含具体方法(有完整实现)。
- 主要用于定义模板和规范,强制子类实现某些行为。
public abstract class Animal { |
抽象类的成员
抽象类几乎可以拥有普通类的一切成员,甚至更多:
| 成员类型 | 是否可以拥有 | 说明 |
|---|---|---|
| 成员变量 | ✅ 可以 | 可以是实例变量、静态变量、常量(final) |
| 构造器 | ✅ 可以 | 但不能直接 new,供子类通过 super() 调用 |
| 具体方法 | ✅ 可以 | 普通方法、静态方法、final 方法 |
| 抽象方法 | ✅ 零个或多个 | 只有声明,没有方法体,子类必须实现(除非子类也是抽象类) |
| 代码块 | ✅ 可以 | 实例初始化块、静态初始化块 |
| 内部类 | ✅ 可以 | 普通内部类、静态内部类等 |
抽象类可以没有抽象方法
12. 接口
什么是接口(Interface)
接口是 Java 中的一种引用类型,它定义了一组规范(契约),规定实现类必须提供哪些行为(方法),但不关心这些行为如何实现。
你可以把接口理解为一个合同:合同上写了要实现哪些功能(方法签名),但具体怎么做由签合同的类自己决定。
接口的核心特征
方法都是抽象的(Java 8 之前)
接口中的方法默认是public abstract,只有方法签名,没有方法体。不能实例化
不能new一个接口,必须由类来实现它。多实现
一个类可以实现多个接口(弥补 Java 单继承的不足)。成员变量只能是常量
接口中的变量默认是public static final,必须初始化。Java 8 之后新增
default方法:可以有默认实现,实现类可以选择重写或不重写。static方法:属于接口本身,通过接口名调用。
Java 9 之后新增
private方法:供接口内部的 default/static 方法调用,不暴露给外部。
接口的语法
// 定义一个接口 |
接口的主要用途
| 用途 | 说明 |
|---|---|
| 制定规范 | 比如 USB 接口,规定了设备必须支持插拔和数据传输,但不同设备实现不同 |
| 实现多态 | 面向接口编程,提高灵活性和可扩展性 |
| 解耦 | 调用者只依赖接口,不依赖具体实现类,便于替换和维护 |
| 多重继承替代 | 一个类可以实现多个接口,获得多种行为能力 |
| 回调机制 | 比如事件监听器接口,注册后由框架回调 |
接口 vs 抽象类(快速对比)
| 对比项 | 接口 | 抽象类 |
|---|---|---|
| 实例化 | 不能 | 不能 |
| 构造器 | 无 | 可以有 |
| 成员变量 | 只能是 public static final 常量 |
任意类型变量 |
| 方法实现 | 抽象方法 + default/static/private 方法(Java 8+) | 抽象方法 + 具体方法 |
| 继承/实现 | 类可以实现多个接口 | 类只能继承一个抽象类 |
| 设计思想 | “能做什么”(能力契约) | “是什么”(类层次结构) |
经典例子
// 接口:可充电 |
普通类:只有具体实现
抽象类:具体实现和规范(抽象方法)都有!
接口:只有规范!
内部类
内部类就是在一个类的内部在定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。
1. 成员内部类
成员内部类是定义在另一个类(外部类)内部的、不带 static 修饰的类。它是外部类的一个成员,类似于外部类的属性或方法。
成员内部类依赖外部类
public class Out { |
public static void main(String[] args) { |
当成员内部类中的属性或者方法和外部类中的一样时,在成员内部类中要使用
外部类.this.属性或外部类.this.方法访问外部类的属性,方法外部类访问内部类:外部类必须通过内部类对象来访问其成员(即使是私有的)
成员内部类的访问修饰符
public class Outer { |
2. 静态内部类
静态内部类是用 static 修饰的、定义在另一个类内部的类。它本质上是外部类的一个静态成员,不依赖于外部类的实例。
1. 基本定义
public class Outer { |
2. 核心特点
| 特点 | 说明 |
|---|---|
| 不依赖外部类实例 | 可以直接 new Outer.StaticInner(),无需外部类对象 |
| 只能访问外部类的静态成员 | 不能直接访问外部类的实例变量和方法(因为没有隐式的 Outer.this 引用) |
| 可以有静态成员 | 与普通类一样,可以定义 static 变量、方法、常量等 |
| 访问修饰符 | 同样可以使用 public、protected、default、private |
| 编译产物 | 生成 Outer$StaticInner.class 文件 |
3. 创建对象
// 方式一:通过外部类名直接创建(推荐) |
4. 访问规则
- 静态内部类内部:可以直接访问外部类的所有静态成员(包括
private的)。 - 访问外部类实例成员:必须通过外部类对象引用(例如
new Outer().instanceMethod())。 - 外部类访问静态内部类:与访问其他静态成员类似,通过
Outer.StaticInner使用。
5. 静态内部类 vs 成员内部类
| 对比项 | 静态内部类 | 成员内部类 |
|---|---|---|
| 是否需要外部类实例 | 不需要 | 需要 |
| 能否访问外部类实例成员 | 不能(需通过对象) | 能(直接访问) |
| 能否有静态成员 | 能 | 不能(除 static final 常量) |
| 创建方式 | new Outer.StaticInner() |
outer.new Inner() |
| 隐式引用 | 无 | 持有 Outer.this |
| 内存泄漏风险 | 低(无外部类引用) | 高(持有外部类引用) |
6. 典型应用场景
| 场景 | 说明 |
|---|---|
| Builder 模式 | 经典实现:new Pizza.Builder().setSize(LARGE).build() |
| 单例模式(静态内部类方式) | 线程安全、延迟加载的单例实现 |
| 辅助工具类 | 与外部类逻辑紧密相关,但不需要访问外部类实例 |
| 数据结构节点 | 如链表节点 Node 定义为静态内部类,节省内存 |
7. 经典示例:单例模式
public class Singleton { |
8. 注意事项
- 命名:虽然叫“静态内部类”,但官方术语是“静态嵌套类”(Static Nested Class),因为它更像一个顶层类被放在了另一个类里面。
- 序列化:静态内部类实现
Serializable时,不会自动包含外部类的引用,序列化更安全。 - 与普通类的区别:除了名字空间被限定在外部类内,静态内部类本质上就是一个普通的顶层类(可以独立存在)。
3. 局部内部类
局部内部类是定义在方法、构造器或代码块中的类,它只在所属的局部范围内可见。
1. 基本定义
public class Outer { |
2. 核心特点
| 特点 | 说明 |
|---|---|
| 作用域受限 | 只在定义它的方法/代码块内有效,外部无法访问 |
| 不能有访问修饰符 | 不能用 public、protected、private、static |
| 可以访问外部类的所有成员 | 包括 private 成员 |
| 可以访问所在方法的局部变量 | 但局部变量必须是 final 或事实上不可变(Java 8+ 要求 effectively final) |
| 不能有静态成员 | 除了 static final 常量 |
| 编译产物 | 生成 Outer$1LocalInner.class(数字表示序号) |
3. 为什么局部变量必须是 final 或 effectively final?
局部内部类对象可能在方法执行完毕后仍然存活(例如作为返回值或传递给事件监听器),而此时方法的局部变量已经销毁。为了确保内部类能访问到正确的值,Java 要求局部变量是 final 或 effectively final(赋值后不再改变),编译器会将变量的值复制到内部类中。
public class Outer { |
4. 典型应用场景
| 场景 | 说明 |
|---|---|
| 复杂算法辅助类 | 只在某个方法内需要的临时数据结构或逻辑 |
| 事件处理器 | 在 GUI 编程中,为某个组件创建一次性的事件监听器 |
| 测试辅助 | 在单元测试方法中定义临时的桩类 |
5. 完整示例
public class Calculator { |
6. 局部内部类 vs 匿名内部类
| 对比项 | 局部内部类 | 匿名内部类 |
|---|---|---|
| 是否有名字 | 有(可以定义多个方法) | 无(只能有一个实例) |
| 可重用性 | 可以在方法内多次实例化 | 只能实例化一次(通常) |
| 语法复杂度 | 相对简单 | 更简洁(适合单次使用) |
| 典型用途 | 需要多个方法或多次实例化 | 一次性回调/事件监听 |
7. 注意事项
- 不能声明为
static:因为局部内部类本身就是方法内的局部实体,没有静态的概念。 - 不能有静态初始化块:同上。
- 接口不能定义为局部内部类:局部内部类只能是类,不能是接口(接口总是顶层的或静态的)。
- 枚举不能定义为局部内部类:枚举类型不能定义在方法内部。
4. 匿名内部类(Anonymous Inner Class)
匿名内部类是没有显式名字的内部类,它在创建对象的同时定义类的实现。通常用于需要快速创建某个接口或抽象类的实例,且只用一次的场景。
1. 基本语法
// 基于接口 |
2. 核心特点
| 特点 | 说明 |
|---|---|
| 没有名字 | 定义时直接 new 接口/类() { ... },不写类名 |
| 必须继承一个类或实现一个接口 | 不能凭空定义,必须基于已有的类型 |
| 只能创建一个实例 | 因为没名字,无法在其他地方再次 new 这个类 |
| 不能有构造器 | 匿名内部类没有构造器(但可以调用父类的构造器) |
| 可以定义自己的成员 | 变量、方法、代码块都可以,但不能有静态成员 |
| 可以访问外部类成员 | 与局部内部类规则相同 |
| 访问局部变量需 final/effectively final | 与方法内的局部内部类一致 |
3. 典型应用场景
| 场景 | 示例 |
|---|---|
| 事件监听器 | button.addActionListener(new ActionListener() {...}) |
| 线程创建 | new Thread(new Runnable() {...}).start() |
| 比较器 | Arrays.sort(arr, new Comparator<Integer>() {...}) |
| 回调函数 | 异步操作完成后执行的逻辑 |
| 临时适配器 | 快速实现一个接口,覆盖少量方法 |
4. 完整示例
import javax.swing.*; |
5. 匿名内部类 vs 局部内部类
| 对比项 | 匿名内部类 | 局部内部类 |
|---|---|---|
| 是否有名字 | 无 | 有 |
| 可重用性 | 仅创建一次 | 可在方法内多次实例化 |
| 定义位置 | 在 new 表达式中 |
在方法/代码块中 |
| 适用场景 | 一次性回调、事件处理 | 需要多次使用或包含多个方法的临时类 |
| 可读性 | 简洁但较长时难读 | 较清晰,适合复杂逻辑 |
6. 注意事项
- 不能有构造器:但可以调用父类带参数的构造器(通过
super(参数)的方式在匿名内部类中实现?实际上匿名内部类不能显式写构造器,但可以在实例初始化块中完成初始化)。 - 编译产物:生成
Outer$1.class、Outer$2.class等(数字递增)。 - 内存泄漏风险:匿名内部类持有外部类引用(如果是在非静态上下文中定义的),可能导致外部类对象无法被 GC 回收。
- Lambda 表达式的替代:对于函数式接口(只有一个抽象方法),Java 8+ 推荐使用 Lambda 表达式代替匿名内部类,更简洁。
// Lambda 替代匿名内部类 |
六、异常
1. 什么是异常(Exception)
异常是程序运行过程中出现的不正常情况,它打断了正常的指令流。Java 通过异常处理机制让开发者能够优雅地捕获和处理这些错误,而不是让程序直接崩溃。
2. 异常的分类(Throwable 体系)
Java 中所有异常类都继承自 Throwable,主要分为三大类
Throwable |

| 类别 | 特点 | 是否需要处理 | 示例 |
|---|---|---|---|
| Error | 系统级错误,程序无法恢复 | 不强制处理 | OutOfMemoryError、StackOverflowError |
| RuntimeException | 编程错误,如空指针、越界 | 不强制处理(可选) | NullPointerException、ArithmeticException |
| Checked Exception | 外部因素导致的异常,如文件不存在 | 必须处理(try-catch 或 throws) | IOException、ClassNotFoundException |
3. 异常处理机制
try-catch-finally 块
try { |
throws 声明
如果方法不打算处理异常,可以在方法签名上声明抛出,交给调用者处理:
public void readFile() throws IOException { |
throw 手动抛出异常
if (age < 0) { |
try-with-resources(Java 7+)
自动关闭实现了 AutoCloseable 的资源,省去 finally 块:
try (FileReader reader = new FileReader("test.txt")) { |
4. 常见异常举例
运行时异常
编译时不强制处理,通常由程序逻辑错误引起。
| 异常类型 | 常见原因 |
|---|---|
NullPointerException |
调用 null 对象的方法或访问其属性 |
ArrayIndexOutOfBoundsException |
数组下标越界(索引为负或 ≥ 数组长度) |
ArithmeticException |
整数除法中除数为零 |
NumberFormatException |
尝试将字符串转换为数字时格式不正确(如 "abc" 转 int) |
ClassCastException |
强制类型转换时目标类型与实际类型不兼容 |
IllegalArgumentException |
方法接收到不合法的参数(如负数作为年龄) |
IndexOutOfBoundsException |
索引越界(数组、列表等的通用父类) |
ConcurrentModificationException |
在使用迭代器遍历集合时,集合的结构被并发修改(增删元素),且未使用迭代器自身的 remove 方法 |
UnsupportedOperationException |
调用了一个集合或对象不支持的操作,例如对 Arrays.asList() 返回的固定长度列表调用 add 或 remove |
检查型异常
编译时必须处理(try-catch 或 throws),通常由外部环境或资源问题引起。
| 异常类型 | 常见原因 |
|---|---|
FileNotFoundException |
试图打开的文件不存在 |
IOException |
输入输出操作失败(如读写文件中断) |
SQLException |
数据库连接或查询错误 |
ClassNotFoundException |
找不到指定的类(反射加载时) |
InterruptedException |
线程在等待/休眠时被中断 |
ParseException |
解析字符串为日期等格式时失败 |
5. 自定义异常
可以继承 Exception(Checked)或 RuntimeException(Unchecked)来创建业务相关的异常:
public class InsufficientBalanceException extends RuntimeException { |
6. 最佳实践
- 不要捕获
Error:系统级错误无法恢复,应该让程序终止。 - 尽量使用更具体的异常类型:不要笼统地
catch (Exception e)。 - 不要忽略异常:空的 catch 块会让问题难以排查。
- 尽早抛出,晚点捕获:在合适的层级处理异常,底层抛出让上层决策。
- 使用日志记录异常:
e.printStackTrace()在生产环境中不好用,建议用日志框架(Log4j、SLF4J)。 - 资源使用 try-with-resources:避免忘记关闭资源导致泄漏。

七、常用类
Object类

java.lang.Object 是 Java 中所有类的根类(超类)。每个类都直接或间接继承自 Object,因此 Object 类中的方法在所有 Java 对象中都可用。
常用方法一览
| 方法 | 作用 | 说明 |
|---|---|---|
toString() |
返回对象的字符串表示 | 默认输出 类名@哈希码十六进制,通常建议重写 |
equals(Object obj) |
判断两个对象是否相等 | 默认比较引用地址(==),通常需要重写以比较内容 |
hashCode() |
返回对象的哈希码 | 与 equals 保持一致:相等的对象必须有相同的哈希码 |
getClass() |
返回对象的运行时类(Class<?> 对象) |
可用于反射,不能重写 |
clone() |
创建并返回对象的一个副本 | 需实现 Cloneable 接口,否则抛出 CloneNotSupportedException |
finalize() |
垃圾回收前调用(已废弃) | Java 9 起标记为 @Deprecated,不建议使用 |
notify() |
唤醒一个在此对象监视器上等待的线程 | 用于线程间通信 |
notifyAll() |
唤醒所有在此对象监视器上等待的线程 | 同上 |
wait() |
让当前线程进入等待状态,直到被唤醒或超时 | 有三个重载版本:wait()、wait(long timeout)、wait(long timeout, int nanos) |
各方法详解
1. toString()
public String toString() |
- 默认实现:
getClass().getName() + "@" + Integer.toHexString(hashCode()) - 用途:方便调试和日志输出,几乎所有 Java Bean 都会重写此方法以展示有意义的内容。
2. equals(Object obj)
public boolean equals(Object obj) |
- 默认实现:
return (this == obj);(比较引用地址) - 重写原则:自反性、对称性、传递性、一致性、非空性(
x.equals(null)返回false) - 与
hashCode的约定:如果两个对象equals返回true,它们的hashCode必须相同;反之不必然。
3. hashCode()
public int hashCode() |
- 默认实现:通常是对象的内存地址转换成的整数(但非绝对)
- 用途:用于散列数据结构(如
HashMap、HashSet)的快速查找 - 重写规则:与
equals保持一致,尽量让不同对象的哈希码均匀分布以减少碰撞。
4. getClass()
public final native Class<?> getClass() |
- 返回当前对象的运行时类对象,可用于反射获取类的信息(方法、字段、构造器等)。
- 不能重写,是
final方法。
5. clone()
protected native Object clone() throws CloneNotSupportedException |
- 创建并返回对象的浅拷贝(shallow copy)。
- 必须实现
Cloneable接口(标记接口),否则调用时会抛出CloneNotSupportedException。 - 若需要深拷贝,需自行重写该方法。
使用 clone() 实现深拷贝的完整 Java 示例。我们将创建一个 Person 类,它包含一个引用类型的成员 Address。通过重写 clone() 方法,我们手动复制 Address 对象,从而实现深拷贝。
// Address 类,也需要实现 Cloneable 并重写 clone() |
运行结果:

6. finalize()(已废弃)
|
- 在垃圾回收器回收对象之前调用一次,用于释放非 Java 资源(如文件句柄)。
- 不推荐使用:调用时机不确定、性能开销大、可能导致对象复活等问题。建议使用
try-with-resources或Cleaner(Java 9+)替代。
7. notify()、notifyAll()、wait()
这三个方法用于线程间的协作(生产者-消费者模式等),必须在同步块(synchronized)内调用,否则会抛出 IllegalMonitorStateException。
wait():让当前线程释放锁并进入等待状态,直到其他线程调用notify()或notifyAll()唤醒它。notify():随机唤醒一个在此对象监视器上等待的线程。notifyAll():唤醒所有在此对象监视器上等待的线程。
其他重要方法
| 方法 | 说明 |
|---|---|
registerNatives() |
私有静态方法,用于注册本地方法,由 JVM 在类加载时调用 |
finalize() |
如上所述,已废弃 |
wait(long timeout, int nanos) |
更精细的超时控制,纳秒部分会被近似处理 |
Math类

java.lang.Math 是 Java 提供的数学工具类,包含用于执行基本数学运算的静态方法,如指数、对数、平方根、三角函数等。该类是 final 的,构造器私有,因此不能实例化,所有方法均通过类名直接调用。
常用常量
| 常量 | 说明 |
|---|---|
Math.PI |
圆周率 π(≈3.141592653589793) |
Math.E |
自然常数 e(≈2.718281828459045) |
常用方法分类
1. 三角函数与角度转换
| 方法 | 说明 |
|---|---|
sin(double a) |
正弦(弧度) |
cos(double a) |
余弦(弧度) |
tan(double a) |
正切(弧度) |
asin(double a) |
反正弦(返回弧度) |
acos(double a) |
反余弦(返回弧度) |
atan(double a) |
反正切(返回弧度) |
toRadians(double angdeg) |
角度转弧度 |
toDegrees(double angrad) |
弧度转角度 |
2. 指数、对数与幂运算
| 方法 | 说明 |
|---|---|
exp(double a) |
e^a |
log(double a) |
自然对数 ln(a) |
log10(double a) |
以10为底的对数 |
pow(double a, double b) |
a^b |
sqrt(double a) |
平方根 |
cbrt(double a) |
立方根 |
hypot(double x, double y) |
√(x²+y²),防止中间溢出 |
3. 取整运算
| 方法 | 说明 |
|---|---|
ceil(double a) |
向上取整(返回 double) |
floor(double a) |
向下取整(返回 double) |
rint(double a) |
四舍五入取整,返回最接近的整数(double),遇.5时取偶数 |
round(float a) |
四舍五入返回 int |
round(double a) |
四舍五入返回 long |
4. 绝对值、最大值、最小值
| 方法 | 说明 |
|---|---|
abs(int/long/float/double a) |
绝对值 |
max(int/long/float/double a, int/long/float/double b) |
较大值 |
min(int/long/float/double a, int/long/float/double b) |
较小值 |
5. 随机数与符号
| 方法 | 说明 |
|---|---|
random() |
返回 [0.0, 1.0) 的随机 double |
signum(double d) |
返回符号函数:负数→-1.0,零→0.0,正数→1.0 |
copySign(double magnitude, double sign) |
用 sign 的符号替换 magnitude 的符号 |
6. 其他实用方法
| 方法 | 说明 |
|---|---|
IEEEremainder(double f1, double f2) |
IEEE 754 标准的余数运算 |
nextAfter(double start, double direction) |
返回 start 向 direction 方向的下一个浮点数 |
nextUp(double d) |
返回 d 正方向的下一个浮点数 |
ulp(double d) |
返回 d 的最后一个单位精度(unit in the last place) |
使用示例
System.out.println(Math.PI); // 3.141592653589793 |
注意事项
- 所有方法均为
public static,直接通过Math.方法名()调用。 - 涉及浮点数运算时注意精度问题(如
0.1 + 0.2的结果不是精确的 0.3)。 random()返回的是伪随机数,如果需要更安全的随机数,可使用java.security.SecureRandom。- 对于整数运算,
Math.abs(Integer.MIN_VALUE)会返回负数(溢出),需注意边界情况。
Random类

java.util.Random 是 Java 提供的伪随机数生成器,用于生成各种类型的随机数(如 int、long、float、double、boolean 等)。与 Math.random() 相比,Random 类功能更丰富、更灵活。
核心特点
- 伪随机:基于线性同余算法生成,可通过种子(seed)控制随机序列。
- 线程不安全:多线程环境下建议使用
ThreadLocalRandom(Java 7+)或SecureRandom。 - 可重复性:给定相同种子,生成的随机数序列完全相同(便于测试复现)。
常用构造器
| 构造器 | 说明 |
|---|---|
Random() |
以当前系统时间(纳秒)作为种子,每次运行结果不同 |
Random(long seed) |
指定种子,每次运行生成相同的随机序列 |
常用方法
| 方法 | 返回类型 | 说明 |
|---|---|---|
nextInt() |
int |
返回一个随机 int(范围:-2³¹ ~ 2³¹-1) |
nextInt(int bound) |
int |
返回 [0, bound) 之间的随机 int |
nextLong() |
long |
返回一个随机 long |
nextFloat() |
float |
返回 [0.0, 1.0) 之间的随机 float |
nextDouble() |
double |
返回 [0.0, 1.0) 之间的随机 double |
nextBoolean() |
boolean |
返回一个随机 boolean |
nextBytes(byte[] bytes) |
void |
用随机字节填充给定的 byte 数组 |
setSeed(long seed) |
void |
重新设置种子 |
doubles() / ints() / longs() |
DoubleStream / IntStream / LongStream |
生成无限随机数流(Java 8+) |
使用示例
import java.util.Random; |
Random vs Math.random()
| 对比项 | Math.random() |
Random 类 |
|---|---|---|
| 返回类型 | double [0.0, 1.0) |
多种类型(int、long、double、boolean 等) |
| 灵活性 | 单一功能 | 丰富的方法,可指定范围、种子 |
| 性能 | 内部使用 Random 实例,稍慢 |
更高效(可复用对象) |
| 种子控制 | 无法指定种子 | 可指定种子实现可重复性 |
线程安全替代方案
ThreadLocalRandom(Java 7+):每个线程独立的随机数生成器,性能更高,适合并发场景。
int r = ThreadLocalRandom.current().nextInt(100); |
SecureRandom:密码学安全的强随机数生成器,用于加密场景(如生成令牌、密钥)。
SecureRandom secureRandom = new SecureRandom(); |
一句话总结
Random 是 Java 中生成伪随机数的工具类,支持多种数据类型和范围控制,可通过种子复现随机序列,适用于大多数非安全场景的随机需求。
File类

java.io.File 是 Java 中用于表示文件和目录路径名 的抽象表示。它提供了对文件和目录的基本操作,如创建、删除、重命名、获取属性、遍历目录等,但不包含读写文件内容 的功能(读写内容需要使用 FileInputStream、FileOutputStream、FileReader、FileWriter 等流类)。
核心概念
- File 对象 代表一个路径(可以是文件或目录),不一定对应磁盘上真实存在的文件。
- 平台无关性:内部使用系统相关的路径分隔符(Windows 为
\,Linux/macOS 为/),但可以通过File.separator获取当前系统的分隔符。 - 不可变:一旦创建,File 对象表示的路径不会改变,但可以调用方法创建新的 File 对象表示其他路径。
常用构造器
| 构造器 | 说明 |
|---|---|
File(String pathname) |
通过路径名字符串创建 File 对象 |
File(String parent, String child) |
通过父路径和子路径创建 |
File(File parent, String child) |
通过父 File 对象和子路径创建 |
File(URI uri) |
通过 URI 创建(如 file:///C:/test.txt) |
常用方法分类
1. 路径与名称操作
| 方法 | 说明 |
|---|---|
getName() |
返回文件名或目录名 |
getPath() |
返回构造时传入的路径字符串 |
getAbsolutePath() |
返回绝对路径字符串 |
getCanonicalPath() |
返回规范化的绝对路径(去除 .、.. 等,可能抛出 IOException) |
toURI() |
转换为 file: 协议的 URI |
getParent() / getParentFile() |
返回父路径字符串 / 父 File 对象 |
2. 文件/目录状态判断
| 方法 | 说明 |
|---|---|
exists() |
文件或目录是否存在 |
isFile() |
是否为文件(不是目录) |
isDirectory() |
是否为目录 |
isHidden() |
是否隐藏 |
canRead() |
是否可读 |
canWrite() |
是否可写 |
canExecute() |
是否可执行 |
length() |
文件大小(字节),目录返回 0 或不确定 |
lastModified() |
最后修改时间戳(毫秒) |
3. 创建与删除
| 方法 | 说明 |
|---|---|
createNewFile() |
创建新文件(不存在时创建,返回 boolean) |
mkdir() |
创建单级目录 |
mkdirs() |
创建多级目录(包括缺失的父目录) |
delete() |
删除文件或空目录 |
deleteOnExit() |
虚拟机退出时删除(用于临时文件清理) |
4. 目录遍历
| 方法 | 说明 |
|---|---|
list() |
返回目录下所有文件名/目录名的字符串数组 |
listFiles() |
返回目录下所有 File 对象数组 |
list(FilenameFilter filter) |
带过滤器的文件名列表 |
listFiles(FileFilter filter) |
带过滤器的 File 对象列表 |
listRoots() |
静态方法,返回系统根目录(如 Windows 的 C:\、D:\) |
5. 重命名与临时文件
| 方法 | 说明 |
|---|---|
renameTo(File dest) |
重命名/移动文件或目录(跨平台行为不一致) |
createTempFile(String prefix, String suffix) |
静态方法,在默认临时目录创建临时文件 |
createTempFile(String prefix, String suffix, File directory) |
在指定目录创建临时文件 |
使用示例
import java.io.File; |
递归遍历目录及子目录
package com.it.day02; |
注意事项
- 不处理文件内容:File 类只管理路径和元数据,读写文件内容需使用流(
FileInputStream/FileOutputStream/FileReader/FileWriter)或Scanner、Files(Java 7+ NIO.2)。 - 安全性:某些操作(如创建、删除、读取属性)可能受系统权限限制,会抛出
SecurityException。 - 跨平台路径分隔符:推荐使用
File.separator或File.separatorChar拼接路径,避免硬编码/或\。 - Java 7+ 推荐使用
java.nio.file.Path和Files类:功能更强大、异常处理更好、支持符号链接等高级特性。File 类虽未废弃,但新项目建议优先考虑 NIO.2 API。
包装类
Java 为八种基本数据类型提供了对应的包装类,使基本类型能够以对象的形式参与面向对象的操作(如放入集合、泛型、反射等)。
| 基本类型 | 包装类 | 父类 |
|---|---|---|
byte |
Byte |
Number |
short |
Short |
Number |
int |
Integer |
Number |
long |
Long |
Number |
float |
Float |
Number |
double |
Double |
Number |
char |
Character |
Object |
boolean |
Boolean |
Object |
核心特性
- 自动装箱/拆箱(Java 5+):编译器自动在基本类型和包装类之间转换。
Integer a = 10; // 自动装箱:Integer.valueOf(10) |
- 缓存机制:部分包装类对常用值进行缓存,节省内存。
Integer:默认缓存 -128 ~ 127(可通过 JVM 参数调整上限)。Long:缓存 -128 ~ 127。Short、Byte:缓存全部值(-128 ~ 127)。Character:缓存 0 ~ 127(ASCII 字符)。Boolean:缓存TRUE和FALSE两个常量。Float、Double:无缓存。
常用方法(以 Integer 为例,其他类似)
1. 构造与转换
| 方法 | 说明 |
|---|---|
| Integer(int value) | 构造(已不推荐,优先用 valueOf) |
Integer(String s) |
从字符串构造(可能抛出 NumberFormatException) |
valueOf(int i) |
返回 Integer 实例,利用缓存(推荐) |
valueOf(String s) |
从字符串解析并返回 Integer 实例 |
parseInt(String s) |
将字符串解析为 int 基本类型 |
parseInt(String s, int radix) |
按指定进制解析 |
intValue() |
返回 int 值(拆箱) |
longValue()、floatValue()、doubleValue() |
转换为其他数值类型 |
2. 字符串与进制转换
| 方法 | 说明 |
|---|---|
toString() |
返回十进制字符串 |
toString(int i) |
静态方法,将 int 转为字符串 |
toBinaryString(int i) |
二进制字符串(无前导零) |
toOctalString(int i) |
八进制字符串 |
toHexString(int i) |
十六进制字符串(小写) |
parseInt(String s, int radix) |
按 radix 进制解析字符串 |
3. 比较与判断
| 方法 | 说明 |
|---|---|
equals(Object obj) |
比较值是否相等(注意:与 == 区别,== 比较引用) |
compareTo(Integer another) |
比较大小,返回 -1、0、1 |
compare(int x, int y) |
静态方法,比较两个 int 值 |
MAX_VALUE / MIN_VALUE |
常量,表示 int 的取值范围 |
SIZE |
位数(32) |
BYTES |
字节数(4) |
4. 其他实用方法
| 方法 | 说明 |
|---|---|
reverse(int i) |
按位反转 |
rotateLeft(int i, int distance) |
循环左移 |
rotateRight(int i, int distance) |
循环右移 |
highestOneBit(int i) |
返回最高位 1 的值(如 12→8) |
lowestOneBit(int i) |
返回最低位 1 的值 |
numberOfLeadingZeros(int i) |
前导零个数 |
bitCount(int i) |
二进制中 1 的个数 |
Character 特有方法
| 方法 | 说明 |
|---|---|
isLetter(char ch) |
是否为字母 |
isDigit(char ch) |
是否为数字 |
isWhitespace(char ch) |
是否为空白字符 |
isUpperCase(char ch) |
是否大写 |
isLowerCase(char ch) |
是否小写 |
toUpperCase(char ch) |
转大写 |
toLowerCase(char ch) |
转小写 |
getNumericValue(char ch) |
获取字符代表的数值(如 ‘9’→9) |
Boolean 特有方法
| 方法 | 说明 |
|---|---|
booleanValue() |
返回基本类型 boolean |
parseBoolean(String s) |
字符串解析为 boolean(”true”→true,其余→false) |
logicalAnd(boolean a, boolean b) |
逻辑与(Java 7+) |
logicalOr(boolean a, boolean b) |
逻辑或 |
logicalXor(boolean a, boolean b) |
逻辑异或 |
注意事项
==与equals陷阱:自动装箱后,Integer a = 127; Integer b = 127; a == b返回true(因为缓存),但Integer a = 128; Integer b = 128; a == b返回false(不同对象)。应始终使用equals()比较值。- 空指针风险:包装类可为
null,自动拆箱时可能抛出NullPointerException。 - 性能:频繁的装箱拆箱会产生额外对象,大量循环中建议使用基本类型。
Date类
java.util.Date 是 Java 最早提供的日期时间类(JDK 1.0),用于表示特定的瞬间(精确到毫秒)。但它存在许多设计缺陷,大部分构造和方法已在 Java 8 之后被标记为 @Deprecated(已弃用),官方推荐使用 java.time 包(Java 8+)替代。
核心特性
| 特性 | 说明 |
|---|---|
| 表示时刻 | 从 1970-01-01 00:00:00 GMT 开始的毫秒数(时间戳) |
| 可变性 | 可变(setTime() 等方法可修改,线程不安全) |
| 已弃用 | 大部分构造和方法(如 getYear()、getMonth()、setHours() 等)已弃用 |
| 遗留兼容 | 仍广泛存在于旧系统和 JDBC 中 |
构造方法
| 构造方法 | 说明 |
|---|---|
Date() |
当前时间(精确到毫秒) |
Date(long date) |
根据时间戳(毫秒)创建 |
Date(int year, int month, int day) |
已弃用,年份从1900开始,月份0~11 |
Date(String s) |
已弃用,格式不易控 |
推荐用法:
Date now = new Date(); // 当前时间 |
常用方法(未弃用部分)
| 方法 | 说明 |
|---|---|
getTime() |
返回时间戳(毫秒) |
setTime(long time) |
设置时间戳 |
before(Date when) |
是否早于指定时间 |
after(Date when) |
是否晚于指定时间 |
compareTo(Date another) |
比较先后顺序 |
equals(Object obj) |
比较时间是否相等 |
toString() |
返回默认格式字符串(如 Sun Oct 18 14:35:22 CST 2026) |
clone() |
克隆对象 |
示例代码
import java.util.Date; |
Date 的缺陷与弃用原因
- 年份偏移:
getYear()返回year - 1900,月份从 0 开始(0=一月),容易出错。 - 线程不安全:
Date是可变的,多线程下修改可能引发问题。 - 命名混乱:
Date既表示日期又包含时间,实际上表示的是时刻。 - 国际化差:
toString()输出英文/依赖系统区域,难以定制格式。 - 缺乏日期计算:加减天数、月份等需要配合
Calendar,复杂且易错。
与 Calendar 和 SimpleDateFormat 的配合(旧式写法)
虽然 Date 本身已弃用,但在 Java 8 之前,通常这样使用:
import java.text.SimpleDateFormat; |
Java 8+ 现代替代方案(推荐)
java.time 包提供了不可变、线程安全、设计清晰的日期时间 API:
| 旧类 | 新类 | 说明 |
|---|---|---|
Date |
Instant |
表示时间戳(时刻) |
Date + SimpleDateFormat |
LocalDateTime + DateTimeFormatter |
不带时区的日期时间 |
Calendar |
LocalDate / LocalTime / ZonedDateTime |
日期、时间、带时区时间 |
现代写法示例:
import java.time.LocalDateTime; |
什么时候仍然使用 Date?
- 遗留系统维护:老项目中大量使用
Date和Calendar。 - JDBC 交互:
java.sql.Date、java.sql.Timestamp继承自java.util.Date,部分框架仍依赖。 - 第三方库兼容:某些旧库方法参数要求
Date类型。
在这种情况下,可以在新旧 API 之间转换:
// Date ↔ Instant |
一句话总结
Date 是 Java 最早的日期时间类,表示时刻(毫秒时间戳),但因设计缺陷(可变、月份偏移、国际化差)已被弃用,新项目应优先使用 java.time 包(LocalDateTime、Instant 等);仅在维护旧代码或与遗留 API 交互时保留使用。
Java 8 日期时间类(java.time 包)
Java 8 引入了全新的日期时间 API,位于 java.time 包中,解决了旧版 Date 和 Calendar 的诸多缺陷(可变、线程不安全、设计混乱等)。新 API 基于 ISO-8601 标准,所有核心类都是不可变且线程安全 的。
核心类总览
| 类 | 说明 | 示例值 |
|---|---|---|
LocalDate |
日期(不含时间和时区) | 2026-10-18 |
LocalTime |
时间(不含日期和时区) | 14:35:22.123 |
LocalDateTime |
日期+时间(不含时区) | 2026-10-18T14:35:22 |
Instant |
时间戳(从1970-01-01T00:00:00Z开始的纳秒数) | 2026-10-18T06:35:22Z |
Duration |
时间段(基于秒/纳秒,用于时间差) | PT1H30M(1小时30分) |
Period |
日期间隔(基于年月日) | P1Y2M3D(1年2月3天) |
ZonedDateTime |
带时区的日期时间 | 2026-10-18T14:35:22+08:00[Asia/Shanghai] |
ZoneId |
时区标识符 | Asia/Shanghai、UTC |
DateTimeFormatter |
日期时间的格式化与解析 | 模式如 yyyy-MM-dd HH:mm:ss |
1. LocalDate – 日期
// 创建 |
2. LocalTime – 时间
LocalTime now = LocalTime.now(); // 当前时间(含毫秒) |
3. LocalDateTime – 日期+时间
LocalDateTime now = LocalDateTime.now(); |
4. Instant – 时间戳
Instant now = Instant.now(); // 当前UTC时间戳 |
常用于记录事件发生的时间点,与 Date 互转:
Date date = Date.from(Instant.now()); |
5. Duration – 时间间隔(秒/纳秒)
LocalTime start = LocalTime.of(10, 0); |
6. Period – 日期间隔(年/月/日)
LocalDate birth = LocalDate.of(2000, 1, 1); |
7. DateTimeFormatter – 格式化与解析
// 预定义格式 |
常用模式字母:
| 字母 | 含义 | 示例 |
|---|---|---|
| yyyy | 四位年份 | 2026 |
| MM | 两位月份 | 10 |
| dd | 两位日期 | 18 |
| HH | 24小时制小时 | 14 |
| mm | 分钟 | 35 |
| ss | 秒 | 22 |
| SSS | 毫秒 | 123 |
8. ZonedDateTime – 带时区的日期时间
// 当前时区 |
新旧API互转
| 旧类 → 新类 | 新类 → 旧类 |
|---|---|
new Date().toInstant() |
Date.from(instant) |
calendar.toInstant() |
GregorianCalendar.from(zdt) |
java.sql.Date.valueOf(localDate) |
localDate.toEpochDay() 再构造 |
Timestamp.valueOf(localDateTime) |
Timestamp.from(instant) |
使用建议
- 纯日期(生日、纪念日)→
LocalDate - 纯时间(营业时间、闹钟)→
LocalTime - 完整时间点(订单时间、日志)→
LocalDateTime(若需时区则用ZonedDateTime) - 时间戳(记录事件顺序)→
Instant - 时间差(耗时统计)→
Duration - 日期间隔(年龄、合同有效期)→
Period - 格式化/解析 →
DateTimeFormatter
一句话总结
Java 8 的 java.time 包提供了不可变、线程安全、设计清晰的日期时间类,包括 LocalDate、LocalTime、LocalDateTime、Instant、Duration、Period 和 DateTimeFormatter,完全取代了旧的 Date 和 Calendar,是现代 Java 开发的标配。
String类
java.lang.String 是 Java 中最常用的类之一,用于表示不可变的字符序列。一旦创建,String 对象的内容就不能被更改(任何看似修改的操作都会返回一个新的 String 对象)。
核心特性
| 特性 | 说明 |
|---|---|
| 不可变性 | 字符串内容不可变,保证了线程安全和哈希缓存的高效 |
| 字符串常量池 | 字面量字符串(如 "hello")存储在堆中的字符串常量池,相同内容的字面量复用同一个对象 |
| final 类 | 不能被继承 |
| 实现接口 | Serializable、Comparable<String>、CharSequence |
创建方式
// 方式一:字面量(推荐,会放入常量池) |
注意:s1 == s2 返回 false,因为字面量和 new 创建的是不同对象。应使用 equals() 比较内容。
常用方法分类
1. 比较方法
| 方法 | 说明 |
|---|---|
equals(Object obj) |
比较内容是否相等(区分大小写) |
equalsIgnoreCase(String str) |
忽略大小写比较 |
compareTo(String str) |
按字典顺序比较,返回 int |
compareToIgnoreCase(String str) |
忽略大小写的字典比较 |
contentEquals(CharSequence cs) |
与 CharSequence 比较内容 |
2. 查找与判断
| 方法 | 说明 |
|---|---|
length() |
返回字符串长度 |
isEmpty() |
是否为空字符串(长度为0) |
isBlank() |
是否为空或仅含空白字符(Java 11+) |
charAt(int index) |
返回指定索引处的 char |
indexOf(String str) |
返回子串首次出现的索引,未找到返回 -1 |
lastIndexOf(String str) |
返回子串最后一次出现的索引 |
contains(CharSequence s) |
是否包含指定子串 |
startsWith(String prefix) |
是否以指定前缀开头 |
endsWith(String suffix) |
是否以指定后缀结尾 |
matches(String regex) |
是否匹配正则表达式 |
3. 截取与提取
| 方法 | 说明 |
|---|---|
substring(int beginIndex) |
从 beginIndex 截取到末尾 |
substring(int begin, int end) |
截取 [begin, end) 范围的子串 |
split(String regex) |
按正则分割为字符串数组 |
toCharArray() |
转换为 char 数组 |
getBytes() |
转换为字节数组(默认编码) |
chars() |
返回 IntStream(每个字符的 Unicode 码点) |
4. 转换与替换
| 方法 | 说明 |
|---|---|
toUpperCase() |
转大写 |
toLowerCase() |
转小写 |
trim() |
去除首尾空白字符 |
strip() |
去除首尾空白(Java 11+,支持 Unicode) |
replace(char old, char new) |
替换所有指定字符 |
replace(CharSequence target, CharSequence replacement) |
替换所有匹配的子串 |
replaceAll(String regex, String replacement) |
用正则替换所有匹配 |
replaceFirst(String regex, String replacement) |
用正则替换第一个匹配 |
concat(String str) |
拼接字符串(等价于 +) |
join(CharSequence delimiter, CharSequence... elements) |
静态方法,用分隔符拼接多个字符串 |
String s = String.join("-", "1", "2", "3", "4", "5"); |
1-2-3-4-5
5. 格式化与拼接
| 方法 | 说明 |
|---|---|
format(String format, Object... args) |
静态方法,格式化字符串(类似 printf) |
valueOf(X x) |
静态方法,将基本类型/对象转为字符串 |
repeat(int count) |
重复字符串 count 次(Java 11+) |
indent(int n) |
缩进/取消缩进(Java 12+) |
6. 检查与转换(Java 8+ Stream 相关)
| 方法 | 说明 |
|---|---|
lines() |
返回按换行符分割的行流(Stream |
transform(Function<String, R> f) |
对字符串应用函数并返回结果(Java 12+) |
不可变性的好处与代价
| 好处 | 代价 |
|---|---|
| 线程安全,无需同步 | 频繁拼接时产生大量临时对象,性能差 |
| 可作为 HashMap 的键(哈希值稳定) | 字符串拼接用 + 在循环中效率极低 |
| 字符串常量池节省内存 | 应使用 StringBuilder/StringBuffer 处理可变字符串 |
| 安全性(如文件路径、SQL 注入预防) |
String、StringBuilder、StringBuffer 对比
| 类 | 可变性 | 线程安全 | 性能 |
|---|---|---|---|
String |
不可变 | 安全(天然) | 拼接慢 |
StringBuilder |
可变 | 不安全 | 快 |
StringBuffer |
可变 | 安全(方法同步) | 较慢(有同步开销) |
使用建议:
- 单线程下字符串拼接:
StringBuilder - 多线程下字符串拼接:
StringBuffer - 少量拼接或不需修改:
String的concat或+
intern() 方法
String s1 = new String("hello"); |
intern() 将字符串对象加入常量池(如果池中已有则返回池中的引用),可用于减少重复字符串的内存占用。
示例代码
public class StringDemo { |
一句话总结
String 是不可变的字符序列,提供了丰富的字符串操作方法,适合存储和查询固定的文本;需要频繁修改字符串时应使用 StringBuilder 或 StringBuffer 以提高性能。
StringBuffer类

java.lang.StringBuffer 是 Java 中另一个可变字符序列 类,与 StringBuilder 功能几乎完全一致,但它的方法是线程安全 的(几乎所有公共方法都加了 synchronized 关键字)。
核心特性
| 特性 | 说明 |
|---|---|
| 可变性 | 内容可修改(追加、插入、删除、替换等) |
| 线程安全 | 方法使用 synchronized 同步,适合多线程环境 |
| 初始容量 | 默认 16 个字符,自动扩容策略与 StringBuilder 相同 |
| 实现接口 | Appendable、Serializable、CharSequence |
与 StringBuilder 的对比
| 对比维度 | StringBuffer | StringBuilder |
|---|---|---|
| 线程安全 | ✅ 安全(同步) | ❌ 不安全 |
| 性能 | 较慢(有同步开销) | 更快 |
| 方法签名 | 几乎相同 | 几乎相同 |
| 引入版本 | JDK 1.0 | JDK 5.0 |
| 适用场景 | 多线程共享可变字符串 | 单线程或局部变量 |
常用方法(与 StringBuilder 完全一致)
| 方法 | 说明 |
|---|---|
append(X x) |
追加各种类型的数据 |
insert(int offset, X x) |
在指定位置插入 |
delete(int start, int end) |
删除 [start, end) 范围的字符 |
deleteCharAt(int index) |
删除指定位置的字符 |
replace(int start, int end, String str) |
替换指定范围的字符 |
reverse() |
反转字符序列 |
charAt(int index) |
获取指定位置的字符 |
setCharAt(int index, char ch) |
设置指定位置的字符 |
substring(int start) / substring(int start, int end) |
截取子串(返回 String) |
length() |
返回当前字符数 |
capacity() |
返回当前容量 |
ensureCapacity(int min) |
确保容量至少为指定值 |
trimToSize() |
将容量调整为当前长度 |
setLength(int newLength) |
设置长度 |
toString() |
转换为 String |
使用示例
public class StringBufferDemo { |
何时使用 StringBuffer?
- 多线程环境 中,多个线程需要同时访问和修改同一个字符串缓冲区。
- 使用
StringBuilder可能出现数据不一致或ConcurrentModificationException的场景。 - 如果确定只在单线程中使用,应优先选择
StringBuilder以获得更好的性能。
历史背景
StringBuffer 是 JDK 1.0 就存在的早期类,当时 Java 还没有 StringBuilder。JDK 5.0 引入了 StringBuilder,作为 StringBuffer 的单线程非同步版本,旨在提高性能。官方建议:单线程优先使用 StringBuilder,多线程才用 StringBuffer。
性能对比示例
// StringBuffer 较慢(同步开销) |
一句话总结
StringBuffer 是线程安全的可变字符序列,方法与 StringBuilder 相同但带有同步锁,适用于多线程共享字符串缓冲区的场景;单线程下应优先使用 StringBuilder 以获得更高性能。
StringBuilder类

java.lang.StringBuilder 是 Java 中用于处理可变字符序列 的类,专门用来高效地拼接和修改字符串。它与 StringBuffer 功能几乎相同,但不是线程安全的,因此在单线程环境下性能更优。
为什么需要 StringBuilder?
- String 不可变:每次拼接(如
str += "abc")都会创建新的 String 对象,频繁操作会导致大量临时对象,浪费内存和 CPU。 - StringBuilder 可变:内部维护一个可扩容的字符数组,直接在原对象上修改,避免了创建大量中间对象。
核心特性
| 特性 | 说明 |
|---|---|
| 可变性 | 内容可修改(追加、插入、删除、替换等) |
| 线程不安全 | 不同步,单线程下速度快 |
| 初始容量 | 默认 16 个字符,超出时自动扩容(通常翻倍 + 2) |
| 实现接口 | Appendable、Serializable、CharSequence |
常用构造器
| 构造器 | 说明 |
|---|---|
StringBuilder() |
初始容量 16 |
StringBuilder(int capacity) |
指定初始容量 |
StringBuilder(String str) |
初始内容为 str,容量为 str.length() + 16 |
常用方法
1. 追加(append)
最常用的方法,支持各种类型(String、int、double、char、boolean、Object 等)。
StringBuilder sb = new StringBuilder(); |
2. 插入(insert)
在指定位置插入内容,原位置后的字符向后移动。
StringBuilder sb = new StringBuilder("HelloWorld"); |
3. 删除(delete / deleteCharAt)
| 方法 | 说明 |
|---|---|
delete(int start, int end) |
删除 [start, end) 范围内的字符 |
deleteCharAt(int index) |
删除指定位置的单个字符 |
StringBuilder sb = new StringBuilder("abcdef"); |
4. 替换(replace)
替换指定范围内的字符为新字符串。
StringBuilder sb = new StringBuilder("Hello World"); |
5. 反转(reverse)
将字符序列反转。
StringBuilder sb = new StringBuilder("abc"); |
6. 获取与修改
| 方法 | 说明 |
|---|---|
charAt(int index) |
获取指定位置的字符 |
setCharAt(int index, char ch) |
设置指定位置的字符 |
substring(int start) / substring(int start, int end) |
截取子串(返回 String) |
length() |
返回当前字符数 |
capacity() |
返回当前容量(可能大于 length) |
ensureCapacity(int minimumCapacity) |
确保容量至少为指定值 |
trimToSize() |
将容量缩小为当前长度(节省空间) |
setLength(int newLength) |
设置长度(缩短会丢弃尾部字符,加长会用 ‘\0’ 填充) |
7. 转换为 String
String result = sb.toString(); // 将 StringBuilder 内容转为不可变的 String |
完整示例
public class StringBuilderDemo { |
StringBuilder vs StringBuffer vs String
| 对比维度 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变天然安全) | 不安全 | 安全(方法加 synchronized) |
| 性能(单线程) | 拼接极慢 | 最快 | 较慢(同步开销) |
| 性能(多线程) | 安全但慢 | 不安全 | 安全且适中 |
| 适用场景 | 固定字符串、少量操作 | 单线程大量拼接/修改 | 多线程共享字符串缓冲区 |
使用建议
- 单线程 中频繁拼接字符串 → 首选
StringBuilder - 多线程 中需要共享可变字符串 → 使用
StringBuffer - 少量拼接或代码简洁性优先 → 可以使用
String的+(编译器会优化为 StringBuilder,但循环中仍建议手动使用 StringBuilder)
一句话总结
StringBuilder 是可变的字符序列,专为单线程下高效拼接和修改字符串而设计,比 String 的 ‘+’ 拼接性能提升数十倍,是 Java 字符串处理的核心工具之一。
八、集合
Java 集合框架
Java 集合框架是一套用于存储和操作对象组的统一架构,位于 java.util 包中。它提供了接口(定义行为)、实现类(具体数据结构)和算法(排序、搜索等),极大地提高了开发效率和代码复用性。
核心接口层次结构
Iterable (接口) |
一、Collection 接口
Collection 是所有单列集合的根接口,定义了通用的操作方法。
常用方法
| 方法 | 说明 |
|---|---|
add(E e) |
添加元素 |
remove(Object o) |
移除元素 |
size() |
返回元素个数 |
isEmpty() |
是否为空 |
contains(Object o) |
是否包含某元素 |
clear() |
清空所有元素 |
iterator() |
返回迭代器 |
toArray() |
转为数组 |
addAll(Collection<? extends E> c) |
添加另一集合的所有元素 |
removeAll(Collection<?> c) |
移除另一集合中包含的元素 |
retainAll(Collection<?> c) |
保留两集合的交集 |
stream() / parallelStream() |
返回流(Java 8+) |
二、List 接口
特点:有序(按插入顺序)、允许重复、每个元素有索引。
实现类对比
| 类 | 底层结构 | 特点 | 线程安全 |
|---|---|---|---|
ArrayList |
动态数组 | 查询快 O(1),增删慢(尾部除外) | 否 |
LinkedList |
双向链表 | 增删快 O(1),查询慢 O(n) | 否 |
Vector |
动态数组 | 与 ArrayList 类似,但方法同步 | 是(已过时) |
Stack |
继承 Vector | 栈(后进先出 LIFO) | 是(已过时) |
ArrayList 示例
List<String> list = new ArrayList<>(); |
LinkedList 特有方法
LinkedList<String> linked = new LinkedList<>(); |
三、Set 接口
特点:无序(除非使用有序实现)、不允许重复(基于 equals() 和 hashCode())。
实现类对比
| 类 | 底层结构 | 顺序 | 允许 null | 线程安全 |
|---|---|---|---|---|
HashSet |
哈希表(HashMap) | 无序 | 最多一个 | 否 |
LinkedHashSet |
哈希表 + 链表 | 按插入顺序 | 最多一个 | 否 |
TreeSet |
红黑树 | 按自然顺序或 Comparator 排序 | 不允许(可定制) | 否 |
HashSet 示例
Set<String> set = new HashSet<>(); |
TreeSet 示例(自然排序)
Set<Integer> numbers = new TreeSet<>(); |
四、Queue 接口
特点:先进先出(FIFO),常用于任务调度、缓冲等。
| 类 | 说明 |
|---|---|
PriorityQueue |
优先级队列,按自然顺序或 Comparator 出队 |
LinkedList |
也可作为 Queue 使用 |
ArrayDeque |
双端队列,性能优于 LinkedList |
Queue 常用方法
| 操作 | 抛出异常 | 返回特殊值 |
|---|---|---|
| 插入 | add(e) |
offer(e) |
| 移除 | remove() |
poll() |
| 检查 | element() |
peek() |
Queue<String> queue = new LinkedList<>(); |
PriorityQueue 示例
Queue<Integer> pq = new PriorityQueue<>(); // 最小堆 |
五、Map 接口
特点:键值对存储,键唯一(基于 equals() 和 hashCode()),值可重复。
实现类对比
| 类 | 底层结构 | 顺序 | 允许 null | 线程安全 |
|---|---|---|---|---|
HashMap |
哈希表(数组+链表/红黑树) | 无序 | 键/值均可 | 否 |
LinkedHashMap |
哈希表+双向链表 | 按插入顺序或访问顺序 | 键/值均可 | 否 |
TreeMap |
红黑树 | 按键的自然顺序或 Comparator 排序 | 键不允许,值允许 | 否 |
Hashtable |
哈希表 | 无序 | 键/值均不允许 | 是(已过时) |
ConcurrentHashMap |
分段锁/CAS | 无序 | 键/值均不允许 | 是(推荐) |
Map 常用方法
| 方法 | 说明 |
|---|---|
put(K key, V value) |
添加键值对 |
get(Object key) |
根据键获取值 |
remove(Object key) |
移除键值对 |
containsKey(Object key) |
是否包含键 |
containsValue(Object value) |
是否包含值 |
keySet() |
返回所有键的 Set |
values() |
返回所有值的 Collection |
entrySet() |
返回所有键值对的 Set |
getOrDefault(Object key, V defaultValue) |
获取值,不存在返回默认值 |
forEach(BiConsumer) |
遍历(Java 8+) |
HashMap 示例
Map<String, Integer> map = new HashMap<>(); |
TreeMap 示例(按键排序)
Map<Integer, String> treeMap = new TreeMap<>(); |
六、迭代器(Iterator)
所有集合都可通过 iterator() 获取迭代器,用于遍历元素。
List<String> list = Arrays.asList("A", "B", "C"); |
增强 for 循环 本质上是语法糖,底层使用迭代器,但不能在遍历时修改集合(会抛 ConcurrentModificationException),此时应显式使用迭代器的 remove()。
七、Collections 工具类
java.util.Collections 提供了大量静态方法操作集合。
| 方法 | 说明 |
|---|---|
sort(List<T> list) |
排序(元素需实现 Comparable) |
sort(List<T> list, Comparator<? super T> c) |
按比较器排序 |
binarySearch(List<? extends Comparable<? super T>> list, T key) |
二分查找 |
reverse(List<?> list) |
反转 |
shuffle(List<?> list) |
随机打乱 |
max(Collection<? extends T> coll) |
最大值 |
min(Collection<? extends T> coll) |
最小值 |
unmodifiableXXX(collection) |
返回不可修改的视图 |
synchronizedXXX(collection) |
返回线程安全的包装集合 |
List<Integer> nums = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5)); |
八、线程安全的集合
| 方式 | 说明 |
|---|---|
Collections.synchronizedXXX() |
包装原有集合,所有方法加同步锁 |
Vector、Hashtable |
遗留类,方法同步,性能较差 |
ConcurrentHashMap |
高并发下推荐,分段锁或 CAS |
CopyOnWriteArrayList |
写时复制,适合读多写少 |
BlockingQueue 系列 |
阻塞队列,用于生产者-消费者模式 |
九、集合的选择指南
| 需求 | 推荐实现 |
|---|---|
| 快速随机访问(按索引) | ArrayList |
| 频繁在中间插入/删除 | LinkedList |
| 保证元素唯一性(无序) | HashSet |
| 保证元素唯一性(有序) | TreeSet / LinkedHashSet |
| 键值对存储(无序) | HashMap |
| 键值对存储(按键排序) | TreeMap |
| 保持插入顺序的键值对 | LinkedHashMap |
| FIFO 队列 | LinkedList / ArrayDeque |
| 优先级队列 | PriorityQueue |
| 多线程共享 Map | ConcurrentHashMap |
| 多线程共享 List | CopyOnWriteArrayList |
十、Java 8+ 新增特性
- Stream API:
collection.stream().filter(...).collect(...) - Lambda 表达式:简化遍历和比较器
Map新方法:computeIfAbsent、merge、forEach、replaceAllCollectors:收集器(toList、toSet、toMap、groupingBy等)
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); |
一句话总结
Java 集合框架提供了 List、Set、Queue、Map 四大接口及其丰富实现,覆盖了绝大多数数据结构的场景,配合泛型、迭代器和工具类(Collections、Stream),能高效、安全地管理对象组。
九、IO
Java I/O
一、什么是 Java I/O?
I/O 是 Input/Output(输入/输出)的缩写,指程序与外部设备(文件、键盘、显示器、网络等)之间的数据交换。在 Java 中,I/O 功能主要由 java.io 包提供(传统阻塞 I/O),此外还有 java.nio 包(非阻塞 I/O,Java 1.4+)作为补充。
核心思想:数据以流(Stream) 的形式流动——输入流将数据从外部读入程序,输出流将数据从程序写出到外部。
二、Java I/O 的核心组成
1. 两大体系
| 体系 | 抽象基类 | 处理的数据类型 | 典型场景 |
|---|---|---|---|
| 字节流 | InputStream(输入)OutputStream(输出) |
二进制数据(图片、视频、音频、任意文件) | 文件复制、网络传输、对象序列化 |
| 字符流 | Reader(输入)Writer(输出) |
文本数据(字符、字符串) | 读写文本文件、处理字符串 |
2. 四种抽象基类
| 基类 | 方向 | 主要方法 |
|---|---|---|
InputStream |
输入 | int read()、int read(byte[] b)、close() |
OutputStream |
输出 | void write(int b)、void write(byte[] b)、flush()、close() |
Reader |
输入 | int read()、int read(char[] cbuf)、close() |
Writer |
输出 | void write(int c)、void write(String str)、flush()、close() |
三、流的分类
| 分类标准 | 类别 | 说明 | 举例 |
|---|---|---|---|
| 数据单位 | 字节流 | 以字节为单位读写 | FileInputStream, BufferedOutputStream |
| 字符流 | 以字符为单位读写(自动处理编码) | FileReader, BufferedWriter |
|
| 方向 | 输入流 | 从数据源读到程序 | InputStream, Reader 的子类 |
| 输出流 | 从程序写到目标 | OutputStream, Writer 的子类 |
|
| 功能角色 | 节点流 | 直接连接数据源(文件、数组、管道等) | FileInputStream, StringReader |
| 处理流 | 包装节点流,增加功能(缓冲、转换、对象序列化等) | BufferedInputStream, DataOutputStream, ObjectInputStream |
四、常用具体流类一览
字节流常用类
| 类名 | 类型 | 作用 |
|---|---|---|
FileInputStream |
节点流(输入) | 从文件读取字节 |
FileOutputStream |
节点流(输出) | 向文件写入字节 |
ByteArrayInputStream |
节点流(输入) | 从内存中的字节数组读取 |
ByteArrayOutputStream |
节点流(输出) | 向内存中的字节数组写入 |
BufferedInputStream |
处理流(输入) | 为输入流提供缓冲,提高效率 |
BufferedOutputStream |
处理流(输出) | 为输出流提供缓冲,提高效率 |
DataInputStream |
处理流(输入) | 读取 Java 基本数据类型(int, double, boolean 等) |
DataOutputStream |
处理流(输出) | 写入 Java 基本数据类型 |
ObjectInputStream |
处理流(输入) | 反序列化对象 |
ObjectOutputStream |
处理流(输出) | 序列化对象 |
PipedInputStream |
节点流(输入) | 与 PipedOutputStream 配合实现线程间通信 |
PipedOutputStream |
节点流(输出) | 同上 |
SequenceInputStream |
处理流(输入) | 将多个输入流合并成一个 |
PrintStream |
处理流(输出) | 格式化输出(System.out 的类型) |
字符流常用类
| 类名 | 类型 | 作用 |
|---|---|---|
FileReader |
节点流(输入) | 从文件读取字符(默认平台编码) |
FileWriter |
节点流(输出) | 向文件写入字符(默认平台编码) |
BufferedReader |
处理流(输入) | 提供缓冲和 readLine() 方法 |
BufferedWriter |
处理流(输出) | 提供缓冲和 newLine() 方法 |
InputStreamReader |
处理流(输入) | 字节流 → 字符流的桥梁,可指定编码 |
OutputStreamWriter |
处理流(输出) | 字符流 → 字节流的桥梁,可指定编码 |
CharArrayReader |
节点流(输入) | 从字符数组读取 |
CharArrayWriter |
节点流(输出) | 向字符数组写入 |
StringReader |
节点流(输入) | 从字符串读取 |
StringWriter |
节点流(输出) | 向字符串写入 |
PrintWriter |
处理流(输出) | 格式化输出(不会抛 IOException) |
LineNumberReader |
处理流(输入) | 继承 BufferedReader,可获取行号 |
五、详细示例
1. 字节流复制文件(带缓冲)
import java.io.*; |
2. 字符流读取文本(逐行)
import java.io.*; |
3. 指定编码读写(转换流)
import java.io.*; |
4. 数据流读写基本类型
import java.io.*; |
5. 对象序列化
import java.io.*; |
六、装饰者模式(流的组合)
Java I/O 的核心设计模式是装饰者模式——你可以将多个处理流层层包裹在节点流上,动态增加功能。
// 从文件读取压缩过的对象数据,并解压 |
七、标准 I/O 流
Java 提供了三个全局流对象:
| 对象 | 类型 | 用途 |
|---|---|---|
System.in |
InputStream |
标准输入(通常关联键盘) |
System.out |
PrintStream |
标准输出(通常关联控制台) |
System.err |
PrintStream |
标准错误输出 |
// 从键盘读取一行 |
八、补充:NIO(New I/O)与 Files 工具类
1. NIO 核心组件
- Channel:双向通道(如
FileChannel,SocketChannel) - Buffer:缓冲区(如
ByteBuffer) - Selector:多路复用选择器(用于网络)
2. Java 7 NIO.2(Files 类)
简化文件操作,无需手动管理流:
// 读取所有行 |
九、总结
- Java I/O 是程序与外部设备交换数据的桥梁,通过流 实现。
- 核心包
java.io提供字节流(InputStream/OutputStream)和字符流(Reader/Writer)两大体系。 - 流分为节点流(直接连接数据源)和处理流(包装节点流增强功能),通过装饰者模式灵活组合。
- 常用流包括文件流、缓冲流、数据流、对象流、转换流、打印流等。
- 推荐使用 try-with-resources 自动管理资源,处理文本时优先用字符流并指定编码。
- 新项目可考虑使用 NIO 的
Files工具类简化文件操作,高性能场景使用 NIO 的 Channel/Buffer。
十、泛型
什么是泛型(Generics)
泛型 是 Java 5 引入的一种参数化类型机制,它允许你在定义类、接口和方法时使用类型参数(Type Parameters),从而让代码能够处理多种类型而不牺牲类型安全性。
简单说:泛型让你编写“通用”的代码,在编译时确定具体类型,而不是在运行时强制转换。
一、为什么需要泛型?
在没有泛型之前,Java 使用 Object 作为通用类型,然后进行强制类型转换,存在两个严重问题:
- 类型不安全:可以向集合中添加任意类型的对象,编译时不报错,运行时可能
ClassCastException。 - 需要手动强转:从集合取出元素时必须强制转换为目标类型,繁琐且容易出错。
// 没有泛型(Java 5 之前) |
有了泛型后,编译器可以在编译期检查类型一致性:
// 使用泛型 |
二、泛型的基本语法
1. 泛型类
// T 是类型参数,可以是任意标识符(习惯用 T, E, K, V) |
2. 泛型方法
public class Utils { |
3. 泛型接口
public interface Pair<K, V> { |
三、类型参数命名惯例
| 参数名 | 含义 |
|---|---|
E |
Element(集合元素) |
K |
Key(键) |
V |
Value(值) |
T |
Type(任意类型) |
N |
Number(数字) |
S, U, V |
第二、第三、第四个类型 |
四、通配符(Wildcards)
通配符 ? 用于表示未知类型,主要在方法参数中使用,增加灵活性。
1. 无界通配符 ?
表示任何类型,但不能写入(除了 null)。
public static void printList(List<?> list) { |
2. 上界通配符 ? extends T
表示类型必须是 T 或 T 的子类,只能读取,不能写入(因为不知道具体子类型)。
public static double sumOfNumbers(List<? extends Number> list) { |
3. 下界通配符 ? super T
表示类型必须是 T 或 T 的父类,只能写入,读取时只能作为 Object。
public static void addNumbers(List<? super Integer> list) { |
PECS 原则(Producer Extends, Consumer Super):
- 如果你只从集合中读取数据(生产者),用
? extends T。 - 如果你只向集合中写入数据(消费者),用
? super T。
五、类型擦除(Type Erasure)
Java 的泛型是编译期 实现的,运行时泛型信息会被擦除(替换为边界类型或 Object)。这意味着:
List<String> strings = new ArrayList<>(); |
擦除规则:
- 若类型参数无边界(如
<T>),擦除为Object。 - 若有上界(如
<T extends Comparable>),擦除为Comparable。
因此,你不能:
- 创建泛型数组:
new T[10]❌ - 使用
instanceof检查泛型类型:if (obj instanceof List<String>)❌ - 在静态上下文中使用类型参数(静态变量、静态方法无法访问类的类型参数,除非方法本身是泛型方法)。
六、泛型与继承
泛型不支持协变(Covariance),即 List<String> 不是 List<Object> 的子类型:
List<String> strs = new ArrayList<>(); |
但是可以使用通配符实现协变:
List<? extends Object> objs = strs; // OK |
数组是协变的(历史遗留问题):
String[] strs = new String[10]; |
七、泛型的限制
- 基本类型不能作为类型参数:
List<int>错误,必须用包装类List<Integer>。 - 不能实例化类型参数:
new T()错误。 - 不能创建泛型数组:
new T[10]错误。 - 不能在静态变量或静态初始化块中使用类的类型参数。
- 异常类不能是泛型:
class MyException<T> extends Exception错误。
八、完整示例:泛型栈
public class GenericStack<E> { |
九、总结
- 泛型 是一种编译时类型安全机制,通过类型参数使代码通用且安全。
- 消除了强制类型转换,减少了
ClassCastException。 - 支持泛型类、泛型方法、泛型接口。
- 通配符(
? extends T/? super T)提供了灵活的协变和逆变支持。 - 由于类型擦除,运行时泛型信息丢失,带来一些限制。
- 泛型是现代 Java 编程的基础,广泛用于集合框架、函数式接口、自定义数据结构等
十一、反射
什么是反射(Reflection)
反射 是 Java 提供的一种运行时自省机制,允许程序在运行时 动态地获取类的完整信息(构造器、方法、字段、注解等),并能够操作这些成员(创建对象、调用方法、访问/修改字段),即使在编译时对这些类一无所知。
简单说:反射让代码在运行时“观察”和“操控”自身或其他类的内部结构。
一、反射的核心 API
反射功能集中在 java.lang.reflect 包中,核心类如下:
| 类 | 作用 |
|---|---|
Class<?> |
代表一个类或接口,是反射的入口 |
Constructor<T> |
代表类的构造方法 |
Method |
代表类的方法(包括继承的) |
Field |
代表类的字段(成员变量) |
Modifier |
解析修饰符(public, static, final 等) |
Annotation |
代表注解 |
Array |
动态创建和操作数组 |
二、获取 Class 对象的三种方式
// 方式1:通过类名.class(最安全、性能最好) |
三、反射的常见操作
1. 获取类的完整信息
Class<?> clazz = User.class; |
2. 创建实例
// 方式1:调用无参构造器(类必须有 public 无参构造) |
3. 调用方法
// 获取方法(参数:方法名,参数类型列表) |
4. 访问和修改字段
// 获取公有字段 |
5. 操作数组
// 动态创建数组 |
6. 获取注解
// 获取类上的注解 |
四、完整示例:通过反射执行任意类的任意方法
import java.lang.reflect.Method; |
五、反射的应用场景
- 框架开发(Spring、MyBatis、Hibernate、JUnit 等)
- Spring IoC 容器通过反射创建 Bean 并注入依赖。
- MyBatis 通过反射将 SQL 结果集映射为 Java 对象。
- JUnit 通过反射找到并执行
@Test标注的方法。
动态代理(JDK 动态代理基于反射实现
InvocationHandler)IDE 和开发工具
- IntelliJ IDEA 的代码补全、重构功能依赖反射分析类结构。
序列化/反序列化框架(Jackson、Gson 使用反射读写对象字段)
数据库 ORM(将数据库表记录映射为对象)
插件系统(动态加载第三方类并调用其方法)
六、反射的优缺点
| 优点 | 缺点 |
|---|---|
| 运行时动态获取类信息,灵活性极高 | 性能较低:反射调用比直接调用慢(因为需要解析方法、安全检查等) |
| 可以实现通用的框架代码,降低耦合 | 破坏封装性:可以访问私有成员,违反面向对象原则 |
| 支持动态代理、注解处理等高级特性 | 安全限制:在某些安全管理器下可能被禁止 |
| 是 Java 动态性的基石 | 代码可读性差:反射代码通常较复杂,不易调试 |
七、性能优化建议
- 缓存
Method/Field/Constructor对象,避免重复获取。 - 对于高频调用,尽量使用直接调用而非反射。
- 使用
setAccessible(true)可以跳过 Java 访问权限检查,略微提升性能。 - 现代 JVM 会对反射调用进行内联优化(如
invoke方法的-Dsun.reflect.inflationThreshold参数)。
八、注意事项
setAccessible(true)可能会被 SecurityManager 阻止,尤其是在受限环境中(如 Applet)。- 反射调用方法时参数类型匹配:基本类型与包装类需要精确对应,例如
int.class与Integer.class不同。 - 数组的 Class 对象:
int[].class与int.class不同,可通过Class.forName("[I")获取。 - 泛型信息在运行时被擦除:反射无法直接获取泛型的具体类型参数(如
List<String>中的String),但可以通过ParameterizedType获取声明时的泛型信息。
九、总结
反射是 Java 语言动态特性的核心机制,允许程序在运行时探查和操纵类的内部结构。 虽然它带来了极大的灵活性,但也伴随着性能开销和封装破坏的风险。在实际开发中,反射主要用于框架、工具库和底层基础设施,普通业务代码应尽量避免过度使用。
十二、网络编程
什么是网络编程
网络编程 是指编写能够在不同计算机之间通过网络进行数据交换的程序。在 Java 中,网络编程主要涉及 java.net 包(传统阻塞 I/O)和 java.nio 包(非阻塞 I/O),用于实现客户端-服务器通信、网页请求、远程方法调用等。
简单说:网络编程让程序能够跨越机器边界发送和接收数据。
一、网络编程的核心概念
1. IP 地址与端口号
- IP 地址:标识网络中的一台主机(如
192.168.1.1、IPv6 地址)。 - 端口号:标识主机上的一个进程(0
65535,01023 为知名端口,如 HTTP 80、HTTPS 443)。 - 套接字(Socket):IP 地址 + 端口号,唯一标识一个网络通信端点。
2. TCP 与 UDP 协议
| 特性 | TCP(传输控制协议) | UDP(用户数据报协议) |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠传输(确认重传) | 不可靠(可能丢包) |
| 顺序 | 保证数据有序到达 | 不保证顺序 |
| 速度 | 相对较慢 | 较快 |
| 适用场景 | 文件传输、网页浏览、邮件 | 视频直播、在线游戏、DNS 查询 |
3. OSI 七层模型与 TCP/IP 四层模型
| OSI 七层 | TCP/IP 四层 | 主要协议 |
|---|---|---|
| 应用层 | 应用层 | HTTP, FTP, SMTP, DNS |
| 表示层 | ↑ | SSL/TLS |
| 会话层 | ↑ | RPC |
| 传输层 | 传输层 | TCP, UDP |
| 网络层 | 网际层 | IP, ICMP, ARP |
| 数据链路层 | 网络接口层 | Ethernet, PPP |
| 物理层 | ↓ | 光纤、双绞线 |
二、Java 网络编程基础(java.net)
1. InetAddress —— IP 地址封装
import java.net.InetAddress; |
运行结果:

2. TCP Socket 编程(Socket / ServerSocket)
服务端流程:
- 创建
ServerSocket绑定端口。 - 调用
accept()阻塞等待客户端连接。 - 通过返回的
Socket获取输入/输出流进行通信。 - 关闭资源。
客户端流程:
- 创建
Socket连接指定 IP 和端口。 - 获取输入/输出流进行通信。
- 关闭资源。
示例:回声服务器(Echo Server)
服务端:
import java.io.*; |
客户端:
import java.io.*; |
3. UDP Socket 编程(DatagramSocket / DatagramPacket)
UDP 无连接,直接发送数据报。
发送端:
import java.net.*; |
接收端:
import java.net.*; |
4. URL 与 URLConnection —— HTTP 访问
import java.io.BufferedReader; |
运行结果:

三、Java NIO 网络编程(非阻塞 I/O)
NIO 使用 Channel(通道)、Buffer(缓冲区)和 Selector(选择器)实现高效的网络通信,特别适合高并发场景。
核心组件
| 组件 | 作用 |
|---|---|
SocketChannel |
非阻塞 TCP 通道 |
ServerSocketChannel |
非阻塞服务端通道 |
DatagramChannel |
非阻塞 UDP 通道 |
ByteBuffer |
字节缓冲区 |
Selector |
多路复用器,监听多个 Channel 的事件 |
示例:NIO 回声服务器(单线程管理多连接)
import java.io.IOException; |
四、高级网络编程
1. Netty 框架
Netty 是一个基于 NIO 的高性能网络应用框架,封装了复杂的 NIO 细节,广泛应用于 RPC、消息推送、游戏服务器等。
2. HTTP 客户端库
HttpURLConnection(内置,但较繁琐)- Apache HttpClient
- OkHttp
- Spring WebClient(响应式)
3. WebSocket
全双工通信协议,适合实时应用(聊天、股票行情)。Java EE 提供 javax.websocket API。
4. RMI(远程方法调用)
Java 原生的分布式对象技术,允许调用远程 JVM 中的方法(已被更现代的框架取代)。
五、常见问题与注意事项
- TCP 粘包/拆包:TCP 是流式协议,多条消息可能合并或拆分。解决方法:固定长度、分隔符、消息头记录长度。
- 端口占用:
BindException: Address already in use,更换端口或等待 TIME_WAIT 状态结束。 - 防火墙:确保端口未被防火墙拦截。
- 编码问题:网络传输统一使用 UTF-8 编码。
- 线程模型:传统 BIO 每连接一线程,高并发下资源耗尽;NIO 单线程可管理数千连接。
- 超时设置:
socket.setSoTimeout(timeout)防止无限阻塞。
六、总结
- 网络编程 是让程序通过网络进行数据交换的技术,核心是 Socket 编程(TCP/UDP)。
- Java 提供
java.net包支持传统的阻塞 I/O,java.nio包支持非阻塞 I/O。 - TCP 可靠但开销大,UDP 快速但不可靠,根据场景选择。
- 实际项目中常使用 Netty、OkHttp 等成熟框架简化开发。
- 理解 IP、端口、协议、Socket 是网络编程的基础。
netty,nio?
十三、多线程
什么是多线程
多线程 是指在一个程序中同时运行多个独立的执行路径(线程),每个线程可以并行地执行不同的任务,从而提高程序的响应速度和资源利用率。
在 Java 中,线程是 java.lang.Thread 类的实例,或者实现了 Runnable 接口的任务。JVM 允许多个线程共享同一个进程的内存空间(堆和方法区),但每个线程拥有独立的程序计数器、虚拟机栈和本地方法栈。
一、为什么需要多线程?
| 场景 | 单线程的问题 | 多线程的优势 |
|---|---|---|
| GUI 应用 | 耗时操作(下载、计算)会卡住界面 | 后台线程处理,前台保持响应 |
| 服务器 | 串行处理请求,吞吐量低 | 每个请求分配一个线程,并发处理 |
| CPU 密集型任务 | 无法充分利用多核 CPU | 多线程并行计算,缩短总耗时 |
| I/O 密集型任务 | 等待 I/O 时 CPU 空闲 | 切换其他线程执行,提高 CPU 利用率 |
二、Java 中创建线程的四种方式
1. 继承 Thread 类
class MyThread extends Thread { |
2. 实现 Runnable 接口(推荐)
class MyTask implements Runnable { |
3. 实现 Callable 接口(带返回值 + 异常)
import java.util.concurrent.*; |
4. 使用线程池(ExecutorService)
ExecutorService executor = Executors.newFixedThreadPool(4); |
三、线程的生命周期
Java 线程共有 6 种状态(Thread.State 枚举):
| 状态 | 说明 | 触发条件 |
|---|---|---|
| NEW | 新建,尚未 start() | new Thread() |
| RUNNABLE | 就绪或运行中 | 调用 start(),或从阻塞恢复后获得 CPU |
| BLOCKED | 阻塞等待监视器锁 | 试图进入 synchronized 块但锁被占用 |
| WAITING | 无限期等待 | 调用 wait()、join()、park() |
| TIMED_WAITING | 有限期等待 | 调用 sleep(millis)、wait(timeout)、join(timeout) |
| TERMINATED | 终止 | run() 正常结束或抛出未捕获异常 |

四、线程安全与同步
1. 线程不安全的原因
- 原子性:操作被中断(如
count++不是原子操作)。 - 可见性:一个线程修改的变量对其他线程不可见(CPU 缓存导致)。
- 有序性:指令重排序可能导致意外结果。
2. 解决方案
a. synchronized 关键字
- 修饰方法:锁住当前实例(静态方法锁住 Class 对象)。
- 修饰代码块:指定锁对象。
public class Counter { |
b. Lock 接口(java.util.concurrent.locks)
更灵活的锁,支持尝试获取、可中断、公平锁等。
import java.util.concurrent.locks.ReentrantLock; |
c. volatile 关键字
保证变量的可见性(禁止指令重排),但不保证原子性。适用于单个变量的读写场景。
public class Flag { |
d. 原子类(AtomicInteger 等)
利用 CAS(Compare-And-Swap)实现无锁线程安全。
import java.util.concurrent.atomic.AtomicInteger; |
五、线程间通信
1. wait() / notify() / notifyAll()
必须在 synchronized 块中调用,作用于同一个锁对象。
class Message { |
2. Condition(配合 Lock)
ReentrantLock lock = new ReentrantLock(); |
3. CountDownLatch / CyclicBarrier / Semaphore
| 工具 | 作用 |
|---|---|
CountDownLatch |
一个或多个线程等待其他线程完成操作 |
CyclicBarrier |
多个线程互相等待,到达屏障后继续 |
Semaphore |
控制同时访问资源的线程数量 |
六、线程池
1. 为什么用线程池?
- 避免频繁创建/销毁线程的开销。
- 控制最大并发数,防止资源耗尽。
- 提供任务队列、定时执行等管理功能。
2. 常用线程池(Executors 工厂方法)
| 方法 | 特点 | 适用场景 |
|---|---|---|
newFixedThreadPool(n) |
固定线程数,无界队列 | 负载稳定的服务器 |
newCachedThreadPool() |
可伸缩,空闲60秒回收 | 短时间异步任务 |
newSingleThreadExecutor() |
单线程顺序执行 | 需要保证任务顺序 |
newScheduledThreadPool(n) |
定时/周期性执行 | 定时任务 |
3. 自定义线程池(推荐)
ThreadPoolExecutor executor = new ThreadPoolExecutor( |
4. 提交任务
executor.execute(runnable); // 无返回值 |
七、常见并发问题
1. 死锁
多个线程相互等待对方持有的锁,导致永久阻塞。
预防:按固定顺序获取锁、使用 tryLock 超时、检测死锁。
// 死锁示例 |
2. 活锁
线程不断重试但始终无法进展(类似两人在走廊互相让路)。
3. 饥饿
低优先级线程长时间得不到 CPU 时间。
4. 伪共享(False Sharing)
多线程修改同一缓存行的不同变量导致缓存失效,性能下降。可通过填充字节对齐解决。
八、ThreadLocal
每个线程拥有独立的变量副本,互不干扰。
public class UserContext { |
九、完整示例:生产者-消费者
import java.util.LinkedList; |
十、总结
- 多线程 让程序同时执行多个任务,提升性能和响应性。
- Java 提供
Thread、Runnable、Callable、线程池等多种方式创建线程。 - 线程安全需要关注原子性、可见性、有序性,通过
synchronized、Lock、volatile、原子类等解决。 - 线程间通信使用
wait/notify、Condition或并发工具类。 - 线程池是管理线程的最佳实践,避免手动创建线程。
- 注意死锁、活锁、饥饿等并发问题,合理设计锁的顺序和粒度。
多线程是 Java 开发的核心技能之一,理解其原理和最佳实践对于构建高性能、可靠的应用程序至关重要。



