Skip to content

常用类与包装类型 ​

学习目标 ​

  • 理解包装类型与基本类型、自动装箱/拆箱和 null 风险。
  • 使用 Integer、Long、Double、Boolean、Character 完成解析和格式转换。
  • 使用 Objects 表达安全比较和前置校验,用 Optional 表达查询结果可能缺失。
  • 能根据需求转到大数与精确计算选择 BigInteger、BigDecimal 或检查溢出的 Math API。

核心知识点 ​

专业术语 ​

  • 包装类型(wrapper type):Integer、Long、Boolean 等对象类型,能用于泛型并可表达 null。
  • 自动装箱与拆箱(boxing/unboxing):基本值与包装对象之间由编译器插入的转换,拆箱 null 会抛 NullPointerException。
  • 解析(parse):Integer.parseInt 等方法把文本转换为基本值;格式非法通常抛 NumberFormatException。
  • Objects 工具类:提供空值安全比较、哈希、字符串和前置条件检查。
  • Optional<T>:明确表达“可能有一个 T,也可能没有”的返回容器,不是通用的空值替代品。

白话解释与边界 ​

包装类型像“可以放进集合的盒子”,可以有 null,但不应因为自动装箱就忘记空值边界。Integer.parseInt 返回基本 int,失败会抛异常;Integer.valueOf 返回对象,缓存只是一种实现优化,不能用 == 判断数值。外部输入解析要捕获并翻译格式错误。

Optional 更适合作为查询方法的返回值,调用方通过 map、orElse 或 orElseThrow 选择缺失语义,而不是到处调用 get()。它不能替代字段、参数和集合的空值设计;Optional 本身通常也不应存进实体字段或序列化协议。

常用用法 ​

Integer.parseInt:解析 int 文本 ​

把十进制或指定进制文本解析为 int;格式错误或越界会抛 NumberFormatException。

java
String text = "42";
// 初始状态:text 当前为 "42"。
int count = Integer.parseInt(text);
// 初始状态:count 当前为 Integer.parseInt(text)。
// 返回:Integer.parseInt("42") 把十进制文本解析为整数 42。
// 作用:把十进制或指定进制文本解析为 int。
// 关键变化:Integer.parseInt(text) 解析 text="42" 后让 count 变为 42。
int hex = Integer.parseInt("ff", 16);
// 返回:hex 接收 Integer.parseInt("ff", 16) 的返回值。
// 关键变化:Integer.parseInt("ff", 16) 按十六进制解析文本,hex 变为 255。
System.out.println(count);
// 输出:42
System.out.println(hex);
// 输出:255

Long.parseLong:解析 long 文本 ​

文本超出 int 范围而仍在 long 范围内时,使用 Long.parseLong 解析。

java
String text = "9000000000";
// 初始状态:text 当前为 "9000000000"。
long id = Long.parseLong(text);
// 初始状态:id 当前为 Long.parseLong(text)。
// 返回:Long.parseLong("9000000000") 返回 long 值 9000000000。
// 作用:文本超出 int 范围而仍在 long 范围内时,使用 Long.parseLong 解析。
long next = id + 1;
System.out.println(id);
// 输出:9000000000
System.out.println(next);
// 输出:9000000001

Integer.valueOf:创建 Integer 对象 ​

需要 Integer 对象用于泛型或可空位置时使用 valueOf,不要依赖包装对象缓存的身份。

java
Integer count = Integer.valueOf("42");
// 初始状态:count 当前为 Integer.valueOf("42")。
// 返回:Integer.valueOf("42") 创建值为 42 的 Integer 对象。
// 作用:需要 Integer 对象用于泛型或可空位置时使用 valueOf,不要依赖包装对象缓存的身份。
int value = count.intValue();
// 初始状态:value 当前为 count.intValue()。
String type = count.getClass().getSimpleName();
System.out.println(type);
// 输出:Integer
System.out.println(value);
// 输出:42

Long.valueOf:创建 Long 对象 ​

需要 Long 对象用于泛型或可空位置时使用 valueOf。

