Java学习笔记01
Java学习笔记01
Lynx FrostJava概述
Java是由Sun Microststems 公司于1995年5月推出的面向对象的程序设计语言,作者 詹姆斯•高斯林。
Java技术体系平台
- Java SE 标准版
- Java EE 企业版 主要学习这个
- Java ME 小型版
Java重要特点
- 语言简单
- 面向对象 oop
- 分布式
- 语言健壮
- 安全
- 体系结构中立
- 可移植
- 跨平台
- 解释型语言 需要解释器
- 高性能
- 动态
Java虚拟机 JVM
用于在不同的设备上运行.class字节码文件,屏蔽系统差异
JDK、JRE
JDK java开发工具包 Java Development Kit 开发者用
JDK=JRE+java开发工具
JRE java运行环境 Java Runtime Environment 普通用户用
JRE=JVM+Java核心类库
注意事项
- 文件名必须与public类名一致
- 一个源文件中最多只能有一个public类
- 编译之后每一个类对应一个.class文件
- Java语言严格区分大小写
- 每个语句以 “;” 结束
- java 命令之后跟类名
- main 方法就是 Java 程序的唯一合法入口
- 原则:一个文件只写一个类
Java基础语法
命名规范
| 标识符类型 | 命名范式 | 示例 | 备注 |
|---|---|---|---|
| 包 (Package) | 全小写 | com.apple.ios |
建议使用反向域名 |
| 类 (Class) | 大驼峰 | StudentInfo |
名词 |
| 方法 (Method) | 小驼峰 | getScore() |
动词 |
| 变量 (Variable) | 小驼峰 | studentAge |
描述性名词 |
| 常量 (Constant) | 大写 + 下划线 | MAX_AGE |
static final 修饰 |
| 抽象类 | 大驼峰 | AbstractService |
通常以 Abstract 开头 |
| 异常类 | 大驼峰 | LoginException |
必须以 Exception 结尾 |
变量
变量是程序的基本组成单位
三要素 :类型+名称+值
变量必须先声明再使用
同一个作用域之内变量不能重名
数据类型
- 基本数据类型
- 数值型
- 整数类型,存放整数:
byte[1]、short[2]、int[4]、long[8] - 浮点 (小数) 类型:
float[4]、double[8]
- 整数类型,存放整数:
- 字符型:
char[2],存放单个字符'a' - 布尔型:
boolean[1],存放true、false
- 数值型
- 引用数据类型
- 类 (class)
- 接口 (interface)
- 数组 ([])
注意事项
- 整型常量默认为 int 型,声明 long 型常量须后加‘l’或‘L’
- 浮点型常量默认为 double 型,声明 float 型常量,须后加‘f’或‘F’
- “小向大”是顺水推舟,自动完成;“大向小”是强人所难,必须强转。
- 浮点数=符号位+指数位+尾数位 尾数部分可能丢失,造成精度损失(小数都是近似值)
- 永远不要用
==来比较两个浮点数。应该判断它们的差值是否小于一个极小的数值(如1e-9) - 当我们对运算结果是小数的进行相等判断是,要小心应该是以两个数的差值的绝对值,在某个精度范围类判断
- char字符常量只能用单引号
''引用,只能存单个字符或者是数字,本质是存储的是一个整数unicode对映的编码,可以进行计算
补充知识浮点数的计算机存储
计算机里的浮点数就像一个精密的二进制科学计数法,其存储结构固定为:
- 符号位 (Sign):1位。
0正1负。 - 指数位 (Exponent):
- 作用:决定数值范围(小数点移动多少位)。
- 黑科技:使用偏移码(Bias),通过给真实指数加一个固定值(如127),强行把负指数变成正整数,方便硬件直接比较大小。
- 尾数位 (Mantissa):
- 作用:决定数值精度(长什么样)。
- 黑科技:隐藏整数位的
1。既然二进制规格化后第一位必然是1,干脆省掉不存,多赚1位精度。
基本数据类型转换
路径 A: byte → short → int → long → float → double
路径 B: char → int → long → float → double
为什么long是8个字节,float是4个字节,但是long会自动转为float?
它们底层的二进制存储方式完全不同。数值范围的大小并不取决于它占用了多少字节,而取决于它的结构设计
long(64 位定点数):1 位符号位 + 63 位数值位,纯精确整数。
float(32 位 IEEE 754 浮点数):1 位符号位 + 8 位指数位 + 23 位尾数位。
long(8 字节):最大值约 $9.22 \times 10^{18}$
float(4 字节):最大值约 $3.4 \times 10^{38}$(范围远大于 long)
引入指数位后,表达范围被几何级放大。
注意看:
byte 可以自动转 short。short 不能直接转 char。byte 也 不能直接转 char。但是它们都可以统一自动转为 int再计算。原因是short和byte有符号。
提升原则:表达式结果的类型自动提升为操作数中最大的类型,最小是int类型
注意事项:
byte short char 类型的变量进行计算都会转换成 int 类型
byte short char 类型的常量(字面常量)进行计算会进行优化机制
常量计算不溢出时,类型由左侧接收变量决定(直通不升格);一旦溢出,则被视为 int 类型(导致报错)。
只要公式中出现了一个变量,整个公式的“常量优化”特权就会立即消失,全面进入变量运算规则。
强制类型转化
将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符 ( ),但可能造成精度降低或溢出,格外要注意。
Java基本数据类型中,数值类型和boolean不能互相强制转换
基本数据类型和String类型的转换
基本数据类型转String类型:将基本类型的值+“”
String类型转基本数据类型:利用包装类 xxx.parsexxx(“”)
包装类的作用:让基本数据类型能够像对象一样工作,具备属性和方法
| 基本数据类型 | 对应的包装类 |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
+号的作用
- 当左右两边都是数值类型时,则做加法运算
- 当左右两边有一方为字符串,则做拼接运算
- 运算顺序,从左到右
Java 转义字符
| 转义字符 | 含义 |
|---|---|
\' |
单引号 ' |
\" |
双引号 " |
\\ |
反斜杠 \ |
\n |
换行 |
\r |
回车 |
\t |
Tab 制表符(缩进) |
\b |
退格 |
\f |
换页(很少用) |
特殊转义字符\r
功能:将光标移动到当前行的开头
不同的操作系统对“换行”的定义曾各不相同:
- Windows: 使用
\r\n(回车 + 换行)代表一行结束。 - Unix / Linux / macOS: 统一使用
\n(换行)代表一行结束。
\r 有一个非常酷的用途:制作简单的命令行进度条。
for (int i = 0; i <= 100; i++) { |
注释
- 单行注释 //
- 多行注释 / /
- 文档注释 /* /
被注释的文字,不会被JVM解释执行
代码规范
- 类、方法的注释,要以Javadoc的方式来写
- 非Java Doc 的注释,往往是给代码维护者看的
- 使用Tab进行右移,shift+tab 进行左移
- 运算符和=两边习惯性各加一个空格
- 源文件使用utf-8编码
- 行宽度不要超过80个字符
- 代码编写推荐行尾风格
修饰符
public:公开的。无论是哪个类、哪个包、还是外部项目,都可以直接访问。protected:受保护的。同一个包内的类可以访问;不同包中的子类也可以访问。- 默认(无修饰符):包级私有(package-private)。只有同一个包内的类可以访问,跨包了就不能访问。
private:私有的。只有在定义它的类内部才能访问,体现了面向对象的封装性。通常属性会设为private,然后通过get/set方法来间接访问。
运算符
算术运算符
示例
public class Test { |
结果
a + b = 30 |
注意事项
System.out.println(10 / 4); //从数学来看是 2.5, java 中 2 |
int total = 4; |
原因: score / total 的计算先发生,因为两个都是 int,所以先算出了 2。
赋值: 算出 2 之后,才把这个整数赋值给 double result。这时 2 变成了 2.0。
在 % 的本质 看一个公式a % b = a - a / b * b
由公式可以知道:在 Java 的取模运算中,结果的符号只取决于左操作数(被除数 $a$)的符号,而与右操作数(除数 $b$)无关。
面试题
int i = 1; |
答案是 1
过程 temp = i i=i+1 i=temp
int i = 1; |
答案是 2
过程 i=i+1 temp=i i=temp
补充知识点
java和python在处理/的时候采用不同的处理方法
Java:向零取整 (Truncate)
Java 的整数除法采用的是 截断除法。简单来说,就是直接把小数点后面的部分“扔掉”。
- 计算
-7 / 3: 结果是-2.333...,截断后变成-2。 - 推导
%: $-7 - (3 \times -2) = -7 + 6 = -1$。
Python:向下取整 (Floor)
Python 的整数除法(//)采用的是 地板除。它向数轴的左侧(负无穷方向)取整。
- 计算
-7 // 3: 结果是-2.333...,向左取整变成-3。 - 推导
%: $-7 - (3 \times -3) = -7 + 9 = 2$。
关系运算符
示例
public class Test { |
结果
a == b = false |
关系运算符的结果都是boolean类型,也是就是要么true要么false
instanceof 判断对象是不是某个类 / 子类 / 接口的实例,返回布尔值 true / false 判断运行类型
对象 instanceof 类型 |
Object obj = new String("hello"); |
位运算符
示例
public class Test { |
结果
a & b = 12 |
逻辑运算符
1) a&b : & 叫逻辑与:规则:当 a 和 b 同时为 true ,则结果为 true, 否则为 false
2) a&&b : && 叫短路与:规则:当 a 和 b 同时为 true ,则结果为 true,否则为 false
3) a|b : | 叫逻辑或,规则:当 a 和 b ,有一个为 true ,则结果为 true,否则为 false
4) a||b : || 叫短路或,规则:当 a 和 b ,有一个为 true ,则结果为 true,否则为 false
5) !a : 叫取反,或者非运算。当 a 为 true, 则结果为 false, 当 a 为 false 是,结果为 true
6) a^b: 叫逻辑异或,当 a 和 b 不同时,则结果为 true, 否则为 false
对于&&短路与而言,如果第一个条件为 false ,后面的条件不再判断,效率高
对于&逻辑与而言,如果第一个条件为 false ,后面的条件仍然会判断,效率低
||短路或:如果第一个条件为 true,则第二个条件不会判断,最终结果为 true,效率高
| 逻辑或:不管第一个条件是否为 true,第二个条件都要判断,效率低
赋值运算符
示例
public class Test { |
结果
c = a + b = 30 |
赋值运算符特点
1) 运算顺序从右往左
2) 赋值运算符的左边 只能是变量,右边 可以是变量、表达式、常量值
3) 复合赋值运算符等价于下面的效果比如:a+=3;等价于 a=a+3;
4) 复合赋值运算符会进行类型转换。
byte b = 3; |
三元运算符
条件表达式 ? 表达式 1: 表达式 2;
运算规则:
- 如果条件表达式为 true,运算后的结果是表达式 1;
- 如果条件表达式为 false,运算后的结果是表达式 2;
注意事项
1) 表达式 1 和表达式 2 要为可以赋给接收变量的类型(或可以自动转换)
2) 三元运算符可以转成 if—else 语句
运算优先级
| 优先级 | 运算符 | 类别 | 结合性 | ||
|---|---|---|---|---|---|
| 1(最高) | expr++ expr-- |
后置自增、后置自减 | 从左→右 | ||
| 2 | ++expr --expr +expr(正) -expr(负) ~ ! (类型) |
前置单目(自增自减、正负、取反、强制类型转换) | 从右→左 | ||
| 3 | * / % |
乘、除、取模 | 从左→右 | ||
| 4 | + - |
加、减(含字符串拼接) | 从左→右 | ||
| 5 | << >> >>> |
移位运算符 | 从左→右 | ||
| 6 | < <= > >= instanceof |
关系比较、类型判断 | 从左→右 | ||
| 7 | == != |
相等、不相等 | 从左→右 | ||
| 8 | & |
按位与 / 非短路逻辑与 | 从左→右 | ||
| 9 | ^ |
按位异或 | 从左→右 | ||
| 10 | ` | ` | 按位或 / 非短路逻辑或 | 从左→右 | |
| 11 | && |
短路逻辑与 | 从左→右 | ||
| 12 | ` | ` | 短路逻辑或 | 从左→右 | |
| 13 | ? : |
三元条件运算符 | 从右→左 | ||
| 14(最低) | = += -= *= /= %= &= ^= ` |
=`<<= >>= >>>= |
全部赋值复合赋值 | 从右→左 |
补充知识点,原码反码补码
原码 (Sign-Magnitude):最高位为符号位(0正1负),其余位是数值。
- 直观但难算,0有两套写法。
反码 (Ones’ Complement):正数不变;负数符号位不变,数值位按位取反。
- 进化的中间产物,解决了符号位参与运算的问题。
补码 (Two’s Complement):正数不变;负数在反码基础上 +1。
- 计算机真正采用的格式。
所有运算都在补码(Two’s Complement)基础上进行
程序控制结构
顺序控制
程序从上到下逐行执行,中间没有任何判断和跳转
分支控制
单分支 if
双分支 if-else
多分支 if-else if- else
switch分支
表达式数据类型应和case后的变量类型一致,或者可以自动转成可以比较的类型
switch(表达式)中的表达式的返回值必须是:byte short int char enum String
case子句中的值必须是常量,而不能是变量
default子句是可选的,当没有匹配的case时,执行default
break语句用来在执行完成一个case分支后使程序跳switch语句块,如果没有break,程序会顺序执行,直到遇到break
循环控制
for循环
for循环四个要素 循环变量初始化 循环条件 循环操作 循环变量迭代
注意 循环里面创建的变量都是临时的,用完就销毁
循环初始值可以有多条初始化语句,但要求类型一样,并且中间用逗号隔开,循环变量迭代也可以有多条变量迭代语句,中间用逗号隔开。
while循环
do while循环
先执行再判断,最少会执行一次
最后 有一个;
多重循环控制
- break 终止循环
- continue 跳出本次循环
跳转控制语句 - return
跳出所在的方法
数组、排序、查找
数组定义方式
动态分配
数据类型 数组名[] = new 数组类型[大小];
先声明再分配
数据类型 数组名[];
数组名 = new 数组类型[大小];数组名.length 表示数组的大小/长度
数据类型 数组名[]; 也可以 数据类型[] 数组名;
注意事项
数组是多个相同类型数据的组合,实现对这些数据的统一管理
数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用。
数组创建后,如果没有赋值,有默认值
| 类型 | 默认值 |
| ———- | ——— |
| byte | 0 |
| short | 0 |
| int | 0 |
| long | 0 |
| float | 0.0 |
| double | 0.0 |
| char | \u0000 |
| boolean | false |
| String | null |使用数组的步骤
声明数组并开辟空间
给数组各个元素赋值
- 使用数组
数组的下标是从 0 开始的。
数组下标必须在指定范围内使用,否则报:下标越界异常,
数组属引用类型,数组型数据是对象(object)
int a = 1; |
Arrays类
一.概述
java.util.Arrays:数组工具类
- 包:
java.util,使用需要导包 - 所有方法都是 static 静态方法,直接
Arrays.方法名()调用 - 构造方法私有,不能 new Arrays 对象
- 作用:提供数组打印、排序、查找、复制、填充、比较等工具方法
import java.util.Arrays; |
二、常用核心方法
- toString () 打印数组
普通数组直接打印输出地址,Arrays.toString 打印数组内容
int[] arr = {1,3,2}; |
注意:只支持一维数组
多维数组使用:
deepToString()
int[][] arr2 = {{1,2},{3,4}}; |
- sort () 数组排序
① 基本类型数组:升序排序(快速排序)
int[] arr = {5,2,9,1}; |
② 对象数组 / 自定义排序(引用类型)
Arrays.sort(数组, Comparator) 支持自定义顺序(只能用于包装类型数组,不能用 int [],要用 Integer [])
Integer[] arr = {5,2,9,1}; |
重点:基本类型 sort 不支持直接传入比较器!
拓展:可以指定区间排序
Arrays.sort(arr,from,to)左闭右开
- binarySearch () 二分查找
前提条件:数组必须先升序排序!否则结果不可预测
- 找到:返回元素索引
- 找不到:返回
-(插入点)-1
int[] arr = {1,2,5,9}; |
- copyOf () /copyOfRange () 数组复制
copyOf (数组,新长度) |
从头复制,可扩容 / 缩容
int[] old = {1,2,3}; |
copyOfRange (数组,起始索引,结束索引) |
区间复制,左闭右开 [start,end)
int[] arr = {10,20,30,40}; |
区别:数组自身 clone () 和 Arrays.copyOf 效果类似;copyOf 底层调用 System.arraycopy。
- fill () 数组填充
给数组全部 / 指定区间统一赋值
int[] arr = new int[5]; |
- equals () 判断两个数组内容是否相同
int[] a = {1,2,3}; |
误区:
a==b 比较数组地址;
Arrays.equals() 比较数组中每个元素的值。
多维数组比较使用:deepEquals()
三、一维数组 vs 多维数组配套方法汇总
| 功能 | 一维数组 | 多维数组 |
|---|---|---|
| 打印内容 | toString() | deepToString() |
| 比较内容 | equals() | deepEquals() |
四、高频易错考点
binarySearch二分查找必须先排序,无序数组结果错误- Arrays.sort 基本类型只能升序;想要降序必须使用包装数组 + Comparator
- copyOf 产生新数组,原数组不会改变
Arrays.toString不能打印多维数组,只会输出地址,要用 deepToString- 数组是引用类型,
equals()默认比较地址,一定要用 Arrays.equals 比较元素
五、拓展:数组转集合(asList)
Integer[] nums = {1,2,3}; |
重要坑:
- asList 接收引用类型数组;不能直接传基本类型数组
int[] - 返回的 List 是 Arrays 内部静态类,固定大小!不能 add/remove,会抛出异常
- 集合和原数组互相引用,修改数组元素,集合同步变化
常用类
包装类
1) 针对八种基本数据类型相应的引用类型—包装类
2) 有了类的特点,就可以调用类中的方法
java 有 8 种基本数据类型,不是对象;
包装类:把基本类型包装成【对象】,位于java.lang包
| 基本类型 | 对应包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
记忆:除了
int→Integer、char→Character,其余基本类型首字母大写就是包装类。
装箱 & 拆箱
- 手动装箱 / 拆箱(JDK5 之前写法)
装箱:基本类型 → 包装对象
int a = 10; |
拆箱:包装对象 → 基本类型
Integer num = new Integer(10); |
- 自动装箱、自动拆箱(JDK5 + 新特性,编译器自动转换)
//自动装箱 |
底层本质:自动装箱底层调用valueOf();自动拆箱调用xxxValue()。
Integer 缓存机制
Integer.valueOf(int i) 存在缓存数组 cache[]
- 默认缓存范围:-128 ~ 127
- 范围内数字:直接复用缓存对象,
==结果 true - 超出范围:新建
Integer对象,==结果 false
Integer a = 100; |
重要提醒:
==比较:两边如果是包装对象 → 比较地址
一边包装、一边基本类型 → 触发自动拆箱,比较数值
开发比较数值一律使用
.equals(),不要直接==
拓展缓存范围
只有 Byte、Short、Integer、Long 缓存区间固定[-128,127]
Character缓存 0~127
Float、Double没有缓存!
字符串 和 基本类型互相转换
- 基本类型 → String
方式 1:变量 + ""
int num = 99; |
方式 2:String.valueOf()(推荐)
String str = String.valueOf(num); |
- String → 基本类型
使用包装类的静态方法 parseXxx()
String s = "123"; |
注意:字符串格式不对,抛出 NumberFormatException 数字格式异常。
包装类与基本类型区别汇总
- 默认值不同
- 基本类型:有默认值(int=0,boolean=false)
- 包装类:默认值
null
坑:包装类变量为 null 时,执行自动拆箱 → NullPointerException 空指针异常
Integer i = null; |
- 存储位置
- 基本类型:局部变量存在栈
- 包装类对象:对象本体在堆
- 使用场景
- 集合
List/Map不能存放基本类型,只能存包装类
List<Integer> list = new ArrayList<>(); //正确 |
String 类
一、String 基本介绍
java.lang.String,代表字符串。- 字符串本质:字符序列,底层:
- JDK8:
private final char[] value - JDK9+:
private final byte[] value(优化存储)
- JDK8:
- 重点:String 对象不可变(immutable)
value数组被final修饰,数组引用不能指向新数组- 一旦字符串创建,内容无法修改
误区:
String s = "abc"; |
不是修改原来对象!只是让s引用指向新字符串对象,旧对象等待 GC 回收。
二、字符串创建两种方式
方式 1:直接赋值(字面量)
String str1 = "Java"; |
先去字符串常量池(堆内存)查找 “Java”
- 找到:直接复用常量池中的对象
- 没找到:在常量池中创建,引用指向常量池对象
方式 2:new String ()
String str2 = new String("Java"); |
- 先检查常量池有没有
"Java",没有则放入常量池; - 在堆中创建新 String 对象,引用指向堆对象。
经典面试对比
String s1 = "abc"; |
三、字符串拼接规则
规则 1:编译期常量优化
两个字面量直接相加,编译器自动合并
String s1 = "a"+"b"+"c"; |
规则 2:存在变量参与拼接 → 在堆创建新对象
String a = "a"; |
intern () 方法
str.intern():主动把字符串放入常量池
如果常量池已经存在相同内容,返回常量池引用;不存在则将对象放入常量池并返回池内引用。
四、== 和 equals ()
==- 基本类型:比较数值
- String 引用:比较对象地址
equals()String 重写了 Object 的 equals,比较字符串内容
开发规范:判断字符串内容相等一律使用
equals()
String s1 = new String("hello"); |
五、常用 API 方法
String str = "HelloJava"; |
六、不可变性带来的优缺点
优点:
- 字符串常量池实现复用,节省内存
- 哈希值只需要计算一次,适合作为 HashMap 的 key
- 线程安全,多线程并发访问无问题
缺点:
频繁字符串拼接,不断产生新对象,产生大量垃圾。
大量拼接解决方案:StringBuilder / StringBuffer
Stringbuffer类
一、基础概念
java.lang.StringBuffer,可变字符序列
底层:char[](JDK9 之后byte[]),数组没有被final修饰,可以直接扩容修改内容,不会频繁创建新对象。
对比:
String:不可变,每次拼接产生新对象,大量拼接效率极低StringBuffer:可变容器,原地修改字符数组
二、构造方法
// 空参,默认容量 16 |
默认容量 16:预留空间,减少频繁扩容。
三、核心常用方法(全部支持链式调用)
StringBuffer sb = new StringBuffer(); |
四、扩容机制(重点)
- 当
append后字符超出容量,触发扩容 - 扩容公式:新容量 = 原有容量 * 2 + 2
- 如果扩容后依然不够,则直接使用所需最小容量
- 底层:新建更大数组,复制旧数组内容
示例:初始容量 16 → 扩容后 16*2+2=34
五、StringBuffer 和 String 的互相转换
// String → StringBuffer |
六、高频易错知识点
- append 返回的是自身 this 对象,所以支持链式调用
sb.append("a").append("b").append("c"); |
toString()会新建 String 对象,不会复用底层数组- 不要频繁 new StringBuffer,预估长度尽量指定初始容量,避免多次扩容
- 线程不安全场景误用 StringBuffer 不能带来收益,反而降低速度;单线程优先
StringBuilder
七、经典误区
错误理解:
“StringBuffer 比 String 快”
准确说法:
少量拼接差距不大;大量循环拼接时,String 不会产生大量临时对象,性能差距巨大。
StringBuilder 类
一、基础介绍
java.lang.StringBuilder |
可变字符序列,用于字符串拼接,JDK1.5 推出
底层:字符数组(JDK8 char [] / JDK9 byte []),数组无 final 修饰,可以直接修改,不会频繁创建新字符串对象。
和 StringBuffer API 几乎完全一致,唯一区别:没有 synchronized 同步锁。
二、构造方法
// 1.无参构造:初始容量 16 |
三、核心常用方法(支持链式调用)
StringBuilder sb = new StringBuilder(); |
原理:所有修改操作直接操作底层数组,不产生新对象。
四、扩容机制
触发时机:追加内容后,字符总数超过当前容量
默认扩容公式:
新容量 = 原有容量 * 2 + 2
如果扩容后的容量依旧不够存放,直接采用所需最小容量。
示例:
初始容量 16 → 扩容:16×2+2 = 34
优化建议:如果你提前预估字符串长度,创建时指定容量,避免多次扩容、数组复制,提升性能。
五、String / StringBuilder / StringBuffer 三者终极对比
| 类 | 可变性 | 线程安全 | 锁 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| String | 不可变 | 线程安全 | 无 | 低 | 少量拼接、字符串常量 |
| StringBuffer | 可变 | 线程安全 | 方法加synchronized |
较低 | 多线程并发拼接 |
| StringBuilder | 可变 | 线程不安全 | 无锁 | 最高 | 单线程拼接(开发首选) |
开发准则:
绝大多数业务代码(单线程) → 优先 StringBuilder
六、相互转换
// String → StringBuilder |
七、高频考点 & 易错点
线程不安全典型场景
多个线程同时操作同一个 StringBuilder 对象,会出现数据错乱、数组越界异常。
解决方案:多线程改用 StringBuffer,或者每个线程新建独立 StringBuilder。
- 循环拼接大坑
// 性能极差!每次循环产生大量中间String对象 |
- 编译器优化误区
String s = "a"+"b"+"c"; |
Math 类
一、基础介绍
java.lang.Math |
- 提供大量数学计算静态方法,全部是
static,不需要 new 对象,直接类名。方法调用 - 构造方法被
private修饰,无法实例化 - 包含两个常用静态常量
Math.PI //圆周率 π ≈ 3.1415926535... |
调用方式示例:
double num = Math.abs(-10); |
二、常用方法分类
- 绝对值 abs ()
Math.abs(-5); //int 绝对值 → 5 |
- 取最值 max /min
Math.max(10,20); //返回较大值 20 |
- 四舍五入、向上取整、向下取整
//1.四舍五入 → long |
极易踩坑:负数的 ceil、floor 不要凭直觉判断!
- 幂运算、开平方
Math.pow(2,3); //2^3 = 8.0 参数、返回值都是double |
- 随机数 random ()
Math.random(); |
业务常用公式:获取 [a,b] 的随机整数
// 范围 [min,max] |
拓展:JDK 推荐也可以使用
java.util.Random类,功能更强。
- 三角函数(参数是弧度,不是角度)
Math.sin(弧度); |
三、Math vs StrictMath(了解)
Math:部分方法平台相关,底层可以使用 CPU 优化,计算结果微小误差
StrictMath:严格遵循浮点运算标准,所有平台计算结果完全一致;速度略慢。
日常开发几乎只用 Math。
四、高频易错点
round()返回 long;ceil()/floor()返回 double
double d = Math.ceil(2.2); //d=3.0 是小数类型,不是int |
- Math.random () 左闭右开
[0.0,1.0) - pow 返回 double,即使整数次方,不能直接当 int 使用,需要强转
- 三角函数传入的是弧度,直接传入角度结果错误
Arrays 类
一、概述
java.util.Arrays:数组工具类
- 包:
java.util,使用需要导包 - 所有方法都是 static 静态方法,直接
Arrays.方法名()调用 - 构造方法私有,不能 new Arrays 对象
- 作用:提供数组打印、排序、查找、复制、填充、比较等工具方法
import java.util.Arrays; |
二、常用核心方法
- toString () 打印数组
普通数组直接打印输出地址,Arrays.toString 打印数组内容
int[] arr = {1,3,2}; |
注意:只支持一维数组
多维数组使用:
deepToString()
int[][] arr2 = {{1,2},{3,4}}; |
- sort () 数组排序
① 基本类型数组:升序排序(快速排序)
int[] arr = {5,2,9,1}; |
② 对象数组 / 自定义排序(引用类型)
Arrays.sort(数组, Comparator) 支持自定义顺序(只能用于包装类型数组,不能用 int [],要用 Integer [])
Integer[] arr = {5,2,9,1}; |
重点:基本类型 sort 不支持直接传入比较器!
拓展:可以指定区间排序
Arrays.sort(arr,from,to)左闭右开
- binarySearch () 二分查找
前提条件:数组必须先升序排序!否则结果不可预测
- 找到:返回元素索引
- 找不到:返回
-(插入点)-1
int[] arr = {1,2,5,9}; |
- copyOf () /copyOfRange () 数组复制
copyOf (数组,新长度) |
从头复制,可扩容 / 缩容
int[] old = {1,2,3}; |
copyOfRange (数组,起始索引,结束索引)
区间复制,左闭右开 [start,end)
int[] arr = {10,20,30,40}; |
区别:
数组自身 clone () 和 Arrays.copyOf 效果类似;copyOf 底层调用 System.arraycopy。
- fill () 数组填充
给数组全部 / 指定区间统一赋值
int[] arr = new int[5]; |
- equals () 判断两个数组内容是否相同
int[] a = {1,2,3}; |
误区:
a==b 比较数组地址;
Arrays.equals() 比较数组中每个元素的值。
多维数组比较使用:deepEquals()
三、一维数组 vs 多维数组配套方法汇总
| 功能 | 一维数组 | 多维数组 |
|---|---|---|
| 打印内容 | toString() | deepToString() |
| 比较内容 | equals() | deepEquals() |
四、高频易错考点
binarySearch二分查找必须先排序,无序数组结果错误- Arrays.sort 基本类型只能升序;想要降序必须使用包装数组 + Comparator
- copyOf 产生新数组,原数组不会改变
Arrays.toString不能打印多维数组,只会输出地址,要用 deepToString- 数组是引用类型,
equals()默认比较地址,一定要用 Arrays.equals 比较元素
五、拓展:数组转集合(asList)大坑
Integer[] nums = {1,2,3}; |
重要坑:
- asList 接收引用类型数组;不能直接传基本类型数组
int[] - 返回的 List 是 Arrays 内部静态类,固定大小!不能 add/remove,会抛出异常
- 集合和原数组互相引用,修改数组元素,集合同步变化
System 类
一、概述
java.lang.System |
- 全部成员都是静态,直接
System.xxx调用,无需实例化 - 构造方法
private,禁止new System() - 代表程序运行平台系统资源,提供标准输入输出、时间、数组复制、退出程序、环境变量等操作
二、三大静态常量(流对象)
// 标准输入流:控制台键盘输入 |
区分 out 和 err:
- out:普通信息输出,缓冲区缓存,顺序不一定立刻打印
- err:错误信息,立即输出,控制台一般红色
三、核心常用方法
- arraycopy () 数组复制,底层非常重要
public static void arraycopy( |
示例:
int[] a = {10,20,30,40,50}; |
特点:
native 本地方法,由 C 实现,速度极快
Arrays.copyOf/copyOfRange 底层本质调用
System.arraycopy注意:自己复制自己、索引越界会抛出
ArrayIndexOutOfBoundsException
- 获取时间
// 当前时间毫秒数:从【1970-01-01 00:00:00 UTC】到现在的毫秒 |
使用场景:计算程序运行耗时
long start = System.currentTimeMillis(); |
区别:
currentTimeMillis:基于系统时钟,可以手动修改系统时间,数值会跳变
nanoTime:基于虚拟机计时器,适合计算时间间隔,不适合获取日期
- exit (状态码) 终止 JVM
System.exit(0); //正常退出 |
重点考点:
执行 System.exit() 直接关闭虚拟机,finally 代码块不会执行!
- gc () 建议垃圾回收
System.gc(); |
作用:向 JVM建议执行垃圾回收;
只是建议,不保证立刻执行 GC,什么时候回收由虚拟机决定。
- 获取环境变量、系统属性
// 获取单个系统属性 |
常用属性:
user.name 当前用户名
java.version Java 版本
四、输入输出重定向(了解)
//把输出写入文件,不再打印控制台 |
五、易错知识点
System.arraycopy是 native 方法,效率高于手动 for 循环复制数组System.exit(0)停止 JVM,会跳过 finally 执行System.gc()只是通知虚拟机,不能强制垃圾回收currentTimeMillis()毫秒时间戳;nanoTime()仅用于计算间隔System.out和System.err是两个不同流,打印顺序无法保证
六、拓展对比
Arrays.copyOf → 底层调用 System.arraycopy
System.arraycopy 是底层原生方法,开发中很少直接手写,框架大量使用。
BigInteger 和 BigDecimal类
一、背景引入
- 基本数据类型局限
long、int有取值上限;float/double浮点运算存在精度丢失(二进制存储小数缺陷)。
System.out.println(0.1 + 0.2); |
- 解决方案
- BigInteger:超大整数(无上限)
- BigDecimal:高精度小数,金融、金额计算必须使用包:
java.math
二、BigInteger(超大整数)
- 创建对象
不能直接使用 + - * / 运算符!只能调用方法
//方式1:字符串构造【推荐】 |
- 常用运算方法
BigInteger a = new BigInteger("20"); |
重点:所有运算返回新 BigInteger 对象,原对象不可变!
- 带余数除法(divideAndRemainder)
BigInteger[] res = a.divideAndRemainder(b); |
三、BigDecimal(高精度小数 )
- 对象创建
// 不推荐!double构造依然存在精度损失 |
- 四则运算
同样不可变,运算返回新对象
BigDecimal a = new BigDecimal("0.1"); |
- 除法 & 保留小数、舍入模式(核心)
除法必须指定保留位数 + 舍入模式
BigDecimal a = new BigDecimal("10"); |
常用舍入模式:
ROUND_HALF_UP:四舍五入(日常最常用)ROUND_UP:向上取整ROUND_DOWN:直接截断,舍弃后面小数ROUND_HALF_EVEN:银行家舍入(JDK 默认)
- 比较大小
禁止使用 ==(比较地址)
尽量不要用 equals ()!equals 会比较小数位数
BigDecimal d1 = new BigDecimal("2.0"); |
正确方式:compareTo()
// 返回值: |
- 小数点操作
bd.setScale(2,BigDecimal.ROUND_HALF_UP); //保留2位小数 |
四、BigInteger vs BigDecimal 对比
| 类 | 适用场景 | 存储内容 |
|---|---|---|
| BigInteger | 超大整数运算 | 整数,无小数 |
| BigDecimal | 金融金额、高精度小数 | 支持小数,解决浮点精度丢失 |
共性:
- 不可变类,每次计算产生新对象
- 不支持运算符重载,必须调用方法
- 在
java.math包
五、高频易错知识点
double 直接传入 BigDecimal 构造器会精度丢失,优先字符串构造
- BigDecimal 除法如果无法整除,必须指定精度和舍入模式,否则抛异常
- equals 会比较小数位数;数值比较用
compareTo - 对象不可变,运算结果需要接收返回值
//错误写法 |
- double/float 不要用于金额计算,强制使用 BigDecimal
六、开发规范(金融项目)
- 数据库金额:decimal 类型
- Java 代码:使用 BigDecimal 存储金额
- 运算统一指定保留小数位数与四舍五入
日期时间类
分为两大体系:
旧版(JDK1.0 ~ JDK7):
Date、SimpleDateFormat、Calendar缺点:线程不安全、API 设计差、可变性
新版(JDK8+ 推荐)java.time 包:
LocalDate、LocalTime、LocalDateTime、DateTimeFormatter、Instant、Duration、Period优点:不可变、线程安全、设计优雅,项目优先使用
一、旧 API(了解,尽量不推荐新项目使用)
- java.util.Date
代表瞬时时间点(毫秒时间戳)
// 当前系统时间 |
缺点:
Date对象可变;setTime 修改原有对象无法直接获取年、月、日;月份从 0 开始(0=1 月,大坑)
SimpleDateFormat 日期格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
致命坑:SimpleDateFormat 线程不安全!多线程共用会异常。
常用格式符号:
yyyy4 位年份MM月份 (01~12)dd日期HH24 小时制;hh12 小时制mm分钟;ss秒;SSS毫秒
- Calendar 日历类(JDK1.1 替代 Date)
抽象类,常用实现 GregorianCalendar
Calendar cal = Calendar.getInstance(); |
缺点:依旧可变,API 别扭。
二、JDK8 全新时间包 java.time
所有对象不可变,修改操作返回新对象;线程安全!
- 核心类区分
| 类 | 作用 |
|---|---|
Instant |
时间戳(UTC 标准时间,毫秒 / 纳秒),对应旧 Date |
LocalDate |
只有年月日 2026-07-28(无时分秒、无时区) |
LocalTime |
只有时分秒 14:30:20 |
LocalDateTime |
年月日 + 时分秒,无时区 |
ZonedDateTime |
带时区的完整时间(需要时区运算使用) |
DateTimeFormatter |
格式化类(替代 SimpleDateFormat,线程安全) |
Duration |
时间间隔:时分秒(秒、毫秒级别,两个时间点) |
Period |
日期间隔:年、月、日(两个 LocalDate) |
- 创建对象
// 获取当前时间 |
- 常用运算(不可变!操作返回新对象)
LocalDateTime time = LocalDateTime.now(); |
- 格式化与解析 DateTimeFormatter 线程安全
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); |
- 时间间隔计算 Duration / Period
// 时分秒间隔(LocalDateTime / Instant) |
- 新旧 API 互相转换(项目兼容必备)
// LocalDateTime ↔ Date |
- 时区简单说明
LocalDateTime不带时区,跨时区业务(海外系统)必须使用 ZonedDateTime。
服务器一般统一使用 UTC 时间存储。
三、新旧 API 核心对比
| 特性 | Date/SimpleDateFormat | java.time(JDK8) |
|---|---|---|
| 可变性 | 可变 | 不可变 |
| 线程安全 | SimpleDateFormat 不安全 | 全部安全 |
| 月份 | 0 开始 | 1 开始,符合直觉 |
| API | 难用,功能弱 | 丰富清晰 |
四、高频易错知识点
- 旧 Date、Calendar 月份从 0 开始;LocalDateTime 月份 1~12
- SimpleDateFormat 禁止多线程共享;新项目一律用 DateTimeFormatter
- LocalDateTime 无时区;不要直接用于跨时区业务
- Duration 处理时分秒;Period 只处理年月日,不能混用
- 所有 JDK8 时间对象修改后返回新实例,原对象不变


