有没有发现一个很奇怪的现象?很多资深程序员写 Java 代码时,几乎很少出现那种我们耳熟能详的判空写法:
if (obj != null) {
obj.doSomething();
}
反而是我们这些刚入门或者中级选手,代码里全是这种“if 不等于 null 就继续”的写法,看起来特别勤奋谨慎,实际上反而显得代码零碎又啰嗦。
这不是你一个人的问题,这是 Java 项目里普遍存在的“判空焦虑症”。
那为啥那些老江湖写代码可以这么潇洒?难道他们心态更强大,不怕空指针报错?当然不是。真相是:他们从设计上就避免了大量判空的场景。而我们呢?啥都不确定,搞得每行代码都像走钢丝,一步不小心就 null。
今天就来聊聊:为啥牛逼的程序员都不怎么写 != null,以及我们应该怎么改。
空指针到底该怎么对待?
Java 的空指针异常(NullPointerException),简直是写 Java 的人挥之不去的梦魇。大部分程序员为了避免 NPE,把代码写得像武侠小说一样,“步步为营、处处设防”,好不容易走到终点,却发现性能也拉了、可读性也糊了,关键 NPE 还没躲掉!
但真高手不是这么写代码的。
我们要先搞清楚一个前提:你的程序到底允不允许返回 null?
这个问题看起来简单,其实是区分新手和老手的关键。
如果你设计的方法是 API 层的一部分,某个参数或者返回值对逻辑执行来说是必须存在的,那你就别返回 null,直接抛异常!没错,你没听错,不要偷偷摸摸返回个 null 等着别人来猜你想干嘛,而是应该光明正大地告诉调用方:“兄弟,你这参数不合格,赶紧改。”
比如你写了一个获取用户信息的方法:
public User getUserById(String id) {
if (id == null) return null; // 新手代码
// 查询数据库逻辑
}
这种代码看起来“温柔体贴”,实际上就像是你去饭店点了一份牛排,结果厨子默默地没做还给你上了个空盘子。高手会直接这么干:
public User getUserById(String id) {
if (id == null) {
throw new IllegalArgumentException("id 不能为空");
}
// 查询数据库逻辑
}
出问题就报错,别憋着。
真的要返回 null 的情况,怎么写才优雅?
有时候,确实是业务允许返回 null,比如你查一个商品库存,发现确实没货,那返回 null 好像也说得过去。
但这里也有个更优雅的写法:空对象模式(Null Object Pattern)。
简单讲,就是你不要真的返回 null,你可以返回一个“空对象”,它啥都不干,但它确实是个对象,这样你就能安全地调用它的方法了。
举个栗子:
public interface Action {
void doSomething();
}
你写了个解析器 Parser,它根据输入返回一个动作 Action:
public Action findAction(String userInput) {
if ("print".equals(userInput)) {
return new PrintAction();
}
return null; // 这是个坑
}
然后你调用:
Action action = parser.findAction(input);
if (action != null) {
action.doSomething();
}
这个判断是不是眼熟?问题是你每次调用都得加这句,烦不烦?
换成空对象模式试试看:
public class NullAction implements Action {
public void doSomething() {
// 啥也不干
}
}
publicclass MyParser implements Parser {
privatestaticfinal Action DO_NOTHING = new NullAction();
public Action findAction(String userInput) {
if ("print".equals(userInput)) {
returnnew PrintAction();
}
return DO_NOTHING;
}
}
再调用时就可以爽快地写:
parser.findAction(input).doSomething();
这就是高手的写法,思路清晰,不怕空,根本不需要判断。
返回集合类型时,千万别返回 null!
这个坑真的是每个 Java 人都踩过一脚。
比如:
public List<String> getUserRoles(String userId) {
if (userId == null) return null;
// 查数据库,如果没有权限,返回 null?
}
你这样写,调用的时候就得小心翼翼:
List<String> roles = getUserRoles(userId);
if (roles != null && !roles.isEmpty()) {
// 有权限逻辑
}
老程序员直接写:
return Collections.emptyList();
然后调用侧就可以放飞自我了:
for (String role : getUserRoles(userId)) {
// 啥也不用判断,开开心心地遍历
}
就问一句:你愿意每次都写一堆判空代码,还是愿意一次性在返回值上处理好问题?
Java 8 的 Optional 怎么看?
这是个挺有争议的东西。
Optional<T> 本质上是个容器,用来优雅地表达“可能有值、可能没值”的语义。
比如:
public Optional<User> findUser(String id) {
return Optional.ofNullable(userRepository.get(id));
}
调用的时候:
findUser(id).ifPresent(user -> user.sayHi());
或者:
User user = findUser(id).orElse(new User("默认用户"));
看起来是不是挺高级?但我跟很多人一样,觉得 Optional 在业务逻辑里用得多了,反而容易让人绕晕,尤其是层层 Optional 嵌套的时候,代码会变得很“奇技淫巧”。
所以我个人建议:**Optional 可以用,但别滥用。**用在一些辅助方法或框架内部还行,核心业务逻辑中,不如直接用空对象或抛异常。
判空的时候,也有技巧
比如说,我们写字符串判断:
if (foo != null && foo.equals("bar")) {
// do something
}
一看就知道是怕 foo 是 null。那高手怎么写?
if ("bar".equals(foo)) {
// 安全又简洁
}
因为 "bar" 是常量,不可能是 null,就能放心大胆地调用它的 equals 方法。这种写法也是我们在项目里常用的“对调 equals”技巧。
写在最后
老程序员不写 != null,不是他们不怕出事,而是他们从根上解决了这些事。
他们通过:
- 提前抛出异常防止脏数据传进来
- 空对象模式让返回值永远不是 null
- 集合类型直接返回 empty list
- 使用 Optional 表达可能缺省的值
- 代码风格上习惯用常量调用 equals
从设计到实现,压根不给 NPE 留下“生根发芽”的机会。你还在满项目地写 if (xxx != null),他们已经在另一种语言模式下自由驰骋了。
所以,写 Java 的我们,其实可以更优雅一点。
空指针报错不怕,怕的是你为了“避免出错”,把代码写得又臭又长。
程序员的成长,从敢于抛异常开始。