java
Long id = Long.valueOf("9000000000");
// 初始状态:id 当前为 Long.valueOf("9000000000")。
// 返回:Long.valueOf("9000000000") 创建值为 9000000000 的 Long 对象。
// 作用:需要 Long 对象用于泛型或可空位置时使用 valueOf。
long value = id.longValue();
// 初始状态:value 当前为 id.longValue()。
String type = id.getClass().getSimpleName();
System.out.println(type);
// 输出:Long
System.out.println(value);
// 输出:9000000000

Integer.toString:把 int 转为文本 ​

使用 Integer.toString 生成十进制或指定进制的 int 文本。

java
String decimal = Integer.toString(42);
// 初始状态:decimal 当前为 Integer.toString(42)。
// 返回:Integer.toString(42) 生成十进制文本 "42"。
// 作用:使用 Integer.toString 生成十进制或指定进制的 int 文本。
String hex = Integer.toHexString(255);
// 初始状态:hex 当前为 Integer.toHexString(255)。
System.out.println(decimal);
// 输出:42
System.out.println(hex);
// 输出:ff

Long.toString:把 long 转为文本 ​

使用 Long.toString 生成 long 的文本表达,并在协议边界明确进制。

java
long value = 5L;
// 初始状态:value 当前为 5L。
String decimal = Long.toString(value);
// 初始状态:decimal 当前为 Long.toString(value)。
// 返回:Long.toString(5L) 生成文本 "5"。
// 作用:使用 Long.toString 生成 long 的文本表达,并在协议边界明确进制。
String binary = Long.toBinaryString(value);
System.out.println(decimal);
// 输出:5
System.out.println(binary);
// 输出:101

Boolean.parseBoolean:解析 boolean 文本 ​

parseBoolean 对不区分大小写的 "true" 返回真,其他文本返回假;严格协议应先校验允许值。

java
boolean enabled = Boolean.parseBoolean("true");
// 初始状态:enabled 当前为 Boolean.parseBoolean("true")。
// 返回:Boolean.parseBoolean("true") 不区分大小写返回 true。
// 作用:parseBoolean 对不区分大小写的 "true" 返回真,其他文本返回假。
boolean typo = Boolean.parseBoolean("yes");
// 关键变化:typo 接收表达式 Boolean.parseBoolean("yes") 的计算结果。
// 初始状态:typo 当前为 Boolean.parseBoolean("yes")。
System.out.println(enabled);
// 输出:true
System.out.println(typo);
// 输出:false

Boolean.valueOf:解析 Boolean 对象 ​

需要包装对象时用 Boolean.valueOf,其文本规则与 parseBoolean 相同。

java
Boolean enabled = Boolean.valueOf("TRUE");
// 初始状态:enabled 当前为 Boolean.valueOf("TRUE")。
// 返回:Boolean.valueOf("TRUE") 创建值为 true 的 Boolean 对象。
// 作用:需要包装对象时用 Boolean.valueOf,其文本规则与 parseBoolean 相同。
Boolean typo = Boolean.valueOf("yes");
// 关键变化:typo 接收表达式 Boolean.valueOf("yes") 的计算结果。
// 初始状态:typo 当前为 Boolean.valueOf("yes")。
System.out.println(enabled);
// 输出:true
System.out.println(typo);
// 输出:false

Double.parseDouble:解析 double 文本 ​

Double.parseDouble 解析浮点文本,但结果仍受二进制精度限制,金额应使用 BigDecimal。

java
double rate = Double.parseDouble("0.25");
// 初始状态:rate 当前为 Double.parseDouble("0.25")。
// 返回:Double.parseDouble("0.25") 解析为 double 0.25。
// 作用:Double.parseDouble 解析浮点文本,但结果仍受二进制精度限制,金额应使用 BigDecimal。
double doubled = rate * 2;
// 初始状态:doubled 当前为 rate * 2。
System.out.println(rate);
// 输出:0.25
System.out.println(doubled);
// 输出:0.5

Double.isFinite:判断有限浮点数 ​

使用 Double.isFinite 排除 NaN 和正负无穷,再把浮点结果交给后续计算。

java
double normal = 0.25;
// 初始状态:normal 当前为 0.25。
double infinity = Double.POSITIVE_INFINITY;
// 初始状态:infinity 当前为 Double.POSITIVE_INFINITY。
System.out.println(Double.isFinite(normal));
// 输出:true
// 作用:使用 Double.isFinite 排除 NaN 和正负无穷,再把浮点结果交给后续计算。
System.out.println(Double.isFinite(infinity));
// 输出:false

