java.lang包
java.lang
包是 Java 语言的基石,封装了最核心的类和功能,涵盖了对象模型、字符串处理、数值运算、异常处理、多线程等基础能力。
Object类
Object
类是 Java 中所有类的根类(超类),任何类都直接或间接继承自 Object
类。即使在类定义时没有显式声明父类,编译器也会默认让它继承 Object
类。Object
类定义了所有 Java 对象都具备的基本行为,其方法在 Java 编程中具有重要意义。
Object 类的核心方法
Object
类包含 11 个方法,其中以下几个是最常用且需要重点理解的:
equals(Object obj)
- 作用:判断当前对象与参数对象是否 "相等"。
- 默认实现:
return (this == obj);
,即比较两个对象的内存地址(是否为同一个对象)。 - 重写场景:当需要判断两个对象的 "内容" 是否相等时(而非地址),需重写此方法。
- 重写原则:
- 自反性:
x.equals(x)
必须返回true
。 - 对称性:若
x.equals(y)
为true
,则y.equals(x)
也必须为true
。 - 传递性:若
x.equals(y)
和y.equals(z)
为true
,则x.equals(z)
也为true
。 - 一致性:多次调用结果应一致(对象未被修改时)。
- 非空性:
x.equals(null)
必须返回false
。
- 自反性:
java
// 示例:重写 equals 比较对象内容
class Student {
String id;
String name;
@Override
public boolean equals(Object obj) {
// 同一对象直接返回 true
if (this == obj) return true;
// 类型不同返回 false
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
// 比较关键属性是否相等
return id.equals(student.id) && name.equals(student.name);
}
}
hashCode()
- 作用:返回对象的哈希码(一个整数),主要用于哈希表(如
HashMap
、HashSet
)中快速定位对象。 - 默认实现:返回对象的内存地址转换的整数(不同对象哈希码通常不同)。
- 重写原则(与
equals
强关联):- 若
x.equals(y)
为true
,则x.hashCode()
必须等于y.hashCode()
。 - 若
x.equals(y)
为false
,x.hashCode()
与y.hashCode()
可以相等(但可能影响哈希表性能)。
- 若
- 常见实现:结合对象中参与
equals
比较的属性计算哈希码,例如:
java
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + name.hashCode(); // 31 是质数,减少哈希冲突
return result;
}
toString()
- 作用:返回对象的字符串表示,便于打印和调试。
- 默认实现:
getClass().getName() + "@" + Integer.toHexString(hashCode())
,例如com.example.Student@1b6d3586
。 - 重写建议:返回包含对象关键信息的字符串,提高可读性。
java
@Override
public String toString() {
return "Student{id='" + id + "', name='" + name + "'}";
}
getClass()
- 作用:返回对象的运行时类(
Class
对象)。 - 特点:
- 方法声明为
final
,不能被重写。 - 常用于反射(动态获取类信息、调用方法等)。
- 方法声明为
java
Student s = new Student();
Class<? extends Student> clazz = s.getClass();
System.out.println(clazz.getName()); // 输出类的全限定名,如 com.example.Student
clone()
- 作用:创建并返回当前对象的副本(克隆)。
- 注意事项:
- 方法声明为
protected
,若要在类外调用,需重写并改为public
。 - 类需实现
Cloneable
接口(标记接口,无实际方法),否则调用时会抛出CloneNotSupportedException
。 - 默认是浅克隆:基本类型字段复制值,引用类型字段复制地址(与原对象共享引用对象)。
- 方法声明为
java
class Person implements Cloneable {
String name;
int age;
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // 调用父类的 clone 实现
}
}
// 使用
Person p1 = new Person();
p1.name = "Alice";
p1.age = 20;
try {
Person p2 = p1.clone(); // 克隆对象
System.out.println(p2.name); // Alice(复制成功)
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
线程相关方法
wait()
:使当前线程进入等待状态,直到被notify()
或notifyAll()
唤醒。notify()
:唤醒在此对象监视器上等待的单个线程。notifyAll()
:唤醒在此对象监视器上等待的所有线程。- 特点 :均为
final
方法,需在同步代码块(synchronized
)中使用,用于线程间通信。
finalize()
- 作用:垃圾回收器回收对象前调用,用于释放资源(如关闭文件、网络连接)。
- 注意:
- 不推荐使用,Java 9 后已标记为过时(
@Deprecated
)。 - 执行时机不确定,无法保证一定会被调用。
- 替代方案:使用
try-with-resources
自动释放资源。
- 不推荐使用,Java 9 后已标记为过时(
Object 类的重要性
- 统一接口 :为所有 Java 对象提供了统一的方法规范,例如任何对象都可以调用
toString()
打印信息。 - 支持核心机制 :
equals()
和hashCode()
是哈希容器(HashMap
等)正常工作的基础;wait()
/notify()
是线程同步的核心工具。 - 反射基础 :
getClass()
方法为反射提供了入口,使动态操作类和对象成为可能。
字符串相关类
Java 中字符串相关的核心类主要有 String
、StringBuilder
和 StringBuffer
,它们用于处理字符串数据,但在特性和使用场景上有显著区别。下面详细介绍这三个类的特点、常用方法及最佳实践。
String(不可变字符串)
String
是 Java 中最常用的字符串类,其核心特性是不可变性:一旦创建,字符串的内容无法被修改,任何修改操作都会返回一个新的 String
对象。
不可变性原理
String
类内部使用 private final char[]
数组存储字符(JDK 9+ 改为 byte[]
节省空间),final
修饰确保数组引用不可变,且没有提供修改数组元素的方法,因此字符串内容无法改变。
java
String s = "hello";
s += " world"; // 实际创建了新的String对象,原"hello"未被修改
常用构造方法
java
// 1. 直接赋值(推荐,会使用字符串常量池)
String s1 = "hello";
// 2. 构造方法(new关键字会创建新对象,不推荐)
String s2 = new String("hello");
String s3 = new String(new char[]{'h', 'i'}); // 从字符数组创建
核心方法
方法 | 功能 | 示例 |
---|---|---|
length() |
返回字符串长度 | "abc".length() → 3 |
charAt(int index) |
获取指定索引的字符 | "abc".charAt(1) → 'b' |
substring(int begin, int end) |
截取子串(左闭右开) | "hello".substring(1,3) → "el" |
equals(Object obj) |
比较内容是否相等 | "a".equals("a") → true |
equalsIgnoreCase(String s) |
忽略大小写比较 | "A".equalsIgnoreCase("a") → true |
startsWith(String prefix) |
判断是否以指定前缀开头 | "hello".startsWith("he") → true |
endsWith(String suffix) |
判断是否以指定后缀结尾 | "hello".endsWith("lo") → true |
indexOf(String str) |
查找子串首次出现的索引 | "hello".indexOf("l") → 2 |
lastIndexOf(String str) |
查找子串最后出现的索引 | "hello".lastIndexOf("l") → 3 |
replace(char old, char new) |
替换字符 | "hello".replace('l','x') → "hexxo" |
replaceAll(String regex, String replacement) |
正则替换 | "a1b2c".replaceAll("\\d", "") → "abc" |
split(String regex) |
按正则拆分字符串 | "a,b,c".split(",") → ["a","b","c"] |
trim() |
去除首尾空白字符 | " hi ".trim() → "hi" |
toUpperCase()/toLowerCase() |
转大小写 | "Hello".toUpperCase() → "HELLO" |
valueOf(xxx) |
静态方法,将其他类型转为字符串 | String.valueOf(123) → "123" |
字符串常量池
为节省内存,Java 维护了一个字符串常量池(方法区中):
- 直接赋值的字符串(如
String s = "abc"
)会优先从常量池查找,若存在则复用,否则创建并放入常量池。 new
关键字创建的字符串(如new String("abc")
)会在堆中创建新对象,且不会自动放入常量池(需调用intern()
方法手动入池)。
java
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1 == s2); // true(常量池复用,地址相同)
System.out.println(s1 == s3); // false(s3在堆中,地址不同)
System.out.println(s1 == s3.intern()); // true(s3.intern()返回常量池中的对象)
StringBuilder 类(可变字符串,非线程安全)
StringBuilder
是 JDK 5 引入的可变字符串类,其内容可以被修改,所有操作都在原对象上进行,不会创建新对象,效率高于 String
。但它非线程安全(方法没有 synchronized
修饰),适合单线程场景。
常用构造方法
java
StringBuilder sb1 = new StringBuilder(); // 初始容量16
StringBuilder sb2 = new StringBuilder(32); // 指定初始容量(减少扩容次数)
StringBuilder sb3 = new StringBuilder("hello"); // 从字符串初始化
核心方法(支持链式调用)
方法 | 功能 | 示例 |
---|---|---|
append(xxx) |
追加内容(任意类型) | sb.append("a").append(123) → "a123" |
insert(int index, xxx) |
在指定位置插入内容 | sb.insert(2, "xyz") |
delete(int start, int end) |
删除指定范围字符 | sb.delete(1,3) |
deleteCharAt(int index) |
删除指定索引的字符 | sb.deleteCharAt(2) |
reverse() |
反转字符串 | new StringBuilder("abc").reverse() → "cba" |
replace(int start, int end, String str) |
替换指定范围内容 | sb.replace(1,3,"xx") |
setCharAt(int index, char c) |
修改指定索引的字符 | sb.setCharAt(1, 'x') |
toString() |
转换为 String 对象 | sb.toString() |
length() |
返回长度 | sb.length() |
StringBuffer 类(可变字符串,线程安全)
StringBuffer
是 JDK 1.0 引入的可变字符串类,功能与 StringBuilder
几乎完全一致 ,但它线程安全(所有方法都被 synchronized
修饰),因此效率略低,适合多线程场景。
java
StringBuffer sbf = new StringBuffer("java");
sbf.append(8).append(" is great");
System.out.println(sbf.toString()); // "java8 is great"
三者对比与选择
特性 | **String** |
**StringBuilder** |
**StringBuffer** |
---|---|---|---|
可变性 | 不可变 | 可变 | 可变 |
线程安全 | 安全(不可变天然安全) | 不安全 | 安全(方法加锁) |
效率 | 低(频繁修改产生大量对象) | 高 | 中(锁开销) |
适用场景 | 字符串不常修改 | 单线程,频繁修改 | 多线程,频繁修改 |
最佳实践
- 字符串不修改时用
String
:如常量定义、字符串拼接次数少的场景。
java
String name = "Alice"; // 推荐
- 单线程频繁修改用
StringBuilder
:如循环拼接字符串。
java
// 反例:效率低,每次循环创建新对象
String s = "";
for (int i = 0; i < 1000; i++) {
s += i;
}
// 正例:效率高,在原对象上操作
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
- 多线程频繁修改用
StringBuffer
:如多线程日志拼接。
java
// 多线程环境下保证线程安全
StringBuffer logBuffer = new StringBuffer();
// 多个线程同时调用 logBuffer.append(...)
- 初始化容量优化 :创建
StringBuilder
/StringBuffer
时,若能预估字符串长度,指定初始容量(如new StringBuilder(1024)
),可减少内部数组扩容次数,提高效率。 - 避免
String
与StringBuilder
混用:如下代码会频繁转换类型,降低效率:
java
// 反例
StringBuilder sb = new StringBuilder();
sb.append("a" + 1 + "b"); // 会先创建临时String对象
基本类型包装类
在 Java 中,基本数据类型(如 int
、char
、boolean
等)不是对象,但在很多场景下需要将它们作为对象处理(如集合框架中只能存储对象)。为此,Java
提供了基本类型包装类,用于将基本类型包装为对象。这些类都位于 java.lang
包中,无需显式导入即可使用。
包装类的对应关系
Java 为 8 种基本类型提供了对应的包装类,具体对应关系如下:
基本类型 | 包装类(java.lang 包) | 父类 |
---|---|---|
byte |
Byte |
Number |
short |
Short |
Number |
int |
Integer |
Number |
long |
Long |
Number |
float |
Float |
Number |
double |
Double |
Number |
char |
Character |
Object |
boolean |
Boolean |
Object |
- 前 6 种数值型包装类继承自
Number
类,提供了数值转换方法(如intValue()
、doubleValue()
)。 Character
和Boolean
直接继承Object
类。
包装类的核心特性
- 装箱与拆箱
- 装箱:将基本类型转换为对应的包装类对象。
- 拆箱:将包装类对象转换为对应的基本类型。
JDK 5 引入了**自动装箱(Auto-Boxing)和自动拆箱(Auto-Unboxing)**机制,编译器会自动完成转换,无需手动调用方法。
java
// 自动装箱:int → Integer
int num = 100;
Integer i1 = num; // 等价于 Integer i1 = Integer.valueOf(num);
// 自动拆箱:Integer → int
Integer i2 = 200;
int num2 = i2; // 等价于 int num2 = i2.intValue();
// 包装类与基本类型混合运算(自动拆箱后计算,再自动装箱)
Integer i3 = 300;
i3 += 100; // 等价于 i3 = Integer.valueOf(i3.intValue() + 100);
System.out.println(i3); // 400
- 常量池缓存机制
为节省内存,部分包装类对一定范围内的值提供了缓存机制,当创建该范围内的对象时,会复用缓存中的对象,而不是新建对象。
Integer
:缓存范围为 -128 ~ 127(可通过 JVM 参数XX:AutoBoxCacheMax
调整上限)。Byte
、Short
、Long
:缓存范围固定为 -128 ~ 127(全部值都在范围内,因此所有对象都会被缓存)。Character
:缓存范围为 0 ~ 127(ASCII 字符)。Boolean
:缓存TRUE
和FALSE
两个静态对象。
java
// Integer 缓存示例
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(复用缓存对象)
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false(超出缓存范围,新建对象)
// Character 缓存示例
Character ch1 = 'A'; // ASCII值65,在0~127范围内
Character ch2 = 'A';
System.out.println(ch1 == ch2); // true(复用缓存)
Character ch3 = 128; // 超出缓存范围
Character ch4 = 128;
System.out.println(ch3 == ch4); // false(新建对象)
注意: ==
比较的是对象地址,equals()
比较的是值。包装类比较值时应使用 equals()
,避免因缓存机制导致误判。
常用包装类及核心方法
以最常用的 Integer
、Character
、Boolean
为例,介绍其核心方法。
Integer 类(对应 int)
Integer 是使用最广泛的包装类,提供了整数类型转换、进制转换等功能。
java
// 1. 构造方法(不推荐,建议用 valueOf())
Integer i1 = new Integer(100); // 已过时
Integer i2 = new Integer("100"); // 已过时
// 2. 静态工厂方法(推荐,会使用缓存)
Integer i3 = Integer.valueOf(100);
Integer i4 = Integer.valueOf("100"); // 字符串转Integer
// 3. 基本类型转换(拆箱)
int num = i3.intValue();
// 4. 字符串转基本类型(常用)
int num2 = Integer.parseInt("123"); // 字符串→int
int num3 = Integer.parseInt("1111", 2); // 二进制"1111"→15(第二个参数指定进制)
// 5. 基本类型转字符串
String str1 = Integer.toString(123); // "123"
String str2 = Integer.toBinaryString(15); // 二进制字符串"1111"
String str3 = Integer.toHexString(255); // 十六进制字符串"ff"
// 6. 常量
System.out.println(Integer.MAX_VALUE); // 2^31-1(2147483647)
System.out.println(Integer.MIN_VALUE); // -2^31(-2147483648)
System.out.println(Integer.SIZE); // 32(位数)
Character 类(对应 char)
Character
提供了字符判断、转换等工具方法。
java
// 1. 静态工厂方法
Character ch = Character.valueOf('a');
// 2. 拆箱
char c = ch.charValue();
// 3. 字符判断方法(静态)
System.out.println(Character.isLetter('A')); // true(是否为字母)
System.out.println(Character.isDigit('5')); // true(是否为数字)
System.out.println(Character.isWhitespace(' ')); // true(是否为空白字符)
System.out.println(Character.isUpperCase('B')); // true(是否为大写字母)
// 4. 字符转换
System.out.println(Character.toUpperCase('a')); // 'A'(转大写)
System.out.println(Character.toLowerCase('C')); // 'c'(转小写)
Boolean 类(对应 boolean)
Boolean
用于包装布尔值,提供了字符串转布尔值的方法。
java
// 1. 静态工厂方法
Boolean b1 = Boolean.valueOf(true);
Boolean b2 = Boolean.valueOf("true"); // 字符串转Boolean(忽略大小写?不,仅"true"为true)
// 2. 字符串转基本类型
boolean flag = Boolean.parseBoolean("false"); // "false"→false
// 3. 常量
System.out.println(Boolean.TRUE); // 静态TRUE对象
System.out.println(Boolean.FALSE); // 静态FALSE对象
Number 类的通用方法(数值型包装类)
Byte
、Short
、Integer
、Long
、Float
、Double
都继承自 Number
类,可通过以下方法转换为其他基本类型:
java
Integer i = 100;
byte b = i.byteValue(); // 转byte
short s = i.shortValue(); // 转short
long l = i.longValue(); // 转long
float f = i.floatValue(); // 转float
double d = i.doubleValue(); // 转double
常见问题与注意事项
null
安全问题
包装类是对象,可能为 null
,而基本类型不能为 null
。自动拆箱时若包装类为 null
,会抛出 NullPointerException:
java
Integer i = null;
int num = i; // 编译通过,但运行时抛出 NullPointerException
解决:使用前判断是否为 null
:
java
Integer i = null;
int num = (i != null) ? i : 0; // 安全处理
- 字符串转换的异常
使用 parseXxx()
方法(如 Integer.parseInt()
)将字符串转为基本类型时,若字符串格式不正确,会抛出 NumberFormatException:
java
try {
int num = Integer.parseInt("abc"); // 字符串不是数字,抛异常
} catch (NumberFormatException e) {
e.printStackTrace();
}
- 与基本类型的选择
- 优先使用基本类型:当不需要对象特性(如作为集合元素)时,基本类型效率更高(无需创建对象,节省内存)。
- 必须使用包装类的场景:
- 集合框架中(如
List<Integer>
,不能存储int
)。 - 泛型参数(如 T 必须是对象类型)。
- 需表示
null
时(如数据库字段可能为null
)。
- 集合框架中(如
数学与数值相关类
Math 类(数学工具类)
Math
类位于 java.lang
包中,是一个final
类(不可被继承),包含大量静态方法,提供了基本数学运算(如绝对值、三角函数、随机数等),无需创建实例即可直接使用。
Math 类的核心特性
- 无构造方法 :构造方法被
private
修饰,无法实例化(所有方法都是静态的)。 - 静态常量 :
Math.PI
:圆周率(约等于 3.141592653589793)。Math.E
:自然对数的底数(约等于 2.718281828459045)。
- 方法分类:涵盖基础运算、三角函数、指数对数、随机数等。
常用方法示例
- 基础运算
java
// 绝对值
int abs1 = Math.abs(-10); // 10
double abs2 = Math.abs(-3.14); // 3.14
// 最大值/最小值
int max1 = Math.max(5, 10); // 10
double max2 = Math.max(3.14, 2.71); // 3.14
int min1 = Math.min(5, 10); // 5
double min2 = Math.min(3.14, 2.71); // 2.71
// 四舍五入(返回 long 类型)
long round1 = Math.round(3.6); // 4
long round2 = Math.round(3.4); // 3
// 向上取整(返回 double,大于等于参数的最小整数)
double ceil = Math.ceil(3.2); // 4.0
// 向下取整(返回 double,小于等于参数的最大整数)
double floor = Math.floor(3.8); // 3.0
- 三角函数(参数为弧度)
java
double pi = Math.PI;
// 正弦(sin(π/2) = 1)
double sin = Math.sin(pi / 2); // 1.0
// 余弦(cos(π) = -1)
double cos = Math.cos(pi); // -1.0
// 正切(tan(π/4) = 1)
double tan = Math.tan(pi / 4); // 1.0(近似值)
// 弧度转角度(π 弧度 = 180 度)
double toDegrees = Math.toDegrees(pi); // 180.0
// 角度转弧度(180 度 = π 弧度)
double toRadians = Math.toRadians(180); // 3.141592653589793(即 π)
- 指数与对数运算
java
// 指数运算(a^b)
double pow1 = Math.pow(2, 3); // 8.0(2^3)
double pow2 = Math.pow(10, 2); // 100.0(10^2)
// 平方根(√a)
double sqrt = Math.sqrt(16); // 4.0
// 自然对数(ln(a),以 e 为底)
double log = Math.log(Math.E); // 1.0(ln(e) = 1)
// 以 10 为底的对数
double log10 = Math.log10(100); // 2.0(log10(100) = 2)
- 随机数生成
Math.random()
生成一个大于等于 0.0 且小于 1.0 的随机 double 值,可通过换算得到任意范围的随机数。
java
// 生成 [0, 1) 之间的随机数
double random = Math.random();
// 生成 [0, 10) 之间的随机整数
int random1 = (int) (Math.random() * 10);
// 生成 [5, 15] 之间的随机整数(包含 5 和 15)
int min = 5;
int max = 15;
int random2 = min + (int) (Math.random() * (max - min + 1));
Number 类(数值包装类的父类)
Number
类是一个抽象类,位于 java.lang
包中,是所有数值型包装类的父类(如 Integer
、Double
、Long
等)。它定义了将数值型对象转换为基本类型的通用方法。
核心抽象方法(数值转换)
Number
类定义了 6 个抽象方法,用于将包装类对象转换为对应的基本类型。子类必须实现这些方法:
方法 | 功能 | 示例(以 Integer 为例) |
---|---|---|
byteValue() |
转换为 byte 类型 |
Integer i = 100; byte b = i.byteValue(); |
shortValue() |
转换为 short 类型 |
short s = i.shortValue(); |
intValue() |
转换为 int 类型 |
int num = i.intValue(); |
longValue() |
转换为 long 类型 |
long l = i.longValue(); |
floatValue() |
转换为 float 类型 |
float f = i.floatValue(); |
doubleValue() |
转换为 double 类型 |
double d = i.doubleValue(); |
使用场景
当需要统一处理不同数值类型的包装类时,Number
类作为父类可以实现多态:
java
// 定义一个方法,接收 Number 类型,打印其各种基本类型的值
public static void printValues(Number num) {
System.out.println("byte: " + num.byteValue());
System.out.println("int: " + num.intValue());
System.out.println("double: " + num.doubleValue());
}
// 测试:传入不同的数值包装类
public static void main(String[] args) {
printValues(100); // Integer 自动装箱
printValues(3.14); // Double 自动装箱
printValues(1000L); // Long 自动装箱
}
/* 输出结果:
byte: 100
int: 100
double: 100.0
byte: 3(3.14 强转为 byte 是 3)
int: 3
double: 3.14
byte: -24(1000L 强转为 byte 是 -24,因超出范围)
int: 1000
double: 1000.0
*/
异常与错误类
异常与错误的根类:Throwable
Throwable
是 Java 中所有错误(Error
)和异常(Exception
)的顶层父类,位于 java.lang
包中。它定义了所有异常 / 错误共有的属性和方法。
主要方法
getMessage()
:返回异常的详细描述信息(创建异常时传入的字符串)。printStackTrace()
:打印异常的堆栈跟踪信息(包含异常类型、信息及发生位置),用于调试。getCause()
:返回引发当前异常的原因(用于链式异常)。toString()
:返回异常的类型和描述信息(如java.lang.NullPointerException:
空指针异常)。
体系结构
Throwable
有两个直接子类,分别代表不同类型的问题:
plain
Throwable
├─ Error:严重错误(程序通常无法处理)
└─ Exception:异常(程序可以处理)
├─ 受检异常(Checked Exception):编译期必须处理
└─ 非受检异常(Unchecked Exception):编译期无需处理(继承自 RuntimeException)
Error 类(严重错误)
Error
表示程序运行时发生的严重错误,通常是由 JVM 或系统级问题导致的,程序无法通过代码处理,应终止运行。
常见子类
OutOfMemoryError
:内存溢出错误(JVM 无法分配足够内存)。StackOverflowError
:栈溢出错误(方法调用栈过深,如无限递归)。NoClassDefFoundError
:类定义未找到(编译时存在该类,运行时缺失)。UnsupportedClassVersionError
:JVM 版本不兼容(类编译版本高于运行环境)。
特点
- 属于非受检错误(无需在代码中处理)。
- 通常是系统级问题,程序无法恢复,应避免捕获(捕获也无法有效处理)。
Exception 类(可处理的异常)
Exception
表示程序运行时发生的异常情况,这些情况是程序可以预测并通过代码处理的(如输入错误、文件不存在等)。根据是否必须处理,分为两类:
受检异常(Checked Exception)
- 定义:除
RuntimeException
及其子类外的所有Exception
子类。 - 特点:编译期强制要求处理(必须用
try-catch
捕获或throws
声明抛出),否则编译报错。 - 常见子类:
IOException
:输入输出异常(如文件读写错误)。ClassNotFoundException
:类未找到(动态加载类时)。SQLException
:数据库操作异常。InterruptedException
:线程被中断异常。
java
// 读取文件(可能抛出 IOException,必须处理)
public static void readFile() throws IOException { // 声明抛出受检异常
FileReader fr = new FileReader("test.txt");
fr.read();
fr.close();
}
// 调用时必须处理(try-catch 或继续抛出)
public static void main(String[] args) {
try {
readFile();
} catch (IOException e) { // 捕获并处理异常
e.printStackTrace();
System.out.println("文件读取失败:" + e.getMessage());
}
}
非受检异常(Unchecked Exception)
- 定义:
RuntimeException
及其子类(继承自Exception
)。 - 特点:编译期无需强制处理(可选择处理或不处理),通常是由代码逻辑错误导致的。
- 常见子类:
NullPointerException
:空指针异常(调用null
对象的方法或属性)IndexOutOfBoundsException
:索引越界(如数组、集合的索引超出范围)。ClassCastException
:类型转换异常(将对象强制转换为不兼容的类型)。ArithmeticException
:算术异常(如除数为 0)。IllegalArgumentException
:参数非法(方法接收到不合法的参数)。
异常处理机制
Java 通过try-catch-finally
和 throws
关键字处理异常,核心目的是捕获异常并进行补救,避免程序崩溃。
try-catch
捕获异常
java
try {
// 可能发生异常的代码块
String s = null;
System.out.println(s.length());
} catch (NullPointerException e) {
// 捕获特定异常并处理
System.out.println("捕获到空指针异常:" + e.getMessage());
} catch (Exception e) {
// 捕获其他异常(父类异常需放在子类异常之后)
System.out.println("捕获到其他异常:" + e.getMessage());
}
finally
释放资源
finally
块中的代码无论是否发生异常都会执行,通常用于释放资源(如关闭文件、数据库连接)。
java
FileReader fr = null;
try {
fr = new FileReader("test.txt");
fr.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 确保资源关闭
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
throws
声明抛出异常
如果方法内部无法处理异常,可通过 throws
声明将异常抛给调用者处理。
java
// 声明抛出受检异常(调用者必须处理)
public static void readFile() throws IOException {
FileReader fr = new FileReader("test.txt");
fr.close();
}
// 调用者处理异常
public static void main(String[] args) throws IOException { // 继续抛出,由JVM处理
readFile();
}
throw
主动抛出异常
通过 throw
关键字主动抛出异常(通常用于校验参数或业务规则)。
java
public static void checkAge(int age) {
if (age < 0 || age > 150) {
// 主动抛出非法参数异常
throw new IllegalArgumentException("年龄必须在0-150之间");
}
}
异常处理最佳实践
- 避免捕获
Throwable
或Exception
:应捕获具体异常,否则可能掩盖严重错误(如Error
)。 - 不要忽略异常:
catch
块中至少打印异常信息,避免空catch
块(导致问题难以排查)。 - 优先使用
try-with-resources
:JDK 7+ 提供的自动资源关闭机制,简化资源释放代码。
java
// 自动关闭实现了 AutoCloseable 接口的资源(如 FileReader)
try (FileReader fr = new FileReader("test.txt")) {
fr.read();
} catch (IOException e) {
e.printStackTrace();
}
- 受检异常与非受检异常的选择:
- 可恢复的错误用受检异常(如文件不存在,提示用户重新输入)。
- 编程错误用非受检异常(如空指针,应修正代码)。
- 异常信息要具体:抛出异常时提供详细描述(如
throw new IllegalArgumentException("用户ID不能为空")
),便于调试。
线程与并发类
java.lang包提供多线程编程的核心类:
Thread
:线程类,通过继承或实现Runnable
接口创建线程。Runnable
:线程任务接口,定义了线程执行的核心方法run()
。ThreadLocal
:为线程提供局部变量,解决多线程共享资源的并发问题。StackTraceElement
:用于表示线程的堆栈跟踪信息。
JUC在此只做了解,笔记整理至JUC阶段。
java.util包
java.util
包提供了大量实用工具类,其中日期时间类(LocalDate
/LocalTime
/LocalDateTime
)、Random
和 UUID
是日常开发中频繁使用的工具。
日期时间类(LocalDate/LocalTime/LocalDateTime)
Java 8 引入了 java.time
包(包含在 JDK 中),提供了全新的日期时间 API,替代了传统的 Date
和 Calendar
类。其中 LocalDate
、LocalTime
、LocalDateTime
是最常用的三个类,它们不可变且线程安全。
核心类简介
LocalDate
:仅包含日期(年、月、日),无时间信息。LocalTime
:仅包含时间(时、分、秒、纳秒),无日期信息。LocalDateTime
:包含日期和时间的完整信息(组合了LocalDate
和LocalTime
)
实例化与获取当前时间
java
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
public class DateTimeDemo {
public static void main(String[] args) {
// 获取当前日期(LocalDate)
LocalDate today = LocalDate.now();
System.out.println("当前日期:" + today); // 输出:2024-05-20(示例)
// 获取当前时间(LocalTime)
LocalTime nowTime = LocalTime.now();
System.out.println("当前时间:" + nowTime); // 输出:15:30:45.123456789(示例)
// 获取当前日期时间(LocalDateTime)
LocalDateTime now = LocalDateTime.now();
System.out.println("当前日期时间:" + now); // 输出:2024-05-20T15:30:45.123456789
// 手动创建指定日期时间
LocalDate specificDate = LocalDate.of(2023, 10, 1); // 2023年10月1日
LocalTime specificTime = LocalTime.of(8, 30, 0); // 8:30:00
LocalDateTime specificDateTime = LocalDateTime.of(2023, 10, 1, 8, 30);
}
}
常用方法(以 LocalDateTime 为例,LocalDate/LocalTime 类似)
- 获取日期时间字段
java
LocalDateTime now = LocalDateTime.now();
int year = now.getYear(); // 年份(如 2024)
int month = now.getMonthValue(); // 月份(1-12)
int day = now.getDayOfMonth(); // 日(1-31)
int hour = now.getHour(); // 时(0-23)
int minute = now.getMinute(); // 分(0-59)
int second = now.getSecond(); // 秒(0-59)
// 星期(返回 DayOfWeek 枚举,如 FRIDAY)
System.out.println("星期:" + now.getDayOfWeek());
- 修改日期时间(返回新对象,原对象不变)
java
LocalDateTime now = LocalDateTime.now();
// 增加/减少年份
LocalDateTime nextYear = now.plusYears(1); // 明年此时
LocalDateTime lastYear = now.minusYears(1); // 去年此时
// 增加/减少月份
LocalDateTime nextMonth = now.plusMonths(1);
// 调整到当月第一天
LocalDateTime firstDayOfMonth = now.withDayOfMonth(1);
// 调整到指定小时
LocalDateTime atThreePM = now.withHour(15);
- 日期时间比较
java
LocalDate date1 = LocalDate.of(2023, 10, 1);
LocalDate date2 = LocalDate.of(2024, 1, 1);
// 比较是否相等
boolean isEqual = date1.equals(date2); // false
// 比较大小
boolean isBefore = date1.isBefore(date2); // true(date1 在 date2 之前)
boolean isAfter = date1.isAfter(date2); // false
- 日期时间格式化与解析
使用 DateTimeFormatter
进行格式化(线程安全,替代传统的 SimpleDateFormat
):
java
import java.time.format.DateTimeFormatter;
LocalDateTime now = LocalDateTime.now();
// 格式化:日期时间 → 字符串
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(formatter);
System.out.println("格式化后:" + formatted); // 2024-05-20 15:30:45
// 解析:字符串 → 日期时间
String str = "2023-10-01 08:30:00";
LocalDateTime parsed = LocalDateTime.parse(str, formatter);
Random 类(随机数生成)
Random
类用于生成伪随机数,支持多种数据类型(int
、double
、boolean
等)的随机值生成。
基本使用
java
import java.util.Random;
public class RandomDemo {
public static void main(String[] args) {
// 创建 Random 实例(默认使用系统时间作为种子)
Random random = new Random();
// 生成随机 int(范围:Integer.MIN_VALUE ~ Integer.MAX_VALUE)
int randomInt = random.nextInt();
// 生成 [0, n) 范围内的随机 int(n 为正整数)
int randomIntRange = random.nextInt(100); // 0-99 之间的随机数
// 生成随机 double(范围:0.0 ~ 1.0)
double randomDouble = random.nextDouble();
// 生成随机 boolean
boolean randomBoolean = random.nextBoolean();
System.out.println("随机整数:" + randomInt);
System.out.println("0-99的随机数:" + randomIntRange);
System.out.println("随机小数:" + randomDouble);
}
}
生成指定范围的随机数
java
// 生成 [min, max] 范围内的随机整数(包含 min 和 max)
public static int randomInRange(Random random, int min, int max) {
if (min >= max) {
throw new IllegalArgumentException("min 必须小于 max");
}
// 公式:min + [0, max - min + 1) → [min, max]
return min + random.nextInt(max - min + 1);
}
// 使用
Random random = new Random();
int num = randomInRange(random, 5, 10); // 5-10 之间的随机数
注意事项
- 种子与随机性:
Random
的随机数由种子(seed)决定,相同种子会生成相同序列。若需更高随机性,可使用SecureRandom
(加密级随机数生成器)。 - 线程安全:
Random
是线程安全的,但多线程下并发使用可能导致性能下降,此时可考虑每个线程创建独立的Random
实例。
UUID 类(通用唯一标识符)
UUID(Universally Unique Identifier)是一个 128 位的唯一标识符,用于在分布式系统中标识信息(如订单号、会话 ID 等),保证全球唯一性。
结构与格式
UUID 格式为 8-4-4-4-12 的十六进制字符串(共 36 个字符),例如:
550e8400-e29b-41d4-a716-446655440000
生成UUID
java
import java.util.UUID;
public class UUIDDemo {
public static void main(String[] args) {
// 生成随机 UUID(最常用,基于随机数)
UUID uuid = UUID.randomUUID();
System.out.println("随机 UUID:" + uuid.toString());
// 从指定的长整数生成 UUID(很少用)
UUID uuid2 = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
System.out.println("解析的 UUID:" + uuid2);
}
}
应用场景
- 作为数据库表的主键(替代自增
ID
,适合分布式系统)。 - 生成唯一的文件名、会话
ID
、Token
等。 - 分布式系统中标识消息或事件,避免冲突。
优势
- 唯一性:通过算法保证全球范围内几乎不会重复(概率极低)。
- 无需中心化协调:生成过程无需依赖外部系统(如数据库),适合分布式场景。