Java中如何避免空值检查
技术背景
在Java开发中,初级到中级开发者常常会面临空值检查的问题。他们可能不了解或不信任所参与的契约,从而过度进行空值检查。此外,在编写自己的代码时,也倾向于返回空值来表示某些情况,这就要求调用者进行空值检查。空值检查会使代码变得冗长且难以维护,因此需要寻找有效的方法来避免不必要的空值检查。
实现步骤
当空值不是有效响应时
从Java 1.7开始,可以使用Objects.requireNonNull(foo)
方法。该方法会返回传入的对象,如果对象为空则抛出NullPointerException
。示例代码如下:
1 2 3
| public Foo(Bar bar) { this.bar = Objects.requireNonNull(bar); }
|
也可以像使用断言一样使用该方法,并添加自定义的异常信息:
1 2
| Objects.requireNonNull(someobject, "if someobject is null then something is wrong"); someobject.doCalc();
|
在Java 1.7之前,可以使用assert
语句作为替代方案。
当空值是有效响应时
- 对于返回集合的方法:尽量返回空集合(或数组)而不是空值。
- 对于非集合类型:可以使用Null Object模式。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public interface Action { void doSomething(); }
public interface Parser { Action findAction(String userInput); }
public class MyParser implements Parser { private static Action DO_NOTHING = new Action() { public void doSomething() { } };
public Action findAction(String userInput) { if ( ) { return DO_NOTHING; } } }
|
使用注解
在一些Java IDE(如JetBrains IntelliJ IDEA、Eclipse、Netbeans)或工具(如findbugs)中,可以使用@Nullable
和@NotNull
注解。示例如下:
1 2 3 4 5 6 7
| @NotNull public static String helloWorld() { return "Hello World"; }
@Nullable public static String helloWorld() { return "Hello World"; }
|
使用Java 8的Optional类
Java 8引入了java.util.Optional
类,它可以提高代码的可读性,并使公共API的契约更加清晰。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static Optional<Fruit> find(String name, List<Fruit> fruits) { for (Fruit fruit : fruits) { if (fruit.getName().equals(name)) { return Optional.of(fruit); } } return Optional.empty(); }
Optional<Fruit> found = find("lemon", fruits); if (found.isPresent()) { Fruit fruit = found.get(); String name = fruit.getName(); }
|
还可以使用map
和orElse
方法:
1 2 3
| String nameOrFallback = find("lemon", fruits) .map(f -> f.getName()) .orElse("empty-name");
|
核心代码
使用Objects.requireNonNull方法
1 2 3 4 5 6 7
| public class Example { private Object obj;
public Example(Object obj) { this.obj = Objects.requireNonNull(obj, "obj cannot be null"); } }
|
使用Null Object模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public interface Action { void doSomething(); }
public interface Parser { Action findAction(String userInput); }
public class MyParser implements Parser { private static Action DO_NOTHING = new Action() { @Override public void doSomething() { } };
@Override public Action findAction(String userInput) { if (userInput == null || userInput.isEmpty()) { return DO_NOTHING; } return null; } }
|
使用Java 8的Optional类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import java.util.ArrayList; import java.util.List; import java.util.Optional;
class Fruit { private String name;
public Fruit(String name) { this.name = name; }
public String getName() { return name; } }
public class OptionalExample { public static Optional<Fruit> find(String name, List<Fruit> fruits) { for (Fruit fruit : fruits) { if (fruit.getName().equals(name)) { return Optional.of(fruit); } } return Optional.empty(); }
public static void main(String[] args) { List<Fruit> fruits = new ArrayList<>(); fruits.add(new Fruit("apple")); fruits.add(new Fruit("banana"));
Optional<Fruit> found = find("apple", fruits); String name = found.map(Fruit::getName).orElse("not found"); System.out.println(name); } }
|
最佳实践
- 明确契约:在设计API时,明确指出哪些方法可以返回空值,哪些不可以。可以使用注解或文档来进行说明。
- 使用Optional类:对于可能返回空值的方法,优先考虑使用
Optional
类。它可以使代码更加清晰,减少空值检查的代码量。 - 使用Null Object模式:在适当的情况下,使用Null Object模式来避免空值检查。例如,在处理找不到合适操作的情况时,可以返回一个不执行任何操作的对象。
- 尽早失败:如果一个方法不允许传入空值,应该在方法开始时就进行检查,并抛出明确的异常,而不是在后续的代码中出现
NullPointerException
。
常见问题
注解不被所有编译器支持
虽然一些IDE(如IntelliJ IDEA)支持@Nullable
和@NotNull
注解,但并不是所有的编译器都支持。因此,在使用这些注解时,需要注意代码的可移植性。
使用Optional类可能会增加代码复杂度
虽然Optional
类可以提高代码的可读性,但在某些情况下,使用它可能会增加代码的复杂度。例如,在嵌套的Optional
对象中,处理起来可能会比较麻烦。
Null Object模式的维护问题
使用Null Object模式时,需要创建额外的对象,这可能会增加代码的维护成本。因此,在使用该模式时,需要权衡利弊。