Character.isDigit:判断数字字符 ​

Character.isDigit 按 Unicode 属性判断数字,不等同于只接受 ASCII 0 到 9。

java
char ascii = '7';
// 初始状态:ascii 当前为 '7'。
char letter = 'A';
// 初始状态:letter 当前为 'A'。
System.out.println(Character.isDigit(ascii));
// 输出:true
// 作用:Character.isDigit 按 Unicode 属性判断数字,不等同于只接受 ASCII 0 到 9。
System.out.println(Character.isDigit(letter));
// 输出:false

Character.isLetter:判断字母字符 ​

Character.isLetter 按 Unicode 属性判断字母,可识别中文等非 ASCII 字符。

java
char chinese = '中';
// 初始状态:chinese 当前为 '中'。
char digit = '7';
// 初始状态:digit 当前为 '7'。
System.out.println(Character.isLetter(chinese));
// 输出:true
// 作用:Character.isLetter 按 Unicode 属性判断字母,可识别中文等非 ASCII 字符。
System.out.println(Character.isLetter(digit));
// 输出:false

自动装箱/拆箱:在基本值与对象间转换 ​

拆箱 null 会抛 NullPointerException;集合读取、数据库字段和配置输入要先判断空值,不要让隐式转换掩盖错误。

java
// 语义:拆箱 null 会抛 NullPointerException。
// 初始状态:boxed 初始为 42;primitive 初始为 boxed。
Integer boxed = 42;
int primitive = boxed;
// 关键变化:primitive 接收表达式 boxed 的计算结果。
// 初始状态:primitive 的初始值为 boxed。
System.out.println(boxed + ", " + primitive);
// 输出:42, 42

Objects.equals:安全比较可能为 null 的值 ​

Objects.equals 在一方或双方为 null 时安全返回结果;它只比较内容,不替代业务上的大小写、规范化或版本规则。

java
String left = null;
// 初始状态:left 当前为 null。
String right = "Java";
// 初始状态:right 当前为 "Java"。
System.out.println(Objects.equals(left, right));
// 输出:false
// 作用:Objects.equals 在一方或双方为 null 时安全返回结果。

Objects.hash:组合字段计算哈希 ​

适合在值对象的 hashCode 中组合字段;参与 equals 的字段必须和参与 hashCode 的字段保持一致。

java
int hash = Objects.hash("U-42", 18);
// 初始状态:hash 当前为 Objects.hash("U-42", 18)。
// 作用:适合在值对象的 hashCode 中组合字段。
System.out.println(hash != 0);
// 输出:true

Objects.requireNonNull:校验必填引用 ​

参数为 null 时立即抛 NullPointerException,适合构造器和方法入口;异常信息应指出字段名,不能把校验拖到更深层才失败。

java
String name = Objects.requireNonNull("Ann", "name");
// 初始状态:name 当前为 Objects.requireNonNull("Ann", "name")。
// 作用:参数为 null 时立即抛 NullPointerException,适合构造器和方法入口。
System.out.println(name);
// 输出:Ann

Optional.of:创建确定非空的 Optional ​

已确认值非 null 时用 Optional.of 包装结果;传入 null 会立即失败。

java
Optional<String> present = Optional.of("Java");
// 初始状态:present 当前为 Optional.of("Java")。
// 返回:Optional.of("Java") 创建包含 "Java" 的非空 Optional。
// 作用:已确认值非 null 时用 Optional.of 包装结果。
String value = present.orElse("default");
// 初始状态:value 当前为 present.orElse("default")。
boolean available = present.isPresent();
// 返回:present.isPresent() 检查包含 "Java",available 为 true。
System.out.println(value);
// 输出:Java
System.out.println(available);
// 输出:true

Optional.ofNullable:把可空值包装为 Optional ​

来源值可能为 null 时用 Optional.ofNullable,它会把 null 转成空容器。

