JavaSE内容梳理与整合
1、软件的下载与卸载
下载
默认安装在C:Profiles,可以调整安装位置
卸载
1、卸载安装目录
2、使用过程中可能存在临时文件和运行文件,需要将此目录一并删除
3、部分软件(如MySQL)需要在电脑的服务中将相关服务停止并删除
2、注释
01、单行注释
作用:注释单行内容,适用于简短说明。
语法:以 //
开头,从 //
到本行末尾的所有内容都会被视为注释
使用:
// 定义一个整数变量,初始值为10
int num = 10;
02、多行注释
作用:注释多行内容(也可注释单行),适用于对一段代码块进行详细说明。
语法:以 /*
开头,以 */
结尾,中间包裹的所有内容(可跨多行)均为注释。
使用:
/* 这是一个多行注释用于解释下面的循环逻辑:从0循环到9,打印每个数字
*/
for (int i = 0; i < 10; i++) {System.out.println(i);
}
注意事项:多行注释不能嵌套(即 /*
内部不能再出现 /*
),否则编译器可能会提前识别 */
导致注释提前结束,引发语法错误。
以下代码会报错:
/* 外层注释/* 内层注释 */ // 错误:内层的*/会被当作外层注释的结束符
*/
03、文档注释(Javadoc 注释)
作用:用于生成 API 文档(通过 Javadoc 工具),通常注释在类、方法、字段等结构的上方,说明其功能、参数、返回值等信息。
语法:以 /**
开头,以 */
结尾,中间可包含标签(如 @author
、@param
等),标签用于标准化文档内容。
常用标签:
@author
:标注作者。@param
:说明方法的参数(格式:@param 参数名 说明
)。@return
:说明方法的返回值(仅用于非 void 方法)。@since
:标注从哪个版本开始存在。@throws
/@exception
:说明方法可能抛出的异常
/*** 一个简单的计算器类,提供加法运算* @author zjc* @since 1.0*/
生成文档:通过 JDK 自带的 javadoc
工具,可将文档注释生成 HTML 格式的 API 文档(类似 Java 官方文档),命令示例:javadoc Calculator.java
(执行后会生成一系列 HTML 文件)。
3、标识符
在 Java 中,标识符是用于给类、方法、变量、包、接口等程序元素命名的符号。标识符的命名有严格的语法规则(必须遵守,否则编译报错),同时也有约定俗成的命名规范(建议遵守,提升代码可读性)。
01、允许使用字符
首字符必须以字母,美元符号,下划线开头。
后续字符可以是字母,美元符号,下划线,数字
02、不允许的情况
不能以数字开头:如123v
不能包含空格或者特殊符号:如 my var
(含空格)、user-name
(含减号)、price#1
(含#
)、a+b
(含+
)等均不合法(仅允许$
和_
作为特殊字符)。
不能使用Java的关键字或者保留字
- 关键字:Java 中已定义特殊含义的单词,如
class
、int
、if
、public
、static
等(共 53 个关键字)。 - 保留字:目前未使用但预留的单词,如
goto
、const
(虽然不常用,但也禁止作为标识符)。
区分大小写:name
和 Name
是两个不同的标识符(Java 对大小写敏感)
03、标识符命名规范
类名/接口名:
使用 帕斯卡命名法(PascalCase):每个单词首字母大写,其余字母小写,不使用下划线。
示例:Student
、UserService
、ArrayList
。
方法名/变量名:
使用 驼峰命名法(camelCase):第一个单词首字母小写,后续单词首字母大写,不使用下划线。
示例:getUserInfo()
(方法名)、studentAge
(变量名)、isValid
(布尔变量,通常以is
开头)。
常量名:
全大写,单词之间用下划线(_
)分隔。
示例:MAX_VALUE
、PI
、DEFAULT_TIMEOUT
。
包名:
全小写,多个单词用点(.
)分隔,通常以公司 / 组织域名倒写开头(避免冲突)。
示例:com.example.utils
、java.lang
。
其他建议:
尽量使用有意义的名称,避免无意义的单字母(局部变量如循环变量i
、j
除外)。
不推荐使用中文或拼音(虽然语法允许,但不利于国际协作和代码通用性)。
避免使用$
符号(通常用于编译器自动生成的标识符,如内部类名)。
4、数据类型
01、Java中的数据类型
1、基本数据类型(字节数)
Java 的基本数据类型是 “值类型”,直接存储数据本身
byte(1),short(2),int(4),long(8),float(4),double(8),boolean(1),char(2)
2、引用数据类型
引用数据类型存储的是数据在堆内存中的 “地址”,而非数据本身
类,接口,数组,枚举
02、JavaScript 中的数据类型
JavaScript 是弱类型语言(变量类型可动态变化),ES6 及之后的数据类型分为基本数据类型和引用数据类型:
1、基本数据类型(值类型):
String
:字符串(如"hello"
);Number
:数值(整数 / 小数,64 位双精度浮点,如123
、3.14
);Boolean
:布尔值(true
/false
);Null
:空值(表示 “无”,类型为object
,历史 bug);Undefined
:未定义(变量声明未赋值时的默认值);Symbol
(ES6 新增):唯一标识符(通过Symbol()
创建,不可重复);BigInt
(ES2020 新增):大整数(解决Number
精度限制,如123n
)。
2、引用数据类型(对象类型)
存储地址,值在堆中
Object
:普通对象(如{name: "Tom"}
);Array
:数组(如[1, 2, 3]
);Function
:函数(如function() {}
);Date
、RegExp
(正则)、Map
、Set
等特殊对象。
03、MySQL 中的数据类型
MySQL 数据类型按用途分为数值型、字符串型、日期时间型等
数值型
整数:
TINYINT
(1 字节,-128~127,无符号 0~255);SMALLINT
(2 字节)、MEDIUMINT
(3 字节)、INT
(4 字节)、BIGINT
(8 字节);
小数:
FLOAT(M,D)
:单精度浮点(M 总位数,D 小数位数,精度有限);DOUBLE(M,D)
:双精度浮点(精度高于 FLOAT,但仍可能有误差);DECIMAL(M,D)
:定点数(精确存储,适合金额等场景,无精度损失)。
字符串型
CHAR(N)
:固定长度字符串(N 为长度,0~255,不足补空格,查询快);VARCHAR(N)
:可变长度字符串(0~65535,按实际长度存储,节省空间);- 文本型:
TEXT
(长文本,0~65535 字节)、LONGTEXT
(更大文本); - 二进制型:
BLOB
(二进制数据,如图片)、LONGBLOB
等。
日期时间型
DATE
:日期(YYYY-MM-DD
,如2023-10-01
);TIME
:时间(HH:MM:SS
,如12:30:45
);DATETIME
:日期 + 时间(YYYY-MM-DD HH:MM:SS
,范围 1000-01-01 ~ 9999-12-31);TIMESTAMP
:时间戳(YYYY-MM-DD HH:MM:SS
,范围 1970-01-01 ~ 2038-01-19,受时区影响)。
04、数据类型的强制转换带来的失真问题
强转
强转(强制类型转换)是手动将一种数据类型转为另一种类型,语法:目标类型 变量 = (目标类型) 源变量;
。
何时会失真
当源类型的取值范围 > 目标类型的取值范围时,强转可能导致精度丢失或数值溢出,即 “失真”:
- 小数转整数:丢失小数部分(精度丢失)。例:
double d = 3.9; int i = (int)d;
→i=3
(小数部分被截断)。 - 大整数转小整数:数值溢出(结果异常)。例:
int a = 300; byte b = (byte)a;
→b=44
(因byte
最大 127,300 超出范围,溢出后结果错误)。 - 高精度浮点转低精度浮点:可能丢失精度。例:
double d = 123456789.123456789; float f = (float)d;
→f
的小数部分会失真(float
精度低于double
)。
何时不失真?
当源类型范围 ≤ 目标类型范围时,强转(或自动转换)不会失真:
- 例:
int a = 100; long b = (long)a;
(int
范围 <long
,结果仍为 100,无失真)。
5、运算符
01、算数运算符
运算符 | 名称 | 作用 | 示例 | 结果 |
---|---|---|---|---|
+ | 加法 | 两数相加;字符串拼接(若有字符串参与) | 3 + 5 ;"a" + 2 | 8;"a2" |
- | 减法 | 两数相减;取负数 | 10 - 4 ;-3 | 6;-3 |
* | 乘法 | 两数相乘 | 4 * 5 | 20 |
/ | 除法 | 两数相除(整数除法会截断小数) | 10 / 3 (整数);10.0 / 3 | 3;3.333... |
% | 取模(余数) | 两数相除的余数(结果符号与被除数一致) | 10 % 3 ;-10 % 3 | 1;-1 |
++ | 自增 | 变量值 + 1(前缀:先增后用;后缀:先用后增) | int a=3; int b=++a; (前缀) | a=4,b=4 |
int a=3; int b=a++; (后缀) | a=4,b=3 | |||
-- | 自减 | 变量值 - 1(前缀:先减后用;后缀:先用后减) | int a=3; int b=--a; (前缀) | a=2,b=2 |
注意事项:
- 整数除法(如
5/2
)结果为整数(截断小数,非四舍五入); - 字符串与其他类型用
+
拼接时,结果为字符串(如1 + 2 + "a"
→"3a"
,"a" + 1 + 2
→"a12"
); - 自增 / 自减只能操作变量(如
3++
报错)。
02、赋值运算符
用于给变量赋值,基本赋值符为=
,另有复合赋值符(简化运算 + 赋值)。
运算符 | 名称 | 作用(等价于) | 示例 | 结果(假设 a 初始为 5) |
---|---|---|---|---|
= | 基本赋值 | 将右边值赋给左边变量 | int a = 5; | a=5 |
+= | 加后赋值 | a = a + b | a += 3 | a=8 |
-= | 减后赋值 | a = a - b | a -= 2 | a=3 |
*= | 乘后赋值 | a = a * b | a *= 4 | a=20 |
/= | 除后赋值 | a = a / b | a /= 2 | a=2(若 a=5) |
%= | 取模后赋值 | a = a % b | a %= 3 | a=2(若 a=5) |
注意事项:
- 复合赋值符会自动进行隐式类型转换(避免直接赋值的类型错误)。例如:
byte a = 1;
a += 2; // 等价于 a = (byte)(a + 2),正确(结果3)
a = a + 2; // 错误(a+2为int类型,无法直接赋给byte)
03、比较运算符
用于比较两个值的关系,返回boolean
类型(true
或false
)。
运算符 | 名称 | 作用 | 示例 | 结果 |
---|---|---|---|---|
== | 等于 | 判断两值是否相等(基本类型比 “值”,引用类型比 “地址”) | 3 == 3 ;"a" == "a" | true;true(字符串常量池地址相同) |
!= | 不等于 | 判断两值是否不相等 | 3 != 5 | true |
> | 大于 | 判断左边是否大于右边 | 5 > 3 | true |
< | 小于 | 判断左边是否小于右边 | 5 < 3 | false |
>= | 大于等于 | 判断左边是否大于或等于右边 | 5 >= 5 | true |
<= | 小于等于 | 判断左边是否小于或等于右边 | 3 <= 2 | false |
注意事项:
- 引用类型(如对象、数组)用
==
比较的是内存地址(是否为同一个对象),若需比较内容,需用equals()
方法(如String
的equals()
); ==
和=
的区别:==
是比较,=
是赋值(如if (a == 5)
正确,if (a = 5)
错误,会将 5 赋给 a 并判断 a 是否非 0)。
04、逻辑运算符
用于连接布尔表达式(boolean
类型),执行逻辑判断,返回boolean
类型。
运算符 | 名称 | 作用(短路特性) | 示例(a=3, b=5) | 结果 | ||||
---|---|---|---|---|---|---|---|---|
&& | 短路与 | 两边都为true 则结果为true ;左边为false 时,右边不执行(短路) | (a > 2) && (b < 10) | true | ||||
` | ` | 短路或 | 两边有一个为true 则结果为true ;左边为true 时,右边不执行(短路) | `(a > 5) | (b < 10)` | true | ||
! | 逻辑非 | 取反(true →false ,false →true ) | !(a > 2) | false |
补充:
- 非短路逻辑运算符:
&
(与)、|
(或),无论左边结果如何,右边都会执行(效率低,较少用)。例如:
int x = 1;
boolean b = (x > 2) & (x++ > 0); // 左边false,但右边仍执行,x变为2
05、三元运算符
语法:条件表达式 ? 表达式1 : 表达式2
- 执行逻辑:若条件为
true
,返回表达式 1 的结果;否则返回表达式 2 的结果。 - 示例:
int a = 5, b = 3;
int max = (a > b) ? a : b; // 条件为true,max = a(5)
String res = (a == b) ? "相等" : "不等"; // res = "不等"
注意事项:
- 表达式 1 和表达式 2 的类型必须兼容(或可自动转换为同一类型),否则编译报错。例如:
int num = (1 > 0) ? 2 : 3.5;
// 错误(2 是 int,3.5 是 double,类型不兼容)
06、位运算符
直接对二进制位进行操作,操作数为整数(byte
、short
、int
、long
),通常用于底层优化(如加密、压缩)。
运算符 | 名称 | 作用(对二进制位操作) | 示例(二进制) | 结果(十进制) | ||
---|---|---|---|---|---|---|
& | 按位与 | 两位都为 1 则为 1,否则为 0 | 5(101) & 3(011) | 1(001) | ||
` | ` | 按位或 | 两位有一个为 1 则为 1,否则为 0 | `5(101) | 3(011)` | 7(111) |
^ | 按位异或 | 两位不同则为 1,相同则为 0(同 0 异 1) | 5(101) ^ 3(011) | 6(110) | ||
~ | 按位取反 | 0 变 1,1 变 0(结果为原数的补码,通常为负数) | ~5(000...0101) | -6 | ||
<< | 左移 | 所有位左移 n 位,右边补 0(等价于原数 × 2ⁿ ,效率高于乘法) | 3(11) << 2 → 1100 | 12(3×2²) | ||
>> | 右移(带符号) | 所有位右移 n 位,左边补原符号位(正数补 0,负数补 1,等价于原数 ÷ 2ⁿ ,取整) | 6(110) >> 1 → 11 | 3(6÷2) | ||
>>> | 无符号右移 | 所有位右移 n 位,左边补 0(负数右移后可能为正数) | -8 >>> 2 (补码右移 2 位) | 1073741822 |
示例说明:
5 & 3
:5 的二进制是101
,3 是011
,按位与后为001
(即 1);~5
:在 32 位 int 中,5 的二进制是000...0101
,取反后为111...1010
(补码),对应十进制 - 6;- 左移 / 右移常用于快速计算(如
a << 1
等价于a * 2
,效率更高)。
6、break,continue,return的区别
01、break
:终止当前循环
- 作用:立即终止当前所在的循环(如果是嵌套循环,只终止最内层循环),跳出循环体,执行循环后面的代码。
- 适用场景:当满足某个条件时,需要提前结束整个循环(不再执行后续任何循环迭代)。
示例:
for (int i = 0; i < 5; i++) {if (i == 2) {break; // 当i=2时,终止当前for循环}System.out.println(i); // 输出:0、1(i=2时循环被终止,不再执行)
}
System.out.println("循环结束"); // 循环终止后执行这句
02. continue
:跳过本次循环,进入下一次
- 作用:跳过当前循环中剩余的语句,直接进入下一次循环的判断(不终止整个循环)。
- 适用场景:当满足某个条件时,需要忽略当前迭代中剩余的逻辑,但仍需继续执行后续迭代。
示例:
for (int i = 0; i < 5; i++) {if (i == 2) {continue; // 当i=2时,跳过本次循环剩余代码,直接进入下一次循环(i=3)}System.out.println(i); // 输出:0、1、3、4(跳过了i=2的输出)
}
03、return
:退出当前方法(函数)
- 作用:立即结束当前所在的方法(函数),无论是否在循环中,都会直接返回方法的调用处(如果有返回值,会携带返回值)。
- 适用场景:当满足某个条件时,需要提前结束整个方法的执行(不仅是循环,方法内所有后续代码都不再执行)。
示例:
public static void test() {for (int i = 0; i < 5; i++) {if (i == 2) {return; // 当i=2时,直接退出整个test()方法}System.out.println(i); // 输出:0、1(方法被终止,后续代码不再执行)}System.out.println("循环结束"); // 不会执行(因为return已退出方法)
}
04、核心区别总结
关键字 | 作用范围 | 执行后流程 |
---|---|---|
break | 终止当前循环 | 执行循环体外的下一条语句 |
continue | 跳过本次循环剩余代码 | 进入下一次循环的判断(继续迭代) |
return | 退出当前方法(函数) | 直接返回方法调用处,方法内所有代码终止 |
7、Switch
switch
是一种分支选择结构(注意:不是循环结构,循环是重复执行代码块,而 switch
是根据条件选择执行某段代码),主要用于根据一个表达式的值,从多个分支中选择一个执行。
01、switch 的基本作用
switch
的核心作用是:根据一个 “表达式的值”,匹配对应的 case
分支,并执行该分支的代码。
语法结构:
switch (表达式) {case 值1:// 当表达式的值等于值1时执行的代码break; // 可选,用于终止switchcase 值2:// 当表达式的值等于值2时执行的代码break;...default:// 当所有case都不匹配时执行的代码(可选)
}
- 表达式的结果通常是整数、字符、字符串(部分语言支持,如 Java 7+)、枚举等离散值(非连续区间)。
- 执行逻辑:计算表达式的值 → 匹配对应的
case
→ 执行该case
下的代码 → 若遇到break
则退出switch
,否则继续执行后续case
(即 “case 穿透”)。
02、使用场景
switch
更适合判断 “离散的、固定的取值”(如枚举值、有限的常量),而非直接判断 “连续区间”(如 1-10、90-100 这类范围)。但通过一定技巧,也可以处理区间判断。
1. 适合的场景:离散值判断
当需要根据一个变量的固定几个取值执行不同逻辑时,switch
比 if-else if
更简洁。
案例:根据星期几输出对应活动
int day = 3; // 假设3代表周三
switch (day) {case 1:System.out.println("周一:开会");break;case 2:System.out.println("周二:写代码");break;case 3:System.out.println("周三:测试");break;case 4:System.out.println("周四:改bug");break;case 5:System.out.println("周五:总结");break;default:System.out.println("周末:休息");
}
// 输出:周三:测试
这里 day
的值是离散的(1-7),用 switch
比多个 if-else
更清晰。
2. 区间判断的处理(间接实现)
switch
本身不直接支持区间判断(如 case 1-10:
是无效语法),但可以通过将区间映射为离散值来间接处理。常见技巧:对区间进行 “分组”,用一个表达式将区间转换为固定值(如整除),再匹配 case
。
案例:根据分数判断等级(区间:90-100 为 A,80-89 为 B,70-79 为 C,否则为 D)
int score = 85;
// 将分数除以10,把区间转换为离散值(90-100→9或10,80-89→8, etc.)
int group = score / 10; switch (group) {case 10: // 100分case 9: // 90-99分System.out.println("等级:A");break;case 8: // 80-89分System.out.println("等级:B");break;case 7: // 70-79分System.out.println("等级:C");break;default: // 70分以下System.out.println("等级:D");
}
// 输出:等级:B
这里通过 score/10
将区间 90-100
转换为 9
或 10
,再用 case 10
和 case 9
共同处理(利用了 case 穿透,下文详解)。
03、switch 中的 case 穿透
1. 什么是 case 穿透?
当 case
分支后没有 break
语句时,程序会在执行完当前 case
的代码后,继续执行下一个 case
的代码,直到遇到 break
或 switch
结束。这种现象称为 “case 穿透”。
2. 为什么会发生穿透?
switch
的设计逻辑是:匹配到第一个符合的 case
后,从该位置开始向下执行,直到被 break
中断。若缺少 break
,则不会中断,继续执行后续分支。
3. 案例:穿透的风险与合理利用
意外穿透(需避免):忘记写 break
可能导致逻辑错误。
int num = 2;
switch (num) {case 1:System.out.println("执行case 1");case 2:System.out.println("执行case 2");case 3:System.out.println("执行case 3");
}
// 输出:
// 执行case 2
// 执行case 3 (因为case 2后没有break,穿透到case 3)
有意利用穿透(简化代码):当多个 case
需要执行相同逻辑时,可省略中间的 break
,利用穿透合并处理(如上文分数等级案例中 case 10
和 case 9
共用同一逻辑)。
char grade = 'B';
switch (grade) {case 'A':case 'B':System.out.println("优秀"); // A和B都输出“优秀”break;case 'C':System.out.println("及格");break;default:System.out.println("不及格");
}
// 输出:优秀
8、static
static
的核心是 “脱离实例依赖”,合理使用可简化代码(如工具类)、共享数据(如计数器),但过度使用会增加代码耦合性(静态成员生命周期长,不易回收),需谨慎设计。
修饰对象 | 核心作用 | 使用场景典型案例 | 关键特性 |
---|---|---|---|
成员变量 | 类级共享数据,唯一实例 | 实例计数器、全局常量(static final ) | 类加载时初始化,所有实例共享 |
内部类 | 不依赖外部类实例的独立内部类 | 工具类中的辅助类,与外部类静态功能绑定 | 可直接创建,不能访问外部类非静态成员 |
方法 | 类级工具方法,无需实例即可调用 | 数学计算(Math.max() )、工厂方法 | 只能访问静态成员,无this |
代码块 | 类加载时初始化静态资源 | 加载配置、注册驱动 | 仅执行一次,早于构造方法 |
9、final
01、final的作用
1、final修饰的基本类型不可以第二次赋值
2、final修饰的引用类型不可以第二次改变指向
3、final修饰的类不可以被继承
4、final修饰方法不可以被重写
5、final防止指令重排序 遏制流水线优化 可以保证多线程并发场景下的可见性
6、final经常和static一起使用
02、final与finally的区别
finally:
finally
是异常处理机制的一部分,必须与try
块配合使用(可搭配catch
,也可单独使用),用于定义 “无论是否发生异常,都必须执行的代码”。
作用:确保关键资源(如文件流、数据库连接、网络连接等)被释放,或完成必要的清理操作(如日志记录),避免资源泄漏。
使用场景
- 关闭文件输入 / 输出流(
IOStream
) - 释放数据库连接(
Connection
) - 释放锁资源等
案例:
import java.io.FileInputStream;
import java.io.IOException;public class FinallyDemo {public static void readFile() {FileInputStream fis = null;try {fis = new FileInputStream("test.txt");// 模拟读取文件(可能抛出IOException)System.out.println("读取文件中...");// int a = 1 / 0; // 模拟算术异常(非IO异常)} catch (IOException e) {System.out.println("处理IO异常:" + e.getMessage());} finally {// 无论是否发生异常,都会执行:关闭流释放资源if (fis != null) {try {fis.close();System.out.println("文件流已关闭");} catch (IOException e) {e.printStackTrace();}}}}public static void main(String[] args) {readFile();// 输出(无异常时):// 读取文件中...// 文件流已关闭// 若触发算术异常(取消注释try中的1/0):// 读取文件中...// 文件流已关闭(finally仍执行)// 然后抛出ArithmeticException}
}
说明:即使try
块中发生异常(无论是否被catch
捕获),finally
块的代码都会执行,确保资源被释放。
03、区别总结:
关键字 | 本质角色 | 作用范围 / 修饰对象 | 核心功能 | 典型场景 |
---|---|---|---|---|
final | 修饰符 | 变量、方法、类 | 限制修改:变量不可重赋值,方法不可重写,类不可继承 | 定义常量、保护方法逻辑、禁止类继承 |
finally | 异常处理块 | 必须与try 配合使用 | 确保代码必然执行,用于资源释放、清理操作 | 关闭文件流、释放数据库连接等 |
10、权限修饰符
一、区别:访问范围对比
修饰符 | 本类内部 | 同一个包内的类(包括非子类) | 不同包的子类 | 不同包的非子类 |
---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
default(缺省) | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
二、适用场景
-
private:用于隐藏类的内部实现细节(如私有变量、辅助方法),仅允许本类内部访问。典型场景:类的成员变量(通过 getter/setter 对外暴露)、工具类中的私有辅助方法。
-
default(缺省):适用于同一包内的类共享逻辑,但不希望被其他包访问的场景。典型场景:包内工具类的辅助方法、包内共享的常量。
-
protected:用于需要被子类继承访问,但不允许无关类(非子类)访问的成员。典型场景:父类中需要被子类重写的方法、父类的核心属性(子类需要修改但外部不能直接访问)。
-
public:用于对外提供的接口、方法或常量,允许所有类访问。典型场景:工具类的静态方法(如
Math.random()
)、公开的服务接口、需要被实例化的类。
11、封装、继承、多态
一、封装:隐藏细节,控制访问
1. 核心定义
封装是将类的属性(数据)和方法(行为)打包,隐藏内部实现细节,仅通过对外提供的 “接口”(方法)允许外部访问和修改数据,避免数据被非法篡改,保证数据安全性。
2. private 权限修饰符:封装的 “访问控制锁”
作用
- private 是权限修饰符中限制最严格的级别,被修饰的属性 / 方法仅能在当前类内部访问,外部类(包括子类)无法直接读取或修改。
- 通过 private 强制隐藏类的核心数据,从根源上杜绝外部直接操作数据的风险(如给 “年龄” 赋值负数、给 “手机号” 赋值非数字)。
使用示例
class Person {// 用private修饰核心属性,外部无法直接访问private String name;private int age; // 年龄不能为负数,需控制访问// 类内部可访问private属性(如定义方法打印属性)public void printInfo() {System.out.println("姓名:" + name + ",年龄:" + age);}
}// 外部类测试
public class Test {public static void main(String[] args) {Person p = new Person();// 错误:外部无法直接访问private属性// p.name = "张三"; // p.age = -20; }
}
3. get/set 方法:封装的 “对外接口”
作用
- 由于 private 限制了外部访问,需通过 get 方法(获取属性值) 和 set 方法(设置属性值) 作为对外接口,实现对 private 属性的间接操作。
- 关键优势:可在 set 方法中添加数据校验逻辑,确保数据合法性(如年龄必须≥0、姓名不能为空),进一步强化数据安全性。
使用示例
class Person {private String name;private int age;// 1. get方法:获取属性值(命名规则:get+属性名首字母大写)public String getName() {return name; // 内部可访问private属性,返回给外部}public int getAge() {return age;}// 2. set方法:设置属性值(命名规则:set+属性名首字母大写)public void setName(String name) {// 数据校验:姓名不能为空if (name != null && !name.trim().equals("")) {this.name = name;} else {System.out.println("姓名不能为空!");}}public void setAge(int age) {// 数据校验:年龄必须≥0if (age >= 0) {this.age = age;} else {System.out.println("年龄不能为负数!");}}public void printInfo() {System.out.println("姓名:" + name + ",年龄:" + age);}
}// 外部类通过get/set操作属性
public class Test {public static void main(String[] args) {Person p = new Person();// 通过set方法设置属性(触发校验)p.setName("张三");p.setAge(-20); // 触发校验:打印“年龄不能为负数!”p.setAge(25); // 校验通过,成功设置// 通过get方法获取属性System.out.println("获取姓名:" + p.getName()); // 输出“张三”System.out.println("获取年龄:" + p.getAge()); // 输出“25”}
}
二、继承:复用代码,扩展功能
1. 核心定义
继承是子类(Subclass)通过特定语法(如 Java 的extends、Python 的class 子类(父类))继承父类(Superclass)的属性和方法,实现代码复用;同时子类可定义新的属性 / 方法,或重写父类方法,实现功能扩展。
2. 核心特性
- 代码复用:子类无需重复编写父类已有的属性和方法,直接调用。
- 单继承为主:多数语言(如 Java、C#)支持单继承(子类仅能继承一个父类),避免多继承的歧义问题;Python 支持多继承。
- 父类私有成员不可直接继承:子类无法直接访问父类的private属性 / 方法,需通过父类的public的 get/set 方法间接访问。
3. 使用示例
// 父类:Animal(提取共性属性和方法)
class Animal {private String name; // 父类私有属性,子类无法直接访问protected int age; // protected:子类可直接访问(介于public和private之间)// 父类构造器public Animal(String name, int age) {this.name = name;this.age = age;}// 父类public方法:子类可继承public void eat() {System.out.println(name + "正在吃食物");}// 父类get方法:子类可通过此方法访问父类private属性namepublic String getName() {return name;}
}// 子类:Cat(继承Animal,复用父类代码,扩展新功能)
class Cat extends Animal {// 子类新增属性private String color;// 子类构造器:必须先通过super()调用父类构造器public Cat(String name, int age, String color) {super(name, age); // 调用父类构造器,初始化父类属性this.color = color; // 初始化子类新增属性}// 子类重写父类方法:修改父类行为(多态的基础)@Overridepublic void eat() {System.out.println(getName() + "正在吃猫粮"); // 覆盖父类eat()的逻辑}
}// 测试类
public class TestInherit {public static void main(String[] args) {// 创建子类对象Cat cat = new Cat("咪宝", 2, "橘色");// 1. 调用继承自父类的方法(已重写)cat.eat(); // 输出“咪宝正在吃猫粮”// 2. 调用子类新增方法cat.catchMouse(); // 输出“咪宝(2岁,橘色)正在抓老鼠”// 3. 子类通过父类get方法访问父类private属性System.out.println("猫的名字:" + cat.getName()); // 输出“咪宝”}
}
三、多态:同一行为,多种形态
1. 核心定义
多态是指同一方法(或行为)在不同对象上调用时,表现出不同的执行结果。其本质是 “父类引用指向子类对象”,通过方法重写实现行为的差异化。
2. 实现前提
- 存在继承关系(子类继承父类);
- 子类重写父类的方法;
- 父类引用变量指向子类对象(父类类型 变量名 = new 子类类型())。
3. 核心作用
- 提高代码灵活性:同一代码逻辑可处理不同子类对象,无需针对每个子类编写单独代码。
- 提高代码可扩展性:新增子类时,无需修改原有父类引用的代码,直接兼容新子类。
4. 使用示例
// 新增子类:Dog(继承Animal,重写eat()方法)
class Dog extends Animal {public Dog(String name, int age) {super(name, age);}// 重写父类eat()方法,实现狗的“吃”行为@Overridepublic void eat() {System.out.println(getName() + "正在吃狗粮");}// 子类新增方法public void watchHouse() {System.out.println(getName() + "正在看家");}
}// 测试多态
public class Test {// 统一方法:接收父类Animal类型的参数(多态的核心:父类引用接收子类对象)public static void feedAnimal(Animal animal) {animal.eat(); // 调用eat()方法:同一方法,不同对象表现不同}public static void main(String[] args) {// 1. 父类引用指向子类对象(多态的关键语法)Animal animal1 = new Cat("咪宝", 2, "橘色"); // 父类引用指向Cat对象Animal animal2 = new Dog("旺财", 3); // 父类引用指向Dog对象// 2. 调用同一方法eat(),表现不同形态animal1.eat(); // 输出“咪宝正在吃猫粮”(Cat的eat())animal2.eat(); // 输出“旺财正在吃狗粮”(Dog的eat())// 3. 统一调用feedAnimal(),兼容不同子类对象System.out.println("===== 统一喂食 =====");feedAnimal(animal1); // 输出“咪宝正在吃猫粮”feedAnimal(animal2); // 输出“旺财正在吃狗粮”}
}
四、三大特性关联总结
特性 | 核心目标 | 关键机制 | 相互支撑关系 |
---|---|---|---|
封装 | 数据安全 | private 修饰符 + get/set 方法 | 为继承提供 “安全的父类结构”(父类私有属性通过接口暴露) |
继承 | 代码复用 | extends+ super 关键字 | 是多态的前提(无继承则无 “父类引用指向子类对象”) |
多态 | 代码灵活与扩展 | 方法重写 + 父类引用指向子类对象 | 基于继承实现,让复用的代码更具适应性 |
12、接口
一、接口与耦合度:接口是降低耦合的核心工具
耦合度指的是软件系统中模块(或组件)之间的依赖程度。
- 高耦合:模块之间直接依赖具体实现(如 A 模块直接调用 B 模块的具体方法),修改 B 会直接影响 A,导致系统僵化、难以维护。
- 低耦合:模块之间仅依赖 “抽象规范” 而非具体实现,修改一方不影响另一方,系统更灵活。
接口(Interface)的核心作用就是通过定义 “抽象规范” 降低耦合度。接口只声明 “必须做什么”(方法名、参数、返回值),不定义 “具体怎么做”(方法实现)。调用方只需依赖接口,无需关心接口的具体实现类;实现类只需遵循接口规范,无需关心谁会调用它。这种 “依赖抽象而非具体” 的设计,彻底切断了模块间的直接依赖,大幅降低耦合度。
二、为什么使用接口?—— 作用、场景与案例
接口的价值体现在 “规范约束”“解耦扩展”“多态支持” 三个核心作用,对应不同的使用场景:
1. 作用 1:定义规范,统一交互标准
接口本质是一种 “契约”,强制所有实现类遵循同一套交互规则,避免因实现差异导致的混乱。
场景:多团队协作开发时,需明确模块间的交互方式(如前端与后端、服务 A 与服务 B)。案例:电商系统的 “订单支付” 模块。
- 定义
Payment
接口:声明pay(Order order)
方法(规范:必须接收订单对象,返回支付结果)。 - 支付宝、微信支付、银联等团队分别实现
Payment
接口(各自写具体支付逻辑)。 - 订单模块只需调用
Payment
接口的pay
方法,无需关心是支付宝还是微信 —— 因为接口已经统一了调用标准。
2. 作用 2:解耦模块,提升可维护性
接口让调用方与实现方 “分离”,修改实现类时无需改动调用方代码,降低维护成本。
场景:系统需替换底层实现(如更换数据库、更换第三方服务)。案例:Java 的 JDBC 接口。
- JDBC 定义了
Connection
、Statement
等接口(规范数据库操作的标准)。 - MySQL、Oracle 等厂商提供各自的实现类(驱动包)。
- 应用程序只需依赖 JDBC 接口写代码(如
conn = DriverManager.getConnection(...)
),当从 MySQL 切换到 Oracle 时,只需换驱动包,应用代码无需修改 —— 因为接口隔离了应用与具体数据库的依赖。
3. 作用 3:支持多态,提升可扩展性
接口允许同一接口有多个实现类,调用方通过接口即可动态切换不同实现,轻松扩展功能。
场景:系统需新增功能(如新增支付方式、新增日志存储方式)。案例:日志框架的 “输出” 模块。
- 定义
Logger
接口:声明log(String message)
方法。 - 实现类:
ConsoleLogger
(控制台输出)、FileLogger
(文件输出)、MqLogger
(消息队列输出)。 - 当需要新增 “数据库日志” 时,只需新增
DbLogger
实现Logger
接口,调用日志的业务代码(如logger.log("操作成功")
)无需任何修改 —— 接口让扩展变得 “无侵入”。
三、为什么面向接口开发?—— 以接口为中心的设计思想
“面向接口开发” 是一种先定义接口、再实现功能的设计思想,核心目标是通过接口提前规划系统的 “骨架”,让系统更易扩展、更易维护。
核心意义:
- 提前锁定交互边界:在编码前先定义接口,明确模块间 “能做什么、不能做什么”,避免后期因需求变更导致的模块冲突。
- 支持 “并行开发”:接口定义后,调用方可以基于接口写代码(无需等待实现类),实现方可以独立开发具体逻辑,提升团队协作效率。
- 应对需求变化:需求变更往往是 “实现逻辑变了,但交互规则不变”,面向接口开发时,只需修改实现类,调用方代码不动,大幅降低变更成本。
案例:电商系统的 “库存扣减” 模块
- 初期需求:仅支持 “本地库存扣减”。面向接口开发:先定义
StockService
接口(deduct(String sku, int num)
),再实现LocalStockService
。 - 后期需求:新增 “分布式库存扣减”(跨仓库调货)。无需修改调用方(如订单模块),只需新增
DistributedStockService
实现StockService
,通过配置动态切换实现类即可。
总结
- 接口通过 “依赖抽象” 降低模块耦合度,是解耦的核心工具;
- 使用接口的本质是 “用规范约束交互、用抽象隔离变化”,解决多实现、易维护、可扩展的问题;
- 面向接口开发是一种 “以契约为中心” 的设计思想,让系统在需求变化中保持灵活,最终实现 “高内聚、低耦合” 的目标。
13、子类父类转换
强转,自动转,不可转以及instanceof方法的使用
类型转换
转换类型 | 方向 | 条件 | 安全性 | 示例 |
---|---|---|---|---|
向上转型 | 子类 → 父类 | 存在继承关系 | 安全(自动) | Animal a = new Dog(); |
向下转型 | 父类 → 子类 | 父类引用实际指向该子类对象 | 需强转(可能不安全) | Dog d = (Dog) a; (需先判instanceof ) |
不可转换 | 无继承关系的类 | 无继承关系 | 编译报错 | Dog d = (Dog) new Cat(); |
instanceof
方法
判断对象类型,避免转换异常
作用:
instanceof
是 Java 的关键字,用于判断某个对象是否是指定类(或接口)的实例,返回boolean
值。其核心作用是在向下转型前检查对象的实际类型,避免ClassCastException
。
语法:
对象 instanceof 类/接口
使用场景与案例:
在强制转换前,先用instanceof
判断对象是否为目标类型,确保转换安全:
Animal animal = new Dog();// 先判断animal是否是Dog的实例
if (animal instanceof Dog) { Dog dog = (Dog) animal; // 安全转换dog.bark();
} else if (animal instanceof Cat) {Cat cat = (Cat) animal; // 若指向Cat,也可安全转换cat.meow();
}
注意:
instanceof
会考虑 “继承链”:若A
是B
的父类,B
的对象 instanceof A
也会返回true
。例:Dog dog = new Dog(); System.out.println(dog instanceof Animal); // 输出true
(因为 Dog 是 Animal 的子类)。
14、==与equals的区别
在 Java 中,==
和equals
是用于比较 “相等性” 的两种方式,但它们的比较逻辑和适用场景有本质区别。核心差异在于:==
主要比较 “值” 或 “内存地址”,而equals
主要用于比较对象的 “内容”(但需看是否重写)。下面结合具体场景和案例详细解析:
一、==
:比较 “值” 或 “内存地址”
==
是 Java 的运算符,其作用取决于比较的对象类型:
1. 比较基本数据类型(如int
、char
、double
等)
对于基本类型,==
直接比较两者的 “值” 是否相等。这是因为基本类型变量存储的是 “具体数值”,而非对象引用。
案例:
int a = 10;
int b = 10;
System.out.println(a == b); // 输出true(值相等)char c = 'A';
char d = 65; // 'A'的ASCII码是65
System.out.println(c == d); // 输出true(值相等)
2. 比较引用数据类型(如对象、数组、字符串引用等)
对于引用类型,==
比较的是两个引用是否指向内存中的 “同一个对象”(即比较内存地址是否相同)。引用类型变量存储的是 “对象在内存中的地址”,而非对象本身的内容。
案例:
// 自定义类(未重写equals)
class Person {String name;Person(String name) { this.name = name; }
}Person p1 = new Person("张三");
Person p2 = new Person("张三");
Person p3 = p1; // p3和p1指向同一个对象System.out.println(p1 == p2); // 输出false(p1和p2是不同对象,地址不同)
System.out.println(p1 == p3); // 输出true(p1和p3指向同一对象,地址相同)
注意:字符串常量池的特殊情况Java 中字符串有 “常量池” 机制,直接赋值的字符串(如"abc"
)会被缓存,而new String("abc")
会创建新对象:
String s1 = "abc";
String s2 = "abc"; // 从常量池获取,与s1指向同一对象
String s3 = new String("abc"); // 新对象,地址不同System.out.println(s1 == s2); // true(同一对象)
System.out.println(s1 == s3); // false(不同对象)
二、equals
:默认比较 “地址”,重写后比较 “内容”
equals
是Object
类中定义的方法,其作用是比较两个对象的 “内容是否相等”,但默认实现与==
一致,需要通过 “重写” 才能实现内容比较。
1. Object
类的默认equals
实现
Object
类中equals
的源码如下:
public boolean equals(Object obj) {return (this == obj); // 本质就是用==比较地址
}
即:如果一个类没有重写equals
,那么equals
与==
的效果完全一致 —— 比较两个引用是否指向同一对象。
2. 重写equals
:比较对象 “内容”
实际开发中,我们通常希望比较对象的 “内容”(而非地址),因此需要重写equals
方法。Java 中许多内置类(如String
、Integer
、ArrayList
等)都重写了equals
,用于比较内容。
案例 1:String
的equals
(重写后比较字符内容)
String s1 = "abc";
String s3 = new String("abc");// equals比较内容,==比较地址
System.out.println(s1.equals(s3)); // true(内容都是"abc")
System.out.println(s1 == s3); // false(地址不同)
案例 2:自定义类重写equals
(比较属性内容)
class Person {String name;int age;Person(String name, int age) { this.name = name; this.age = age; }// 重写equals:当name和age都相同时,认为两个对象相等@Overridepublic boolean equals(Object obj) {if (this == obj) return true; // 同一对象,直接返回trueif (obj == null || getClass() != obj.getClass()) return false; // 类型不同,返回falsePerson person = (Person) obj; // 强转为Personreturn age == person.age && name.equals(person.name); // 比较属性}
}// 测试
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println(p1.equals(p2)); // true(内容相等)
System.out.println(p1 == p2); // false(地址不同)
3. 使用equals
的注意事项
- 不能用于基本类型:
equals
是对象的方法,基本类型(如int
)不能调用equals
(需装箱为包装类,如Integer
)。 - 避免空指针异常:如果对象可能为
null
,直接调用equals
会报错,建议用 “常量调用 equals” 的方式:String str = null; // str.equals("abc"); // 报错:NullPointerException System.out.println("abc".equals(str)); // 安全,输出false
三、==
与equals
的核心区别总结
维度 | == | equals |
---|---|---|
本质 | 运算符 | Object 类的方法 |
比较内容(基本类型) | 比较 “值” 是否相等(适用) | 不适用(基本类型不能调用方法) |
比较内容(引用类型) | 比较 “内存地址”(是否为同一对象) | 默认比较地址,重写后比较 “对象内容” |
适用场景 | 判断基本类型值相等、判断引用是否指向同一对象 | 判断两个对象的内容是否逻辑相等 |
空指针风险 | 无(null == null 返回 true) | 有(若对象为 null,调用时会抛异常) |
四、典型使用场景
- 用
==
:比较基本类型的值(如a == 10
)、判断两个引用是否指向同一对象(如list1 == list2
)、判断对象是否为null
(如obj == null
)。 - 用
equals
:比较字符串内容(如str1.equals(str2)
)、比较自定义对象的属性是否一致(如p1.equals(p2)
)、比较包装类的值(如Integer a = 10; a.equals(10)
)。
15、Object类是java提供的,java核心根本是jdk
16、String,StringBuffer,StringBuilder
在 Java 中,String
、StringBuffer
、StringBuilder
都是用于处理字符串的类,但它们在可变性、线程安全和性能上有本质区别,适用场景也截然不同。下面从作用、使用场景和案例三个维度详细解析:
一、核心特性与作用:本质差异的根源
三者的核心区别源于是否可变性和是否线程安全,这直接决定了它们的作用和适用场景:
类 | 可变性 | 线程安全 | 底层实现(简化) | 核心作用 |
---|---|---|---|---|
String | 不可变 | 天然安全(无修改) | private final char[] value | 存储 “不频繁修改” 的字符串,作为常量或只读数据(如配置项、固定文本)。 |
StringBuffer | 可变 | 安全(同步方法) | char[] value (无 final 修饰) | 多线程环境下,处理 “需要频繁修改” 的字符串(如多线程拼接日志、共享字符串缓存)。 |
StringBuilder | 可变 | 不安全(无同步) | char[] value (无 final 修饰) | 单线程环境下,高效处理 “需要频繁修改” 的字符串(如单线程拼接 SQL、动态生成文本)。 |
关键概念解释:
- 不可变(
String
):String
的底层字符数组被final
修饰,一旦创建,字符序列不可修改。任何 “修改” 操作(如拼接、替换)都会创建新的String
对象,原对象不变。 - 可变(
StringBuffer
/StringBuilder
):底层字符数组无final
修饰,可直接修改字符序列(通过append
、insert
等方法),无需创建新对象,节省内存和时间。 - 线程安全(
StringBuffer
):所有修改方法(如append
)都被synchronized
修饰,多线程同时操作时不会出现数据混乱;StringBuilder
无同步,多线程操作可能导致数据错误。
二、使用场景:根据 “修改频率” 和 “线程环境” 选择
1. String
:适合 “字符串不频繁修改” 的场景
当字符串内容固定(或修改极少)时,用String
更简洁高效。因为不可变特性可缓存复用(如字符串常量池),且无需处理线程安全的额外开销。
典型场景:
- 存储常量(如
String name = "张三";
)、配置项(如String url = "https://xxx";
); - 字符串仅需少量拼接(如
"Hello" + "World"
,编译期会优化为常量); - 作为方法参数或返回值(传递的是不可变数据,避免意外修改)。
案例:
// 字符串固定,无需修改
String userName = "admin";
String sqlTemplate = "SELECT * FROM user WHERE id = ?"; // 模板固定,仅参数替换(修改少)// 少量拼接(编译期优化为"HelloWorld",不产生额外对象)
String greeting = "Hello" + "World";
2. StringBuffer
:适合 “多线程 + 频繁修改” 的场景
当多个线程需要同时修改同一个字符串(如共享的日志缓冲区、多线程协作生成文本)时,必须保证线程安全,此时用StringBuffer
。
典型场景:
- 多线程日志框架(如多个线程同时往日志缓冲区追加内容);
- 分布式系统中,多线程拼接共享的消息字符串(如分布式事务日志)。
案例:多线程拼接日志
// 多线程共享的日志缓冲区
StringBuffer logBuffer = new StringBuffer();// 线程1:追加操作日志
new Thread(() -> {logBuffer.append("[Thread1] 操作成功:").append(System.currentTimeMillis()).append("\n");
}).start();// 线程2:追加错误日志
new Thread(() -> {logBuffer.append("[Thread2] 错误:").append("连接超时").append("\n");
}).start();// 最终logBuffer的内容不会因多线程混乱(因StringBuffer同步)
3. StringBuilder
:适合 “单线程 + 频繁修改” 的场景
单线程环境下,无需考虑线程安全,StringBuilder
因省去同步开销,性能远高于StringBuffer
,是频繁修改字符串的首选。
典型场景:
- 单线程内动态生成字符串(如拼接 SQL、组装 JSON 字符串);
- 循环中频繁追加内容(如读取文件时拼接每行文本);
- 字符串的复杂修改(如插入、替换、删除字符)。
案例:单线程拼接 SQL
// 单线程中动态生成查询SQL
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("SELECT id, name FROM user WHERE 1=1 ");// 动态添加条件(频繁修改)
String name = "张三";
int age = 20;
if (name != null) {sqlBuilder.append("AND name = '").append(name).append("' ");
}
if (age > 0) {sqlBuilder.append("AND age = ").append(age);
}String sql = sqlBuilder.toString(); // 最终SQL:SELECT id, name FROM user WHERE 1=1 AND name = '张三' AND age = 20
三、性能对比:为什么单线程优先选StringBuilder
?
频繁修改字符串时,String
的性能极差(每次修改创建新对象),而StringBuffer
因同步开销性能低于StringBuilder
。
案例:循环拼接 10 万次字符串的性能测试
public class StringTest {public static void main(String[] args) {// 1. String拼接(性能最差)long start1 = System.currentTimeMillis();String s = "";for (int i = 0; i < 100000; i++) {s += i; // 每次创建新String对象}long end1 = System.currentTimeMillis();System.out.println("String耗时:" + (end1 - start1) + "ms"); // 约10000ms(视环境而定)// 2. StringBuffer拼接(多线程安全,性能中等)long start2 = System.currentTimeMillis();StringBuffer sb = new StringBuffer();for (int i = 0; i < 100000; i++) {sb.append(i); // 同步方法,有开销}long end2 = System.currentTimeMillis();System.out.println("StringBuffer耗时:" + (end2 - start2) + "ms"); // 约10ms// 3. StringBuilder拼接(单线程高效,性能最优)long start3 = System.currentTimeMillis();StringBuilder sbd = new StringBuilder();for (int i = 0; i < 100000; i++) {sbd.append(i); // 无同步,开销小}long end3 = System.currentTimeMillis();System.out.println("StringBuilder耗时:" + (end3 - start3) + "ms"); // 约5ms}
}
结果分析:
String
因频繁创建新对象,耗时是StringBuilder
的几千倍;StringBuilder
比StringBuffer
快约 50%(因省去synchronized
的同步开销)。
四、总结:如何选择?
- 字符串不修改或修改极少:用
String
(简洁、可复用); - 单线程 + 频繁修改:用
StringBuilder
(性能最优); - 多线程 + 频繁修改:用
StringBuffer
(保证线程安全,牺牲部分性能)。
记住核心原则:优先根据 “是否频繁修改” 和 “是否多线程” 选择,性能敏感的单线程场景坚决不用StringBuffer
,多线程场景避免用StringBuilder
。
17、八个包装类和日期类
在 Java 中,八个包装类和日期类是处理基本数据类型对象化、时间日期操作的核心工具。它们的设计解决了特定场景下的问题(如基本类型无法直接用于集合、旧日期类的设计缺陷等),下面从作用、使用场景和案例展开解析。
一、八个包装类:基本数据类型的 “对象化” 包装
Java 是面向对象语言,但存在 8 种基本数据类型(boolean
、byte
、short
、int
、long
、float
、double
、char
),它们不是对象,无法直接参与 “面向对象” 的操作(如作为集合的元素、调用方法等)。包装类的作用就是将基本类型 “包装” 为对象,弥补这一缺陷。
1. 八个包装类的对应关系与继承体系
基本数据类型 | 包装类 | 父类 / 特点 | 备注 |
---|---|---|---|
boolean | Boolean | 直接继承Object | 无数值操作,仅布尔值包装 |
byte | Byte | 继承Number ,实现Comparable | 字节型(8 位) |
short | Short | 继承Number ,实现Comparable | 短整型(16 位) |
int | Integer | 继承Number ,实现Comparable | 最常用的数值包装类 |
long | Long | 继承Number ,实现Comparable | 长整型(64 位) |
float | Float | 继承Number ,实现Comparable | 单精度浮点型 |
double | Double | 继承Number ,实现Comparable | 双精度浮点型 |
char | Character | 直接继承Object ,实现Comparable | 字符型包装(无Number ) |
Number
类:是除Boolean
和Character
外的 6 个数值包装类的父类,提供了intValue()
、longValue()
等方法,用于将包装类对象 “拆箱” 为基本类型。Comparable
接口:所有包装类都实现了该接口,支持对象之间的比较(如Integer
的大小比较)。
2. 核心作用与关键特性
- 对象化包装:将基本类型转为对象,使其能参与集合(如
List<Integer>
)、泛型等面向对象场景。 - 自动装箱与拆箱:Java 5 + 支持自动转换(无需手动调用
valueOf()
或xxxValue()
):- 装箱:基本类型 → 包装类(如
int i = 10; Integer obj = i;
)。 - 拆箱:包装类 → 基本类型(如
Integer obj = 10; int i = obj;
)。
- 装箱:基本类型 → 包装类(如
- 工具方法:提供了类型转换、常量定义等工具方法(如
Integer.parseInt("123")
将字符串转 int)
二、日期类:处理时间与日期的工具
Java 中日期类的发展经历了三个阶段:早期的Date
、Calendar
,以及 Java 8 + 的新日期时间 API(java.time
包)。旧类因设计缺陷(可变性、线程不安全、API 混乱)被新 API 替代,新 API 是目前开发的首选。
核心类及作用
类名 | 作用 | 示例 |
---|---|---|
LocalDate | 仅包含日期(年 / 月 / 日),无时间 | 2025-10-20 |
LocalTime | 仅包含时间(时 / 分 / 秒 / 纳秒),无日期 | 15:30:45.123 |
LocalDateTime | 包含日期 + 时间(最常用) | 2025-10-20T15:30:45 |
ZonedDateTime | 包含日期 + 时间 + 时区(如 UTC、CST) | 2025-10-20T15:30:45+08:00[Asia/Shanghai] |
DateTimeFormatter | 日期格式化 / 解析(替代旧SimpleDateFormat ) | 自定义格式如yyyy-MM-dd HH:mm:ss |
18、集合体系
一、Collection 集合体系(单列集合)
1. List 接口(有序、可重复,允许 null 元素)
核心特点:元素有索引,可通过索引操作,允许重复元素。
实现类 | 底层结构 | 核心特点 | 适用场景 |
---|---|---|---|
ArrayList | 动态数组(Object []) | 1. 查询快(直接通过索引访问,O (1));2. 增删慢(需移动元素,O (n));3. 线程不安全;4. 初始容量默认 10(JDK8 懒加载,首次添加时扩容),扩容为 1.5 倍(old + old>>1)。 | 频繁查询,少增删 |
LinkedList | 双向链表 | 1. 增删快(仅需修改指针,O (1),首尾操作最优);2. 查询慢(需从头 / 尾遍历,O (n));3. 线程不安全;4. 实现 Deque 接口,可作为队列 / 栈使用。 | 频繁增删(尤其是首尾) |
Vector | 动态数组(Object []) | 1. 与 ArrayList 类似,但线程安全(方法加 synchronized);2. 初始容量 10,扩容为 2 倍;3. 效率低,基本被 ArrayList 替代。 | 多线程场景(已过时,推荐 Collections.synchronizedList ()) |
2. Set 接口(无序、不可重复,允许 null 元素)
核心特点:元素唯一(去重),无索引,不能通过索引访问。
实现类 | 底层结构 | 核心特点 | 适用场景 |
---|---|---|---|
HashSet | 依赖 HashMap(数组 + 链表 + 红黑树) | 1. 无序(存储顺序与插入顺序无关);2. 去重依赖元素的 hashCode () 和 equals ();3. 线程不安全;4. 元素可为 null(仅一个)。 | 无需排序,仅需去重 |
LinkedHashSet | HashSet + 双向链表 | 1. 继承 HashSet,底层多了双向链表维护插入顺序(有序);2. 去重规则同 HashSet;3. 性能略低于 HashSet(维护链表开销)。 | 需要保持插入顺序的去重场景 |
TreeSet | 依赖 TreeMap(红黑树) | 1. 有序(自然排序或定制排序);2. 去重依赖比较器(Comparable 或 Comparator),返回 0 则视为重复;3. 线程不安全;4. 元素不可为 null(排序时会报错)。 | 需要排序的去重场景 |
3. Queue 队列(先进先出,FIFO)
核心特点:主要用于存储按顺序处理的元素,提供队列操作(入队、出队)。
典型实现类 | 底层结构 | 特点 |
---|---|---|
LinkedList | 双向链表 | 实现 Deque 接口,可作为队列(offer/poll)或栈(push/pop)使用。 |
PriorityQueue | 小顶堆(数组) | 优先队列,元素按优先级排序(自然排序或 Comparator),非 FIFO。 |
二、集合遍历方式
-
普通 for 循环:仅适用于 List(有索引),通过
get(index)
访问元素。for (int i = 0; i < list.size(); i++) { ... }
-
增强 for 循环(for-each):适用于所有 Collection(实现 Iterable 接口),简洁但不能修改集合结构(否则抛 ConcurrentModificationException)。
for (String s : list) { ... }
-
迭代器(Iterator):适用于所有 Collection,支持在遍历中安全删除元素(通过
iterator.remove()
)。Iterator<String> it = list.iterator(); while (it.hasNext()) {String s = it.next();if (条件) it.remove(); // 安全删除 }
三、泛型(Generic)
- 作用:约束集合中元素的类型,编译时检查,避免运行时 ClassCastException。
- 语法:
Collection<Type>
, 如List<String>
(只能存 String)。 - 泛型擦除:编译后泛型信息被擦除,底层仍为 Object(JVM 无泛型类型)。
四、可变参数
- 语法:
类型... 参数名
(本质是数组),用于方法参数个数不确定的场景。 - 规则:一个方法只能有一个可变参数,且必须位于参数列表最后。
public void sum(int... nums) { // nums是int[]for (int n : nums) { ... } }
五、数组工具类(Arrays)
提供操作数组的静态方法:
sort(arr)
:排序(基本类型快排,对象归并排序)。binarySearch(arr, key)
:二分查找(返回索引,需先排序)。toString(arr)
:数组转字符串(如[1, 2, 3]
)。copyOf(arr, newLength)
:复制数组(新数组长度为 newLength)。asList(arr)
:数组转 List(返回固定大小的 List,不能 add/remove)。
六、集合工具类(Collections)
提供操作集合的静态方法:
sort(list)
:排序 List(元素需实现 Comparable 或传入 Comparator)。reverse(list)
:反转 List 元素顺序。shuffle(list)
:随机打乱 List 元素。synchronizedList(list)
:将集合转为线程安全的(加同步锁)。max/min(collection)
:获取最大 / 小元素(依赖 Comparable)。
七、核心类底层原理
1. ArrayList 底层原理
- 底层结构:动态数组(
transient Object[] elementData
,transient 避免序列化空元素)。 - 初始化:
- 无参构造器:JDK8 初始化为空数组(
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
),首次添加元素时扩容为 10。 - 有参构造器:直接初始化指定容量的数组。
- 无参构造器:JDK8 初始化为空数组(
- 扩容机制:
- 当
size == elementData.length
(元素个数达容量)时触发扩容。 - 扩容后容量:默认 1.5 倍(
oldCapacity + (oldCapacity >> 1)
);若需超大容量(> Integer.MAX_VALUE - 8),则扩容至 Integer.MAX_VALUE。 - 扩容过程:通过
Arrays.copyOf()
复制旧数组到新数组(耗时操作,建议初始化时指定容量)。
- 当
2. HashMap 底层原理(JDK8)
- 底层结构:数组(
Node[] table
,哈希桶)+ 链表 + 红黑树(解决哈希冲突)。 - 存储流程:
- 计算 key 的 hash 值:
(key.hashCode() ^ (key.hashCode() >>> 16))
(高位低位混合,减少冲突)。 - 计算索引:
hash & (table.length - 1)
(table 长度为 2ⁿ,保证索引均匀分布)。 - 插入元素:
- 若索引位置为空,直接放入 Node。
- 若不为空:
- 若 key 相同(hash 相等且 equals 为 true),覆盖 value。
- 若不同,链表存储;当链表长度 > 8 且 table 长度≥64 时,转为红黑树(查询效率从 O (n)→O (logn))。
- 计算 key 的 hash 值:
- 扩容机制:
- 初始容量 16,负载因子 0.75(阈值 = 容量 × 负载因子,默认 12)。
- 当元素个数(size)> 阈值时,扩容为 2 倍(保证 2ⁿ),重新计算索引并转移元素(尾插法,避免 JDK7 头插法的循环链表问题)。
- 线程安全:线程不安全(多线程扩容可能导致数据丢失)。
3. HashSet 底层原理
- 本质:依赖 HashMap 实现(
private transient HashMap<E, Object> map
)。 - 存储逻辑:add (E e) → 调用 map.put (e, PRESENT)(PRESENT 是静态空 Object,仅占位)。
- 去重原理:同 HashMap 的 key 去重规则 —— 两个元素 hashCode () 相等且 equals () 为 true,则视为重复,不添加。
八、Map 集合体系(双列集合,key-value 键值对)
实现类 | 底层结构 | 核心特点 | 适用场景 |
---|---|---|---|
HashMap | 数组 + 链表 + 红黑树 | 1. 无序(key 存储顺序与插入顺序无关);2. key 和 value 均可为 null(key 仅一个 null);3. 线程不安全,效率高;4. 去重依赖 key 的 hashCode () 和 equals ()。 | 无需排序的键值对存储 |
TreeMap | 红黑树 | 1. 有序(key 按自然排序或定制排序);2. 去重依赖比较器(Comparable 或 Comparator),返回 0 则 key 重复;3. 线程不安全;4. key 不可为 null(排序报错)。 | 需要按 key 排序的场景 |
HashTable | 哈希表 | 1. 古老实现,线程安全(方法加 synchronized);2. key 和 value 均不可为 null;3. 初始容量 11,扩容为 2 倍 + 1;4. 效率低,基本被 HashMap 替代。 | 多线程场景(已过时) |
LinkedHashMap | HashMap + 双向链表 | 1. 继承 HashMap,通过双向链表维护 key 的插入顺序或访问顺序(accessOrder=true 时为访问顺序,可实现 LRU 缓存);2. 其他特性同 HashMap。 | 需要保持顺序的键值对存储 |
TreeMap 的 Comparable 接口作用
- 排序规则:key 需实现
Comparable<T>
接口,重写compareTo(T o)
方法(自然排序);或创建 TreeMap 时传入Comparator<T>
(定制排序)。 - 去重实现:通过
compareTo()
或compare()
返回值判断 —— 返回 0 时,视为 key 相同,新 value 覆盖旧 value。
总结
- List:有序可重复,选 ArrayList(查快)或 LinkedList(增删快)。
- Set:无序去重选 HashSet,有序去重选 LinkedHashSet,排序去重选 TreeSet。
- Map:无序选 HashMap,排序选 TreeMap,需顺序选 LinkedHashMap。
- 线程安全:优先用
Collections.synchronizedXXX()
包装非线程安全集合,而非 Vector/HashTable。
19、文件类File
File 类是 Java 中操作文件和目录路径的核心类,仅处理路径信息(如判断是否存在、创建 / 删除),不涉及文件内容的读写。以下按核心功能模块整理其关键 API。
功能分类 | 方法名 | 功能描述 | 返回值 | 关键注意事项 |
---|---|---|---|---|
构造器 | File(String pathname) | 通过路径字符串创建 File 对象(路径可为绝对 / 相对路径) | File 对象 | 仅初始化路径,不创建物理文件 / 目录 |
File(File parent, String child) | 通过父目录 File 对象 + 子路径创建 File 对象 | File 对象 | 父目录可不存在(仅表示路径关系) | |
File(String parent, String child) | 通过父目录路径 + 子路径创建 File 对象 | File 对象 | 等价于new File(parent + "/" + child) | |
创建功能 | createNewFile() | 创建新文件(若文件已存在,返回 false) | boolean(成功 / 失败) | 1. 需处理IOException ;2. 父目录不存在时抛异常;3. 仅创建文件,不创建目录 |
mkdir() | 创建单级目录(父目录不存在或目录已存在则失败) | boolean(成功 / 失败) | 仅能创建 “最后一级目录”,如"a/b" 中若a 不存在则失败 | |
mkdirs() | 创建多级目录(父目录不存在则自动创建,目录已存在则失败) | boolean(成功 / 失败) | 推荐用于创建目录(支持多级),如"a/b/c" 可直接创建三级目录 | |
删除功能 | delete() | 删除文件或空目录(文件 / 目录不存在则失败) | boolean(成功 / 失败) | 1. 永久删除(无回收站);2. 非空目录删除失败(需先删内部内容) |
deleteOnExit() | 注册删除:程序退出时删除文件 / 空目录(不立即执行) | void | 适合临时文件清理,JVM 退出前触发删除 | |
路径信息获取 | getAbsolutePath() | 获取文件 / 目录的绝对路径(完整路径) | String | 无论构造器传入相对 / 绝对路径,均返回完整路径 |
getPath() | 获取构造器中传入的路径(可能是相对路径) | String | 与创建时传入的路径一致(如构造器传"src/a.txt" ,则返回"src/a.txt" ) | |
getName() | 获取文件 / 目录的名称(最后一级路径) | String | 如"D:/a/b.txt" 返回"b.txt" ,"D:/a/b" 返回"b" | |
getParent() | 获取父目录的路径(无父目录则返回 null) | String | 如"D:/a/b.txt" 返回"D:/a" | |
getParentFile() | 获取父目录的 File 对象 | File | 便于后续操作父目录(如创建、删除父目录) | |
属性信息获取 | length() | 获取文件大小(单位:字节) | long | 1. 目录的 length () 返回 0;2. 文件不存在时返回 0 |
lastModified() | 获取最后修改时间(毫秒数,自 1970-01-01 00:00:00 起) | long | 可通过new Date(lastModified()) 转为日期格式 | |
状态判断 | exists() | 判断文件 / 目录是否存在 | boolean | 所有创建 / 删除操作前建议先判断 |
isFile() | 判断是否为文件(非目录) | boolean | 仅当exists() 为 true 时结果有效(不存在时返回 false) | |
isDirectory() | 判断是否为目录(非文件) | boolean | 同isFile() ,需先确保exists() 为 true | |
canRead() | 判断是否可读(系统权限) | boolean | 依赖操作系统权限设置 | |
canWrite() | 判断是否可写(系统权限) | boolean | 依赖操作系统权限设置 | |
isHidden() | 判断是否为隐藏文件 / 目录(Windows 隐藏属性 / Linux 以 “.” 开头) | boolean | 隐藏属性由系统定义,与文件内容无关 |
一、File 类的构造器(创建 File 对象)
File 构造器用于初始化 “文件 / 目录的路径对象”,不实际创建物理文件 / 目录。常用构造器有 3 种,需区分绝对路径(完整路径,如D:/test.txt
)和相对路径(基于项目根目录,如src/test.txt
)。
构造器格式 | 参数说明 | 示例代码 |
---|---|---|
File(String pathname) | pathname :文件 / 目录的路径字符串(绝对 / 相对) | File file = new File("D:/demo/test.txt"); (绝对路径)File file = new File("src/test.txt"); (相对路径) |
File(File parent, String child) | parent :父目录的 File 对象;child :子文件 / 目录名 | File parent = new File("D:/demo"); File file = new File(parent, "test.txt"); |
File(String parent, String child) | parent :父目录路径字符串;child :子文件 / 目录名 | File file = new File("D:/demo", "test.txt"); (等价于第一种绝对路径示例) |
二、文件 / 目录的创建功能 API
创建功能需注意:创建文件前需确保父目录存在,否则会抛IOException
;创建目录时需区分 “单级” 和 “多级”。
方法名 | 功能描述 | 返回值 | 关键注意事项 |
---|---|---|---|
boolean createNewFile() | 创建文件(若文件已存在,返回 false) | true = 创建成功false = 文件已存在 | 1. 需处理IOException (必须 try-catch 或 throws);2. 父目录不存在时抛异常。 |
boolean mkdir() | 创建单级目录(若目录已存在或父目录不存在,返回 false) | true = 创建成功false = 失败 | 仅能创建 “最后一级目录”,如父目录D:/demo 不存在时,new File("D:/demo/a").mkdir() 会失败。 |
boolean mkdirs() | 创建多级目录(若目录已存在,返回 false;父目录不存在则自动创建) | true = 创建成功false = 失败 | 推荐用于创建目录,如new File("D:/demo/a/b/c").mkdirs() 会自动创建 demo、a、b、c 四级目录。 |
示例代码(创建文件 + 多级目录)
import java.io.File;
import java.io.IOException;public class FileCreateDemo {public static void main(String[] args) throws IOException {// 1. 创建多级父目录File parentDir = new File("D:/test/demo");if (!parentDir.exists()) { // 判断目录是否已存在parentDir.mkdirs(); // 创建多级目录System.out.println("父目录创建成功");}// 2. 在父目录下创建文件File file = new File(parentDir, "test.txt");if (file.createNewFile()) {System.out.println("文件创建成功");} else {System.out.println("文件已存在");}}
}
三、文件 / 目录的删除功能 API
删除功能为永久删除(无回收站),需谨慎使用;删除目录时,仅能删除空目录(非空目录需先删除内部文件 / 子目录)。
方法名 | 功能描述 | 返回值 | 关键注意事项 |
---|---|---|---|
boolean delete() | 删除文件或空目录(若文件 / 目录不存在,返回 false) | true = 删除成功false = 失败 | 1. 删除非空目录时返回 false;2. 删除后立即生效,不可恢复。 |
void deleteOnExit() | 程序退出(JVM 关闭)时,删除文件或空目录(注册删除,不立即执行) | 无返回值 | 适合创建临时文件,确保程序退出后自动清理,如new File("temp.txt").deleteOnExit(); |
示例代码(删除文件 + 空目录)
public class FileDeleteDemo {public static void main(String[] args) {// 1. 删除文件File file = new File("D:/test/demo/test.txt");if (file.delete()) {System.out.println("文件删除成功");} else {System.out.println("文件不存在或删除失败");}// 2. 删除空目录(需先删除目录内所有文件)File emptyDir = new File("D:/test/demo");if (emptyDir.delete()) {System.out.println("空目录删除成功");} else {System.out.println("目录非空或不存在");}}
}
四、文件 / 目录的获取功能 API
获取功能用于查询文件 / 目录的路径、基本信息和状态,是日常操作的核心 API,按功能分类整理如下:
1. 获取路径相关信息
方法名 | 功能描述 | 返回值类型 | 示例(假设 file 路径为D:/test/demo/test.txt ) |
---|---|---|---|
String getAbsolutePath() | 获取绝对路径(完整路径) | String | D:\test\demo\test.txt |
String getPath() | 获取构造器中传入的路径(可能是相对路径) | String | 若构造器传"src/test.txt" ,则返回"src/test.txt" |
String getName() | 获取文件 / 目录的名称(最后一级路径) | String | test.txt (文件)、demo (目录) |
String getParent() | 获取父目录的路径(若无父目录,返回 null) | String | D:\test\demo |
File getParentFile() | 获取父目录的 File 对象(便于后续操作父目录) | File | 对应D:/test/demo 的 File 对象 |
2. 获取基本属性信息
方法名 | 功能描述 | 返回值类型 | 说明 |
---|---|---|---|
long length() | 获取文件的大小(单位:字节) | long | 1. 目录的 length () 返回 0;2. 文件不存在时返回 0。 |
long lastModified() | 获取文件 / 目录的最后修改时间(单位:毫秒,自 1970-01-01 00:00:00 起) | long | 可通过new Date(file.lastModified()) 转为日期格式。 |
3. 判断状态信息
方法名 | 功能描述 | 返回值类型 | 说明 |
---|---|---|---|
boolean exists() | 判断文件 / 目录是否存在 | boolean | 所有创建 / 删除操作前,建议先判断 exists ()。 |
boolean isFile() | 判断是否为文件(非目录) | boolean | 仅当 exists () 为 true 时,结果才准确(不存在时返回 false)。 |
boolean isDirectory() | 判断是否为目录(非文件) | boolean | 同 isFile (),需先确保 exists () 为 true。 |
boolean canRead() | 判断是否可读(系统权限) | boolean | |
boolean canWrite() | 判断是否可写(系统权限) | boolean | |
boolean isHidden() | 判断是否为隐藏文件 / 目录(Windows 中带隐藏属性,Linux 中以 “.” 开头) | boolean |
示例代码(获取文件信息)
import java.io.File;
import java.util.Date;public class FileGetDemo {public static void main(String[] args) {File file = new File("D:/test/demo/test.txt");if (file.exists()) { // 先判断文件是否存在System.out.println("绝对路径:" + file.getAbsolutePath());System.out.println("文件名称:" + file.getName());System.out.println("文件大小:" + file.length() + " 字节");System.out.println("最后修改时间:" + new Date(file.lastModified()));System.out.println("是否为文件:" + file.isFile());System.out.println("是否可读:" + file.canRead());} else {System.out.println("文件不存在");}}
}
20、IO流
在 Java IO 流体系中,字符流和字节流是两大核心分支,它们的设计目的和适用场景不同,以下是详细解析:
一、字符流(Character Stream)
定义:字符流是以 “字符” 为基本单位处理数据的流(1 个字符通常对应 2 字节,受编码影响,如 UTF-8 中 1 个字符可能是 1-4 字节)。适用场景:主要用于处理文本文件(如.txt、.java、.xml 等),因为文本文件的本质是字符序列,字符流会自动处理数据的编码与解码(如将字节转换为字符时使用指定编码),避免中文等字符乱码。
核心父类:
- 输入流:
java.io.Reader
(抽象类,所有字符输入流的父类) - 输出流:
java.io.Writer
(抽象类,所有字符输出流的父类)
二、字节流(Byte Stream)
定义:字节流是以 “字节” 为基本单位处理数据的流(1 字节 = 8 位二进制)。适用场景:用于处理二进制文件(如音频、视频、图片、压缩包等),因为这些文件的本质是二进制数据,无需编码转换,直接以字节形式存储和传输。
核心父类:
- 输入流:
java.io.InputStream
(抽象类,所有字节输入流的父类) - 输出流:
java.io.OutputStream
(抽象类,所有字节输出流的父类)
三、字符流向字节流转换的桥梁
字符流和字节流的本质区别是处理单位(字符 vs 字节),两者的转换依赖转换流,其中字符流向字节流转换的桥梁是OutputStreamWriter
。
OutputStreamWriter
继承自Writer
(字符输出流),但其构造方法需要传入一个OutputStream
(字节输出流)和指定编码(如 UTF-8)。- 作用:将字符流输出的字符数据,先通过指定编码转换为字节数据,再写入底层的字节流(实现字符→字节的转换)。
四、字符流的输入流与输出流
1. 字符输出流(Writer 体系)
-
高效缓冲输出流:
BufferedWriter
作用:通过内置缓冲区(默认 8192 字符)减少与磁盘的直接 IO 操作,提高写入效率。需包装一个Writer
子类(如FileWriter
)使用。示例:BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"));
-
标准字符输出流:通常指通过转换流得到的、对应控制台输出的字符流。说明:System.out 是标准字节输出流(
PrintStream
),若需字符流输出到控制台,需用OutputStreamWriter
转换:Writer out = new OutputStreamWriter(System.out);
2. 字符输入流(Reader 体系)
-
高效缓冲输入流:
BufferedReader
作用:通过缓冲区提高字符读取效率,还提供便捷方法(如readLine()
读取一行文本)。需包装一个Reader
子类(如FileReader
)使用。示例:BufferedReader br = new BufferedReader(new FileReader("test.txt"));
-
标准字符输入流:通常指通过转换流得到的、对应控制台输入的字符流。说明:System.in 是标准字节输入流(
InputStream
),若需字符流读取控制台输入,需用InputStreamReader
转换:Reader in = new InputStreamReader(System.in);
五、字节流的输入流与输出流
1. 字节输出流(OutputStream 体系)
-
标准字节输出流:
System.out
(PrintStream
类型,OutputStream
的子类)作用:默认输出到控制台,可直接输出字节数据或通过print()
/println()
输出字符串(内部自动转换为字节)。 -
字节高效缓冲输出流:
BufferedOutputStream
作用:通过内置字节缓冲区(默认 8192 字节)减少磁盘 IO 次数,提高字节写入效率。需包装一个OutputStream
子类(如FileOutputStream
)使用。示例:BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.mp3"));
2. 字节输入流(InputStream 体系)
-
标准字节输入流:
System.in
(InputStream
的实例)作用:默认从控制台读取字节数据(如用户键盘输入)。 -
字节高效缓冲输入流:
BufferedInputStream
作用:通过字节缓冲区提高字节读取效率,需包装一个InputStream
子类(如FileInputStream
)使用。示例:BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.mp3"));
总结
- 字符流处理文本(字符为单位,涉及编码),字节流处理二进制(字节为单位,不涉及编码)。
- 转换流
OutputStreamWriter
是字符流向字节流转换的桥梁。 - 缓冲流(
BufferedXXX
)通过缓冲区提高读写效率,是实际开发中的首选(而非直接使用FileReader
/FileOutputStream
等基础流)。 - 标准流(
System.in
/System.out
)是字节流,需通过转换流才能得到对应的字符流。
21、字符编码
字符编码(Character Encoding)是一套规则,用于将人类可识别的字符(如字母、汉字、符号等)转换为计算机可存储和传输的二进制数据(字节序列),反之则称为 “解码”。不同编码的核心区别在于支持的字符范围和字符与字节的映射规则。以下是常见的字符编码:
1. ASCII(American Standard Code for Information Interchange)
- 起源:1963 年制定,针对英文及基本符号的编码标准。
- 规则:使用1 个字节(7 位有效位) 表示字符,共定义 128 个字符(0-127),包括:
- 控制字符(如换行
\n
、回车\r
,0-31); - 可打印字符(如大小写字母、数字、标点,32-127)。
- 控制字符(如换行
- 局限:仅支持英文,无法表示中文、日文等非英文字符。
- 地位:所有现代编码的基础,几乎所有编码都兼容 ASCII(即 ASCII 字符在其他编码中映射规则一致)。
2. GB 系列(中文编码,国家标准)
为解决中文在计算机中的表示问题,中国制定了一系列编码标准:
-
GB2312(1980 年)
- 支持简体中文,包含 6763 个常用汉字、682 个符号。
- 编码规则:汉字用2 个字节表示(每个字节最高位为 1,避免与 ASCII 冲突),ASCII 字符仍用 1 字节。
- 局限:不支持生僻字、繁体中文。
-
GBK(1995 年,“国标扩展”)
- 兼容 GB2312,扩展了字符范围:包含 21003 个汉字(含繁体)、883 个符号。
- 编码规则:汉字仍以2 个字节表示,覆盖更多字符。
- 应用:Windows 系统默认的中文编码(如简体中文 Windows 的 ANSI 编码实际是 GBK)。
-
GB18030(2000 年,最新国家标准)
- 兼容 GB2312 和 GBK,支持更多字符:包含 7 万 + 汉字、少数民族文字(如藏文、蒙文)、日韩文字等。
- 编码规则:采用可变长度字节(1 字节、2 字节或 4 字节),1 字节兼容 ASCII,2 字节兼容 GBK,4 字节扩展新字符。
3. Unicode(字符集,而非编码)
- 问题背景:各国编码标准不统一(如中文用 GBK,日文用 Shift-JIS),导致跨语言文本处理混乱。
- 定义:Unicode 是一个全球统一的字符集(Character Set),为世界上所有字符(包括文字、符号、表情等)分配唯一的数字编号(称为 “码点”,如
U+4E2D
表示 “中”)。 - 注意:Unicode 仅定义 “字符→码点” 的映射,不规定 “码点→字节” 的转换规则(即不直接对应二进制),需通过具体编码方式(如 UTF-8、UTF-16)实现。
4. UTF 系列(Unicode 的实现编码)
UTF(Unicode Transformation Format)是将 Unicode 码点转换为字节序列的编码方式,解决 Unicode 的存储和传输问题。
-
UTF-8(最常用)
- 编码规则:可变长度字节(1-4 字节),根据字符的码点大小动态调整:
- ASCII 字符(码点 0-127):1 字节(与 ASCII 完全兼容);
- 欧洲、阿拉伯等字符:2 字节;
- 中文、日文等:3 字节;
- 罕见字符(如 emoji):4 字节。
- 优势:
- 兼容 ASCII,节省英文文本的存储空间;
- 无字节序问题(无需区分大端 / 小端),适合网络传输(如网页、邮件)和跨平台存储。
- 应用:互联网主流编码(网页、文件、数据库等几乎都默认 UTF-8)。
- 编码规则:可变长度字节(1-4 字节),根据字符的码点大小动态调整:
-
UTF-16
- 编码规则:2 字节或 4 字节:
- 大部分常用字符(码点 0-65535):2 字节;
- 超出 65535 的字符(如部分 emoji):4 字节(用 “代理对” 表示)。
- 特点:
- 字节序敏感(分 UTF-16BE 大端、UTF-16LE 小端,需用 BOM 标识);
- 中文等常用字符用 2 字节,比 UTF-8 更节省空间(但英文比 UTF-8 多 1 字节)。
- 应用:Java、C# 的
char
类型默认用 UTF-16;Windows 系统内部字符处理、.NET 框架等。
- 编码规则:2 字节或 4 字节:
-
UTF-32
- 编码规则:固定 4 字节表示任意 Unicode 码点(直接存储码点的二进制值)。
- 优势:编码 / 解码简单(无需计算长度);
- 劣势:空间浪费严重(即使 ASCII 字符也占 4 字节),几乎不用于存储或传输,仅少数场景(如字符集工具)使用。
5. 其他常见编码
- ISO-8859 系列:针对欧洲语言的单字节编码,如 ISO-8859-1(拉丁 1)支持西欧语言(英文、法文、德文等),共 256 个字符(0-255),兼容 ASCII 但不支持中文。早期网页常用,现逐渐被 UTF-8 取代。
- Big5(大五码):繁体中文编码,主要用于台湾、香港地区,支持 13053 个繁体汉字,与 GB 系列不兼容(用 Big5 打开 GBK 文件会乱码)。
总结
- ASCII:基础英文编码,1 字节。
- GBK/GB18030:中文编码,适合纯中文场景(如国内 Windows 软件)。
- UTF-8:通用性最强,跨语言、跨平台,推荐优先使用(网页、文件、数据库等)。
- UTF-16:适合内部字符处理(如 Java、.NET),不适合存储 / 网络传输。
22、常见异常
在 Java 中,异常(Exception)是程序运行时出现的非预期情况,常见异常通常与逻辑错误、数据操作不当或资源问题相关。以下是开发中最常遇到的几类异常,包括其含义、产生原因及示例:
1. 空指针异常(NullPointerException
)
含义:当程序试图调用一个 **null
对象 ** 的方法、访问其字段,或把null
当作一个数组 / 对象使用时抛出。本质:null
表示 “没有指向任何对象”,对其进行 “对象级操作”(如调用方法、访问属性)必然失败。
常见场景:
- 调用
null
对象的方法:String s = null; s.length();
(s
为null
,无法调用length()
)。 - 访问
null
对象的字段:User u = null; u.name;
(u
为null
,无法访问name
字段)。 - 数组引用为
null
时访问索引:int[] arr = null; arr[0] = 1;
(arr
未初始化,无索引可言)。 - 自动拆箱
null
:Integer i = null; int num = i;
(i
为null
,无法拆箱为int
)。
避免方式:使用对象前先判断是否为null
(如if (obj != null) { ... }
)。
2. 数组下标越界异常(ArrayIndexOutOfBoundsException
)
含义:访问数组时,使用的索引值超出了数组的有效范围(即索引 < 0 或 ≥ 数组长度)。本质:数组的索引从0
开始,最大索引为 “数组长度 - 1”,超出此范围的索引无效。
常见场景:
- 索引大于等于数组长度:
int[] arr = new int[3]; arr[3] = 10;
(数组长度为 3,有效索引为 0、1、2)。 - 索引为负数:
arr[-1] = 5;
(负数索引无意义)。 - 循环遍历错误:
for (int i = 1; i <= arr.length; i++) { ... }
(i 从 1 开始,最后一次循环 i=arr.length,超出范围)。
避免方式:访问数组前检查索引范围(如if (index >= 0 && index < arr.length) { ... }
),循环时使用i < arr.length
作为条件。
3. 类型转换异常(ClassCastException
)
含义:当试图将一个对象强制转换为不是其实际类型的子类时抛出(即 “向下转型” 失败)。本质:转换的前提是 “对象的实际类型是目标类型的子类”,否则转换无效。
常见场景:
- 错误的向下转型:
Object obj = "hello"; // obj实际类型是String Integer num = (Integer) obj; // 错误:String不能转为Integer,抛出ClassCastException
- 集合未指定泛型时的转型错误:
List list = new ArrayList(); list.add("test"); Integer i = (Integer) list.get(0); // 实际是String,转Integer失败
避免方式:转型前用instanceof
判断对象实际类型(如if (obj instanceof Integer) { ... }
),或使用泛型(如List<String>
)避免类型混乱。
4. 类找不到异常(ClassNotFoundException
)
含义:当 JVM 试图加载某个类(如通过Class.forName()
、类加载器loadClass()
),但在类路径(classpath)中找不到对应的.class
文件时抛出。本质:类的字节码文件未被正确引入(如缺少依赖、路径错误)。
常见场景:
- 动态加载类时路径错误:
Class.forName("com.example.User"); // 若com/example/User.class不存在,抛出异常
- 项目依赖缺失:如使用第三方库时,未将
jar
包放入 classpath。 - 打包错误:编译后的
.class
文件未被正确打包到输出目录(如target/classes
)。
避免方式:检查类全限定名是否正确、依赖是否完整、classpath 配置是否包含类所在路径。
其他常见异常
除上述外,以下异常也频繁出现:
-
算术异常(
ArithmeticException
):因算术操作错误产生,最典型的是 “除以 0”:int a = 10 / 0;
(整数除法不允许除数为 0)。 -
非法参数异常(
IllegalArgumentException
):方法接收到不符合要求的参数时抛出,如Arrays.copyOf(arr, -1);
(复制长度为负数,参数非法)。 -
数字格式化异常(
NumberFormatException
):试图将字符串转为数字但格式不符时抛出,如Integer.parseInt("abc");
("abc" 无法转为整数)。 -
索引越界异常(
IndexOutOfBoundsException
):集合(如List
)的索引超出范围时抛出(类似数组越界),如List<String> list = new ArrayList<>(); list.get(0);
(集合为空,索引 0 无效)。
总结
常见异常多为运行时异常(RuntimeException
子类),编译时不强制处理,但需通过逻辑判断避免(如非空校验、索引范围检查)。理解异常的产生原因,能帮助快速定位 bug—— 例如看到NullPointerException
就检查对象是否初始化,看到ClassCastException
就排查类型转换的合理性。
23、网络编程
网络编程是指通过计算机网络实现不同设备之间数据传输的编程技术,其核心依赖于网络协议(一套规定数据传输格式、时序、错误处理的规则)。以下从网络基层模型、TCP 协议、UDP 协议三个核心维度展开解析:
一、网络基层模型(协议分层模型)
计算机网络的复杂性决定了其需要 “分层设计”—— 将复杂的通信过程拆分为多个独立的 “层”,每层专注于特定功能,通过层间接口协作完成整体通信。最经典的是OSI 七层模型和实际应用中广泛使用的TCP/IP 四层(或五层)模型。
1. OSI 七层模型(理论标准)
OSI(Open Systems Interconnection,开放系统互连)是国际标准化组织(ISO)定义的理想模型,从底层到高层依次为:
层级 | 核心功能 | 典型协议 / 组件 |
---|---|---|
7. 应用层 | 直接为用户应用程序提供服务(如文件传输、网页访问) | HTTP、FTP、SMTP、DNS、Telnet |
6. 表示层 | 处理数据格式转换(如编码、压缩、加密),确保不同系统能理解数据内容 | JPEG、ASCII、UTF-8、SSL/TLS |
5. 会话层 | 建立、管理和终止 “会话”(如维持客户端与服务器的连接状态) | RPC(远程过程调用)、NetBIOS |
4. 传输层 | 负责端到端的可靠数据传输(如分片、重传、流量控制) | TCP、UDP |
3. 网络层 | 负责跨网络的路径选择(路由)和数据转发(将数据从源主机发送到目标主机) | IP、ICMP(ping 基于此)、路由协议 |
2. 数据链路层 | 负责相邻设备间的帧传输(如 MAC 地址识别、差错校验) | Ethernet(以太网)、PPP |
1. 物理层 | 负责将二进制数据转换为物理信号(如电信号、光信号),定义硬件接口(如网线) | 网线、光纤、网卡物理接口 |
2. TCP/IP 四层模型(实际应用)
OSI 模型过于理想化,实际中广泛使用的是 TCP/IP 模型(因核心协议 TCP 和 IP 得名),它将 OSI 七层简化为四层:
层级 | 对应 OSI 层级 | 核心功能 |
---|---|---|
应用层 | 应用层 + 表示层 + 会话层 | 同 OSI 应用层,提供具体应用服务(如 HTTP、DNS) |
传输层 | 传输层 | 同 OSI 传输层,负责端到端数据传输(TCP/UDP) |
网络层 | 网络层 | 同 OSI 网络层,负责跨网络路由(IP 协议) |
网络接口层 | 数据链路层 + 物理层 | 负责物理传输和相邻设备通信(如以太网) |
分层的意义:
- 每层仅关注自身功能,降低整体复杂度(如应用层无需关心数据如何通过网线传输);
- 层间通过标准接口交互,便于不同厂商设备兼容(如任何品牌的网卡都遵循以太网协议)。
二、TCP 协议(Transmission Control Protocol,传输控制协议)
TCP 是传输层的核心协议,专为可靠传输设计,是大多数网络应用(如网页、文件传输)的基础。
1. 核心特点
- 面向连接:通信前必须先建立连接(类似 “打电话”,先拨号接通再说话),通信结束后需断开连接。
- 可靠传输:通过一系列机制保证数据不丢失、不重复、按序到达(核心优势)。
- 面向字节流:数据被视为连续的字节序列(无固定大小限制),发送方可以分多次发送,接收方负责重组。
- 全双工通信:连接建立后,双方可同时发送和接收数据(类似双向车道)。
2. 可靠传输的核心机制
-
三次握手(建立连接):目的是确认双方的发送和接收能力正常,避免 “失效的连接请求” 被误处理。
- 客户端 → 服务器:“我想连接你(SYN=1)”;
- 服务器 → 客户端:“我收到了,我也准备好了(SYN=1, ACK=1)”;
- 客户端 → 服务器:“我收到你的确认了,开始通信(ACK=1)”。
-
四次挥手(断开连接):因 TCP 是全双工,双方需分别确认 “不再发送数据”。
- 客户端 → 服务器:“我说完了(FIN=1)”;
- 服务器 → 客户端:“我收到了(ACK=1)”(此时服务器可能还在发送剩余数据);
- 服务器 → 客户端:“我也说完了(FIN=1)”;
- 客户端 → 服务器:“我收到了,断开(ACK=1)”。
-
其他机制:
- 确认应答(ACK):接收方收到数据后必须回复确认,未收到确认则发送方重传;
- 超时重传:发送方若超时未收到确认,自动重传数据;
- 流量控制:通过 “滑动窗口” 限制发送方速率,避免接收方缓冲区溢出;
- 拥塞控制:根据网络拥堵情况动态调整发送速率(如慢启动、拥塞避免)。
3. 适用场景
需可靠、有序但可容忍稍低效率的场景:
- 网页浏览(HTTP/HTTPS 基于 TCP);
- 文件传输(FTP);
- 邮件发送(SMTP);
- 即时通讯的文字消息(需确保不丢消息)。
三、UDP 协议(User Datagram Protocol,用户数据报协议)
UDP 是传输层的另一种核心协议,与 TCP 相反,它以效率优先,牺牲可靠性。
1. 核心特点
- 无连接:通信前无需建立连接(类似 “发短信”,直接发送,不管对方是否准备好)。
- 不可靠:不保证数据到达、不保证顺序、不重传丢失的数据(发送后即不管)。
- 面向数据报:数据以 “数据报” 为单位发送(每个数据报有固定大小,最大 65507 字节),发送方和接收方需按数据报处理。
- 效率高:无需握手 / 挥手、无确认重传机制,协议开销极小,传输速度快。
2. 核心机制
- 无连接管理:发送方直接封装数据为 “UDP 数据报”(包含源端口、目标端口、数据长度、校验和),交给网络层发送;
- 简单校验:仅通过 “校验和” 检查数据是否损坏(损坏则直接丢弃,不重传);
- 无流量 / 拥塞控制:发送方按自身速率发送,可能导致网络拥堵或接收方处理不及。
3. 适用场景
需实时性高,可容忍少量丢包的场景:
- 视频 / 语音通话(如 Zoom、微信通话,丢几个包不影响整体流畅度);
- 实时游戏(如王者荣耀,延迟比偶尔丢包更影响体验);
- DNS 查询(域名解析需快速响应,一次查询失败可重试);
- 广播 / 多播(向多个设备同时发送数据,无需单独连接)。
总结:TCP vs UDP
维度 | TCP | UDP |
---|---|---|
连接性 | 面向连接(三次握手) | 无连接 |
可靠性 | 可靠(不丢包、有序) | 不可靠(可能丢包、乱序) |
数据单位 | 字节流 | 数据报(固定大小) |
效率 | 低(协议开销大) | 高(协议开销小) |
适用场景 | 可靠传输(网页、文件) | 实时传输(视频、游戏) |
网络编程的核心就是基于 TCP 或 UDP 协议,在应用层实现具体的通信逻辑(如客户端发送请求、服务器处理并响应),而分层模型则是理解协议工作原理的基础。