这是一个 Java 快速入门项目,非常适合用来练手。相关源码已上传至 GitHub,👉 点击查看 GitHub 仓库。欢迎交流指正、提交 issue。
null 是 Java 中表示引用类型变量不指向任何对象的特殊字面量。
一、基本特性
- 只能赋给引用类型(对象、数组、String),不能赋给基本类型
- 类中未初始化的引用类型字段默认值为
null - 可转型为任何引用类型:
(String) null
二、常见错误:NullPointerException
java
String s = null;
s.length(); // ❌ NPE
只要引用为 null 时调用 . 方法,就会触发 NPE------Java 中最常见的运行时异常。
三、安全处理 null 的 4 种方式
java
// 1. 显式判空
if (str != null) System.out.println(str.length());
// 2. 三元运算符
int len = (str == null) ? 0 : str.length();
// 3. Objects 工具类(Java 7+)
int len = Objects.requireNonNull(str, "str 不能为 null").length();
String def = Objects.toString(str, "默认值");
// 4. Optional(Java 8+)
int len = Optional.ofNullable(str).map(String::length).orElse(0);
三、常见陷阱
java
// 陷阱1:equals() 调用顺序
String s = null;
s.equals("abc"); // ❌ NPE
"abc".equals(s); // ✅ 推荐
// 陷阱2:包装类型自动拆箱
Integer num = null;
int n = num; // ❌ NPE
// 陷阱3:数组元素默认值
String[] strs = new String[3]; // 默认 null
strs[0].length(); // ❌ NPE
四、实战排查案例:多层嵌套对象的 NPE 排查
假设有一个用户信息查询场景,需要获取用户所在城市的名称,代码中出现了多层链式调用:
java
public class User {
private Profile profile;
public Profile getProfile() { return profile; }
}
public class Profile {
private Address address;
public Address getAddress() { return address; }
}
public class Address {
private String city;
public String getCity() { return city; }
}
public class UserService {
public String getUserCity(User user) {
// 问题代码:多层链式调用,任何一层为 null 都会触发 NPE
return user.getProfile().getAddress().getCity();
}
}
4.1 排查思路
- 定位异常栈 :从日志中捕获的异常栈可以看到
NullPointerException发生在UserService.getUserCity()的第 3 行,指向user.getProfile().getAddress().getCity()。 - 逐层断点调试 :在调用链的每一层设置断点,分别检查
user、user.getProfile()、user.getProfile().getAddress()是否为null。 - 日志输出辅助:在调用前打印各层对象状态:
java
public String getUserCity(User user) {
System.out.println("user = " + user);
if (user != null) {
System.out.println("profile = " + user.getProfile());
if (user.getProfile() != null) {
System.out.println("address = " + user.getProfile().getAddress());
}
}
return user.getProfile().getAddress().getCity();
}
假设日志输出为:
ini
user = com.example.User@123456
profile = null
则说明 user 对象存在,但其 profile 字段未初始化,导致后续调用 getAddress() 时触发 NPE。
4.2 防御性编程建议
- 逐层判空 :最直接的方式,在每一层调用前检查是否为
null:
java
public String getUserCity(User user) {
if (user == null) return "未知城市";
Profile profile = user.getProfile();
if (profile == null) return "未知城市";
Address address = profile.getAddress();
if (address == null) return "未知城市";
return address.getCity();
}
- 使用 Optional(Java 8+):
java
public String getUserCity(User user) {
return Optional.ofNullable(user)
.map(User::getProfile)
.map(Profile::getAddress)
.map(Address::getCity)
.orElse("未知城市");
}
-
使用工具类方法 (如 Apache Commons Lang 的
ObjectUtils.defaultIfNull或自定义工具类)封装判空逻辑,避免重复代码。 -
设计层面预防 :在构造函数或工厂方法中确保
Profile、Address等嵌套对象被正确初始化,或使用构建器模式强制要求必要字段非空。
五、小结
| 要点 | 说明 |
|---|---|
| 本质 | 引用不指向任何对象 |
| 避免 NPE | 访问前判空,多用 Optional 和 Objects |
| 方法调用 | "常量".equals(var) 优于 var.equals("常量") |
| 自动拆箱 | 包装类型为 null 时赋值给基本类型会抛 NPE |
六、常见面试题
1. 如何有效避免 NullPointerException?
- 防御性检查 :在访问对象方法或属性前显式判空(
if (obj != null))。 - 使用工具类 :
Objects.requireNonNull()快速校验参数,StringUtils.isEmpty()等工具方法简化判空。 - 合理使用 Optional :用
Optional.ofNullable()包装可能为 null 的返回值,配合orElse()/orElseGet()提供默认值。 - 避免自动拆箱 :包装类型赋值给基本类型前确认非 null,如
Integer count = null; int c = count;会抛 NPE。
2. Optional 是解决 NPE 的银弹吗?
不是。Optional 主要用于返回值场景,提醒调用者处理空值,但不应滥用:
- 不应用于字段、方法参数或集合中。
- 过度使用
Optional.get()而不判isPresent()反而更危险。 - 核心仍是培养编码习惯:尽早校验、明确契约、善用工具类。
3. == 判空与 equals() 判空有何区别?
==用于判断引用是否为null,是安全的,不会抛 NPE。equals()是实例方法,若调用对象为null会直接抛 NPE,因此推荐用"常量".equals(var)或Objects.equals(var1, var2)来避免。