java
String input = null;
// 初始状态:input 当前为 null。
Optional<String> value = Optional.ofNullable(input);
// 初始状态:value 当前为 Optional.ofNullable(input)。
// 返回:Optional.ofNullable(null) 创建空 Optional,value.isEmpty() 为 true。
// 作用:来源值可能为 null 时用 Optional.ofNullable,它会把 null 转成空容器。
boolean missing = value.isEmpty();
// 返回:value.isEmpty() 检查 Optional.empty(),missing 为 true。
String fallback = value.orElse("default");
// 返回:value.orElse("default") 在缺失时返回 "default"。
System.out.println(missing);
// 输出:true
System.out.println(fallback);
// 输出:default

Optional.empty:创建明确缺失的结果 ​

需要显式表示“没有结果”时返回 Optional.empty(),不要让 Optional 变量本身为 null。

java
Optional<String> result = Optional.empty();
// 初始状态:result 当前为 Optional.empty()。
// 返回:Optional.empty() 创建不包含元素的 Optional,result.isEmpty() 为 true。
// 作用:需要显式表示“没有结果”时返回 Optional.empty(),不要让 Optional 变量本身为 null。
boolean missing = result.isEmpty();
// 初始状态:missing 当前为 result.isEmpty()。
// 返回:result.isEmpty() 确认 Optional.empty() 没有元素,missing 为 true。
int count = result.stream().toList().size();
// 返回:result.stream().toList().size() 对空 Optional 产生空列表,count 为 0。
System.out.println(missing);
// 输出:true
System.out.println(count);
// 输出:0

Optional.orElse:提供立即计算的默认值 ​

默认值已经存在或计算便宜时用 orElse,其参数在 Optional 非空时也会先求值。

java
Optional<String> value = Optional.empty();
// 初始状态:value 当前为 Optional.empty()。
String fallback = "default";
// 初始状态:fallback 当前为 "default"。
String result = value.orElse(fallback);
// 返回:value.orElse("default") 在 value 为空时返回 fallback,即 result="default"。
// 作用:默认值已经存在或计算便宜时用 orElse,其参数在 Optional 非空时也会先求值。
int length = result.length();
// 返回:result.length() 读取 "default" 的长度 7。
System.out.println(result);
// 输出:default
System.out.println(length);
// 输出:7

Optional.orElseGet:延迟计算默认值 ​

默认值需要昂贵计算或有副作用时用 orElseGet,仅在缺失时调用供应器。

java
Optional<String> value = Optional.empty();
// 初始状态:value 当前为 Optional.empty()。
String result = value.orElseGet(() -> "computed");
// 初始状态:result 当前为 value.orElseGet(() -> "computed")。
// 返回:空 value 触发供应器 () -> "computed",result 为 "computed"。
// 作用:默认值需要昂贵计算或有副作用时用 orElseGet,仅在缺失时调用供应器。
boolean computed = result.startsWith("comp");
// 返回:result.startsWith("comp") 检查 "computed" 前缀,computed 为 true。
System.out.println(result);
// 输出:computed
System.out.println(computed);
// 输出:true

Optional.orElseThrow:缺失时抛出异常 ​

结果是业务必需值时用 orElseThrow,通过异常明确报告缺失原因。

java
Optional<String> value = Optional.empty();
// 初始状态:value 当前为 Optional.empty()。
String required;
try {
    required = value.orElseThrow(() -> new IllegalArgumentException("missing"));
    // 作用:结果是业务必需值时用 orElseThrow,通过异常明确报告缺失原因。
    // 关键变化:value.orElseThrow(() -> new IllegalArgumentException("missing")) 发现 value 为空并抛出 "missing",随后 required 设为 "handled"。
} catch (IllegalArgumentException error) {
    required = "handled";
}
System.out.println(required);
// 输出:handled
System.out.println(value.isEmpty());
// 输出:true

Optional.map:转换可选值 ​

map 在值存在时转换内容,在空结果上短路,并继续返回 Optional。

java
Optional<String> input = Optional.of(" java ");
// 初始状态:input 当前为 Optional.of(" java ")。
Optional<Integer> length = input.map(String::strip).map(String::length);
// 初始状态:length 当前为 input.map(String::strip).map(String::length)。
// 返回:两次 map 依次把 " java " 清理为 "java" 并取长度,length 包含 4。
// 作用:map 在值存在时转换内容,在空结果上短路,并继续返回 Optional。
System.out.println(length.orElse(0));
// 输出:4

