null的诅咒:为什么我们一遍又一遍地写同样的代码?
\n\n如果你写Java超过一周,你肯定遇到过NullPointerException。而且,很可能你的第一反应是:“啊哈,只需要检查一下!”。于是,成千米的代码就这样诞生了:
\n\nif (user != null) {\n if (user.getAddress() != null) {\n String city = user.getAddress().getCity();\n if (city != null) {\n System.out.println(city.toUpperCase());\n }\n }\n}\n\n\n这段代码能运行。但它很丑陋。它把阅读逻辑变成了一种折磨,把维护变成了噩梦。Stack Overflow上关于“How do I avoid checking for nulls in Java?”的问题获得了数千次浏览并非偶然。每个Java开发者迟早都会问自己这个问题。
\n\n好消息是:避免null检查不仅可能,而且必要。Java不是JavaScript,我们有工具来编写干净、安全的代码,而无需“防御性”结构。让我们逐一分析它们——从基础工具到高级技术。
\n\n1. Optional<T>——你的主要盟友
\n\n从Java 8开始,我们有Optional。这是一个可以包含值或为空的容器。它的设计初衷就是为了避免null检查,并使代码更具声明性。
它是如何工作的?
\n\n与其从方法返回null,不如返回Optional。与其进行检查,不如使用方法链。
// 糟糕的做法\npublic String getCity(User user) {\n if (user == null) return "Unknown";\n Address address = user.getAddress();\n if (address == null) return "Unknown";\n return address.getCity();\n}\n\n// 好的做法\npublic Optional<String> getCity(User user) {\n return Optional.ofNullable(user)\n .map(User::getAddress)\n .map(Address::getCity);\n}\n\n// 使用\nString city = getCity(someUser).orElse("Unknown");\n\n\n注意:没有一个if。map仅在值不为null时工作。如果在任何阶段遇到null,链式调用就会中断,你会得到Optional.empty()。
Optional的常用方法
\n\n- \n
orElse(T other)——返回值或备用值 \n orElseGet(Supplier<? extends T> other)——惰性版本,仅在需要时计算 \n orElseThrow(Supplier<? extends X> exceptionSupplier)——抛出你自己的异常 \n ifPresent(Consumer<? super T> consumer)——如果值存在则执行操作 \n filter(Predicate<? super T> predicate)——根据条件过滤 \n
// 带过滤器的示例\nOptional<String> name = Optional.of("Java");\nname.filter(n -> n.startsWith("J"))\n .ifPresent(System.out::println); // 输出 "Java"\n\n\n重要提示:Optional用于返回值,而不是用于类的字段或方法的参数。不要滥用。
2. Objects.requireNonNull()——用于显式约定的实用工具
\n\n有时null是一个错误,需要尽早发现。与其手动编写检查,不如使用java.util.Objects.requireNonNull()。
public class UserService {\n private final UserRepository repository;\n\n public UserService(UserRepository repository) {\n this.repository = Objects.requireNonNull(repository, \n "UserRepository must not be null");\n }\n\n public User findById(Long id) {\n Objects.requireNonNull(id, "ID must not be null");\n return repository.findById(id);\n }\n}\n\n\n这种方法被称为