空指针防御编程
深入 NPE 成因、Optional 类、防御性编程技巧
空指针防御编程
NullPointerException 是 Java 最常见的异常——但也是最容易预防的。写出不会 NPE 的代码是一种思维习惯,而不是靠事后修 bug。
学完本章你将: 识别 NPE 的常见场景、用 Optional 优雅处理 null、建立防御策略。
NPE 的四种典型场景
java
// 1. 调用 null 的方法 —— 最经典
String s = null;
s.length(); // NPE
// 2. 自动拆箱 null —— 最隐蔽
Integer count = null;
int n = count; // NPE!Integer → int 自动拆箱时调用 count.intValue()
// 3. 数组元素默认是 null
String[] names = new String[5]; // 5 个都是 null
names[0].toUpperCase(); // NPE
// 4. Map.get() 返回 null
Map<String, String> map = new HashMap<>();
String value = map.get("不存在的key"); // null
value.length(); // NPE
💡 核心原则:如果你拿不准一个值是不是 null,在用它之前先检查。
Optional —— 包装"可能存在也可能不存在"的值
Optional 是一个容器:要么装着一个值,要么空着。它强迫你思考"如果值是 null 怎么办",而不是忘记检查。
java
// ❌ 直接返回 null —— 调用者容易忘记检查
public User findUser(String id) { return null; }
// ✅ 返回 Optional —— 调用者必须处理"不存在"的情况
public Optional<User> findUser(String id) {
User user = db.query(id);
return Optional.ofNullable(user);
}
java
Optional<String> opt = findName(42); // 可能是空
// 有值时执行
opt.ifPresent(name -> System.out.println(name));
// 没值时给默认值
String name = opt.orElse("默认名称");
String name2 = opt.orElseGet(() -> loadFromCache()); // 延迟计算
// 没值时抛异常
String name3 = opt.orElseThrow(() ->
new NotFoundException("用户不存在"));
// 链式处理
Optional<String> upper = opt
.filter(s -> s.length() > 3) // 长度不够 → 变空
.map(String::toUpperCase); // 有大写 → 转大写
⚠️ 不要滥用 Optional: 不要用它做字段类型或方法参数(序列化麻烦)。它最适合做方法返回值。
防御策略清单
java
// ✅ 参数校验 —— 第一时间发现 null
public void process(String input) {
Objects.requireNonNull(input, "input 不能为空");
}
// ✅ 返回空集合而不是 null
public List<String> getNames() {
return names != null ? names : Collections.emptyList();
// 调用者可以放心遍历,不用判空
}
// ✅ 使用注解表明 null 契约
public @Nullable User findByEmail(String email) { } // 可能返回 null
public void update(@NotNull User user) { } // 不接受 null
// ✅ 用 Objects.equals 替代 .equals()
Objects.equals(a, b); // null 安全,不用写 a != null && a.equals(b)
// ✅ 级联访问用 Optional 链
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知");
// 清晰的 null 契约
}
// ✅ 方法链保护 String city = user.getAddress() != null ? user.getAddress().getCity() : "未知"; // 或 String city = Optional.ofNullable(user) .map(User::getAddress) .map(Address::getCity) .orElse("未知");