Optional.filter:筛选可选值 ​

filter 在谓词不满足时把现有值转为空结果,原本为空时不会调用谓词。

java
Optional<String> input = Optional.of("Java");
// 初始状态:input 当前为 Optional.of("Java")。
Optional<String> result = input.filter(value -> value.length() > 3);
// 初始状态:result 当前为 input.filter(value -> value.length() > 3)。
// 返回:filter 检查 "Java" 的长度 4 大于 3,result 仍包含 "Java"。
// 作用:filter 在谓词不满足时把现有值转为空结果,原本为空时不会调用谓词。
boolean present = result.isPresent();
System.out.println(result.orElse("missing"));
// 输出:Java
System.out.println(present);
// 输出:true

不常用但需要知道 ​

Optional.ifPresent:有值时执行动作 ​

只需要在值存在时执行一次边界动作,就使用 ifPresent。

java
Optional<String> value = Optional.of("Java");
// 初始状态:value 当前为 Optional.of("Java")。
value.ifPresent(System.out::println);
// 关键变化:value.ifPresent(System.out::println) 发现 value="Java",调用 println 输出 Java。
boolean present = value.isPresent();
System.out.println(present);
// 输出:Java

Optional.ifPresentOrElse:按有无执行不同动作 ​

值存在和缺失时都需要明确执行动作,就使用 ifPresentOrElse。

java
Optional<String> value = Optional.empty();
// 初始状态:value 当前为 Optional.empty()。
value.ifPresentOrElse(
        System.out::println,
        () -> System.out.println("missing"));
// 输出:missing
        // 关键变化:value 为空,ifPresentOrElse 选择缺失分支并输出 missing。
        // 作用:值存在和缺失时都需要明确执行动作,就使用 ifPresentOrElse。
System.out.println(value.isEmpty());
// 输出:true

Optional.flatMap:避免嵌套 Optional ​

当映射函数本身返回 Optional 时使用 flatMap;异常边界要有统一策略,不能用空结果掩盖所有输入错误。

java
Optional<String> text = Optional.of("42");
// 初始状态:text 当前为 Optional.of("42")。
Optional<Integer> number = text.flatMap(value -> {
// 返回:flatMap 解析 "42" 为 Integer 42 并保持单层 Optional<Integer>。
// 作用:通过 Optional.flatMap 避免嵌套 Optional。
    try {
        return Optional.of(Integer.parseInt(value));
    } catch (NumberFormatException error) {
        return Optional.empty();
    }
});
System.out.println(number.orElse(0));
// 输出:42

Optional.stream:把可选结果接入 Stream ​

适合把多个可选查询结果汇入流;单个值的简单分支不必为了使用 Stream 而增加复杂度。

java
long count = Stream.of(Optional.of("a"), Optional.<String>empty())
    .flatMap(Optional::stream)
    .count();
    // 初始状态:count 当前保存 Stream.of(Optional.of("a"), Optional.<String>empty()) .flatMap(Optional::stream) .count()的计算结果。
    // 返回:flatMap(Optional::stream) 丢弃空 Optional,只保留 "a",count 为 1。
    // 作用:适合把多个可选查询结果汇入流;单个值的简单分支不必为了使用 Stream 而增加复杂度。
    // 作用:通过 Optional.stream 把可选结果接入 Stream。
System.out.println(count);
// 输出:1

Integer.compare:按有符号整数比较 ​

普通业务整数需要排序或比较时,使用 Integer.compare 按有符号语义判断顺序。

java
int order = Integer.compare(2, 10);
// 输入:order 的初始值为 Integer.compare(2, 10)。
// 返回:Integer.compare(2, 10) 返回负数,因此 order < 0 为 true。
// 关键变化:Integer.compare(2, 10) 比较两个有符号整数后让 order 成为负数。
System.out.println(order < 0);
// 输出:true
System.out.println(order == 0);
// 输出:false

Integer.compareUnsigned:按无符号整数比较 ​

协议序号或位运算场景把 int 当作无符号 32 位值时,使用 compareUnsigned。

java
int allBitsSet = -1;
// 初始状态:allBitsSet 当前为 -1。
int one = 1;
int order = Integer.compareUnsigned(allBitsSet, one);
// 返回:Integer.compareUnsigned(-1, 1) 按无符号值比较 4294967295 与 1,order > 0 为 true。
// 关键变化:Integer.compareUnsigned(allBitsSet, one) 按无符号值比较 -1 与 1,order 变为正数。
System.out.println(order > 0);
// 输出:true
System.out.println(Integer.toUnsignedString(allBitsSet));
// 输出:4294967295

Math 检查与取整:选择更合适的数值页 ​

这里只展示入口;溢出检查、BigInteger、BigDecimal、舍入和金额边界集中在大数与精确计算。

java
// 输入:Math.addExact 检查 20 + 22 的溢出,Math.floorDiv 将 -7 / 3 向负无穷方向取整。
int total = Math.addExact(20, 22);
// 初始状态:total 的初始值为 Math.addExact(20, 22)。
// 返回:Math.addExact(20, 22) 得到 42;溢出时会抛 ArithmeticException。
// 作用:这里只展示入口;溢出检查、BigInteger、BigDecimal、舍入和金额边界集中在[大数与精确计算](/courses/java/02-数组与文本/06-大数与精确计算)。
// 关键变化:Math.addExact(20, 22) 检查加法未溢出并让 total 变为 42。
long page = Math.floorDiv(-7, 3);
// 返回:Math.floorDiv(-7, 3) 向负无穷取整,page 为 -3。
// 关键变化:Math.floorDiv(-7, 3) 向负无穷取整,page 变为 -3。
System.out.println(total + ", " + page);
// 输出:42, -3

继续阅读 ​

简单案例 ​

ArrayUtils.contains:判断数组是否含有元素 ​

Apache Commons Lang 的这个方法支持对象数组,也对 null 数组安全返回 false。

java
import org.apache.commons.lang3.ArrayUtils;
String[] roles = {"admin", "user"};
// 初始状态:roles 当前为 {"admin", "user"}。
System.out.println(ArrayUtils.contains(roles, "admin"));
// 输出:true
// 作用:通过 ArrayUtils.contains 判断数组是否含有元素。
System.out.println(ArrayUtils.contains(roles, "guest"));
// 输出:false

RegExUtils.replaceAll:按正则批量替换 ​

它是空值友好的工具封装;模式参数仍是正则表达式而非普通文本。

java
import org.apache.commons.lang3.RegExUtils;
String masked = RegExUtils.replaceAll("138-0013-8000", "\\d", "*");
// 输入:masked 的初始值为 RegExUtils.replaceAll("138-0013-8000", "\\d", "*")。
// 作用:通过 RegExUtils.replaceAll 按正则批量替换。
System.out.println(masked);
// 输出:***-****-****
System.out.println(RegExUtils.replaceAll(null, "x", "y"));
// 输出:null

Validate.notBlank:拒绝空白参数 ​

它在参数为 null、空串或纯空白时抛出 IllegalArgumentException。

java
import org.apache.commons.lang3.Validate;
String name = Validate.notBlank(" admin ", "name required");
// 输入:name 的初始值为 Validate.notBlank(" admin ", "name required")。
// 作用:通过 Validate.notBlank 拒绝空白参数。
System.out.println(name.trim());
// 输出:admin、name required
try { Validate.notBlank(" ", "name required"); }
// 作用:Validate.notBlank(" ", "name required") 检查到输入只有空格,立即抛出 IllegalArgumentException。
// 异常:输入 " " 触发 Validate.notBlank 的 IllegalArgumentException,异常消息为 name required。
catch (IllegalArgumentException e) { System.out.println(e.getMessage()); }
// 输出:name required

IOUtils.close:兼容旧代码的资源关闭 ​

Commons IO 的旧式关闭辅助应放在 finally;新代码优先使用 try-with-resources。

java
import java.io.StringReader;
import org.apache.commons.io.IOUtils;
var reader = new StringReader("java");
// 初始状态:reader 当前为 new StringReader("java")。
IOUtils.close(reader);
// 关键变化:IOUtils.close(reader) 关闭 StringReader;后续 reader.read() 抛 IOException。
// 作用:通过 IOUtils.close 兼容旧代码的资源关闭。
try { reader.read(); }
catch (Exception e) { System.out.println(e.getClass().getSimpleName()); }
// 输出:IOException
java
import java.math.BigDecimal;
import java.util.Objects;
import java.util.Optional;

public class CommonTypesDemo {
    static final class UserId {
        private final String value;

        UserId(String value) {
            this.value = Objects.requireNonNull(value, "value");
            if (!value.matches("U-\\d+")) {
                throw new IllegalArgumentException("invalid user id");
// 异常:throw new IllegalArgumentException("invalid user id") 立即进入异常路径。
// 初始状态:表达式为 throw new IllegalArgumentException("invalid user id")。
            }
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof UserId id && value.equals(id.value);
        }

        @Override
        public int hashCode() {
            return Objects.hash(value);
        }

        @Override
        public String toString() {
            return "UserId[" + value + "]";
        }
    }

    static Optional<UserId> parse(String text) {
        try {
            return Optional.of(new UserId(text));
        } catch (IllegalArgumentException error) {
            return Optional.empty();
        }
    }

    public static void main(String[] args) {
        UserId first = new UserId("U-42");
// 初始状态:new UserId("U-42") 校验成功,first.value 为 "U-42"。
        UserId same = new UserId("U-42");
// 初始状态:第二个 new UserId("U-42") 产生相同逻辑值,same.value 为 "U-42"。
        BigDecimal total = new BigDecimal("0.10").add(new BigDecimal("0.20"));
// 返回:BigDecimal 加法得到精确金额 total=0.30。
        System.out.println("equal=" + first.equals(same) + ", hash=" + (first.hashCode() == same.hashCode()));
        // 输出:equal=true, hash=true
        System.out.println("total=" + total + ", missing=" + parse("bad").orElse(null));
        // 输出:total=0.30, missing=null
    }
}

案例把包装无关的领域 ID 校验、Objects 前置检查、值对象相等和 Optional 查询结果放在一起;金额细节请查大数专题。

易混点 ​

  • 包装类型的 == 可能受缓存影响;数值内容比较使用 equals 或 compareTo。
  • 自动拆箱 null 会抛 NullPointerException,集合和外部输入边界要先判断。
  • parseInt 返回基本值并可能抛格式异常,valueOf 返回包装对象,二者不能只看方法名字猜语义。
  • Optional.get() 会把缺失问题推迟成异常;优先选择 orElse、orElseGet、orElseThrow 或链式转换。
  • Optional 不替代参数校验、字段建模和集合空值策略;不要为了“看起来函数式”到处嵌套。

课后小问 ​

  1. 为什么 Integer a = 128; Integer b = 128; a == b 不能用来判断数值相等? 答案:包装对象的身份可能来自不同对象,缓存范围也不是业务契约;应使用 a.equals(b) 或 Integer.compare。 解析:自动装箱把基本值包装成对象,== 在两个引用之间比较身份,而不是调用包装类型的数值相等逻辑。

  2. 查询方法返回 Optional<UserId> 时,调用者该如何处理缺失值? 答案:根据业务选择 orElse、orElseGet 或 orElseThrow,明确默认、延迟计算或异常策略。 解析:Optional 的价值是把“可能没有”放到返回类型中,调用者必须作出选择;直接 get() 只是把问题推迟成异常。

本节小结 ​

  • 包装类型适合泛型和可空输入,但自动拆箱、缓存身份和解析异常都有边界。
  • Objects 提供空值安全比较、哈希和前置校验,值对象要保持 equals/hashCode 一致。
  • Optional 表达查询缺失,调用方通过默认、异常或映射链明确处理。
  • 浮点金额和超范围整数应转到 BigDecimal、BigInteger 专题,不要依赖隐式类型转换。

快速回顾 ​

  • 能区分 parseInt、valueOf、自动装箱和自动拆箱。
  • 能用 Objects.equals、hash、requireNonNull 处理常见空值边界。
  • 能为 Optional 返回选择合理的缺失值处理方式。
  • 能判断何时应使用 BigDecimal 或检查溢出的 Math API。