发布于 2026-01-06 0 阅读
0

如何用 Java 让审稿人哭泣(可选)

如何用 Java 让审稿人哭泣(可选)

我认为代码审查是最好的灵感来源之一。我把它看作是一个学习新知识的机会,可以从其他提交 pull/merge 请求的软件开发人员那里获得启发。此外,有时你可能需要更深入地研究某个特定主题,特别是与被审查的代码相关的细节。通常,这个过程会让你对该领域有更深刻的理解。然而,代码审查还有另一个事实:一段时间后,你会遇到一些常见的错误。最近,我审查了一个功能,发现其中存在一个Optional 类型的陷阱。说实话,我在不同的合并请求中多次看到过这个问题,这些请求是由不同的开发人员(从新手到经验丰富的开发人员)提交的。

我知道网上有很多关于 Java Optional 的文章,其中一些非常有用,比如Anghel Leonard ( 《Java 编码问题》的作者)写的这篇文章(尽管我对文中提到的一些情况持保留意见) 。我不想重复那些你只需谷歌一下就能找到的内容。我打算谈谈使用Optional时一些最常见但又最少被提及的陷阱

代码审查图像

什么是可选的?

在深入探讨主题之前,让我们先回顾一些定义,了解一下Optional是什么。作为一名 Java 开发人员,您应该遇到过臭名昭著的NullPointerException,它会在您访问引用时抛出。只需简单搜索一下,您就能找到成千上万个关于 Java(以及其他语言)中空引用的梗图和笑话。OptionalJava 8 中引入,旨在帮助程序员摆脱引用带来的所有问题。您可以阅读Oracle 技术资源中的这篇文章,了解更多关于Java 中引入Optional 的原因。

现在我们将查看 Optional 的JavaDoc并检查提供的定义。

一个容器对象,其中可能包含非空值,也可能不包含非空值。

继以上介绍之后,作为 API 说明,这里回答一下大家可能会想到的第一个问题:“什么时候应该使用Optional?”

Optional 类型主要用于方法返回值,用于明确表示“无结果”,且使用 null 值很可能导致错误的情况。类型为 Optional 的变量本身永远不应该为 null;它应该始终指向一个 Optional 实例。

因此,我们可以从JavaDoc描述中提取以下几点

  • Optional 是一个容器对象。
  • 它可能不包含非空值。
  • 它主要用于在使用null值会带来麻烦时作为返回类型
  • Optional 类型的变量不应该为

牢记这些要点,你就能轻松发现大多数与Optional相关的代码异味。现在,让我们来看看四个最常见的Optional陷阱。

传统的异常处理

我在代码审查中经常遇到的一个错误是使用 if/else 语句块来处理Optional变量不包含非空值的情况(如前面Optional
定义中所述)。请看这里:

Optional<User> userOptional = userDao.findByCellNumber(cellNumber);

if (!userOptional.isPresent()) {
    throw new UserNotFoundException("...");
} else {
    User user = userOptional.get();
    //...
}
Enter fullscreen mode Exit fullscreen mode

假设你想通过数据库中特定的单元格编号查找用户。你会从相应的 DAO 类中收到一个可选变量。现在,你想检查是否存在具有你提供的单元格编号的用户。上面的代码有什么问题?它不起作用吗?说实话,它运行得非常流畅。但是,它不够优雅;这就像用扳手拧木螺丝一样。现在,请看下面的代码片段:

User user = userDao.findByCellNumber(cellNumber).orElseThrow(UserNotFoundException::new);
Enter fullscreen mode Exit fullscreen mode

使用orElseThrow,您可以更简洁优雅地完成同样的操作,而无需使用get方法。
您还可以使用Java 10 中引入的orElseThrow无参版本。

orElse 和 orElseGet 滥用

第二个常见的陷阱可能出现在使用orElseorElseGet时。这些方法看似功能相同,但实际上存在一个显著的区别,虽然不像前一个方法那样显而易见。让我们来看看它们的签名和对应的 Javadoc:

public T orElse(T other)
//If a value is present, returns the value, otherwise returns other.
//Parameters:
//other - the value to be returned, if no value is present. May be null.
//Returns:
//the value, if present, otherwise other
Enter fullscreen mode Exit fullscreen mode
public T orElseGet(Supplier<? extends T> supplier)
//If a value is present, returns the value, otherwise returns the result produced by the supplying function.
//Parameters:
//supplier - the supplying function that produces a value to be returned
//Returns:
//the value, if present, otherwise the result produced by the supplying function
//Throws:
//NullPointerException - if no value is present and the supplying function is null
Enter fullscreen mode Exit fullscreen mode

关于上述签名,第一个区别显而易见:orElse接受一个泛型对象作为参数;而orElseGet接受一个生成值的提供者函数。那又怎样呢?你说得对,这看起来似乎微不足道。然而,事情并非如此简单。让我们来看下面的示例:

Optional<Integer> number = Optional.of(10);
System.out.println("The number is: " + number.orElseGet(() -> alternativeNumber()));

private static Integer alternativeNumber() {
    int number = 747;
    System.out.println("The alternative number is: " + number);
    return number;
}
Enter fullscreen mode Exit fullscreen mode

上述代码片段的输出是什么?如您所见,number 是一个可选变量,初始值为 10。因此,我们并不期望orElseGet执行任何操作。以下是输出结果:

The number is: 10
Enter fullscreen mode Exit fullscreen mode

很简单,对吧?现在看看这个:

Optional<Integer> number = Optional.of(10);
System.out.println("The number is: " + number.orElse(alternativeNumber()));

private static Integer alternativeNumber() {
    int number = 747;
    System.out.println("The alternative number is: " + number);
    return number;
}
Enter fullscreen mode Exit fullscreen mode

除了第二行使用了`orElse`而不是`orElseGet`之外,其他所有代码都与上一个例子相同。我们暂时不期望 ` orElse`执行任何操作,因为我们的数字已经有了值。到此为止,猜猜输出结果!和上一个例子一样吗?不!输出结果是:

The alternative number is: 747
The number is: 10
Enter fullscreen mode Exit fullscreen mode

orElse参数始终会被求值,即使Optional变量包含值也是如此。然而,传递给orElseGet 的supplier 方法仅在Optional变量为空时才会被求值。这就是第二个输出不同的原因。请注意,如果您使用orElse并向其传递一个复杂的方法,这可能会严重影响性能。

使用 orElse(null)

我在代码审查中经常遇到的另一个常见错误有点哲学意味,那就是使用`orElse(null)`。这样做有什么问题呢?为了找到答案,我们先来回顾一下 `Optional` 存在的初衷。正如我之前提到的,`Optional` 的存在是为了帮助我们避免引用导致的诸如`NullPointerException`之类的问题。我们在上一节也讨论过`orElse`方法。现在,把这两部分结合起来。如果`Optional`对象为空且不包含任何值,那么要求它返回`null`真的合理吗?

orElse方法是在Optional对象为空时返回替代值的正确方法。返回null会让人质疑Optional的用法。你可能更倾向于使用引用而不是Optional;这当然可以,但我并不推荐这样做。然而,将这两种方法混用会产生误导。乍一看,代码似乎是安全的;但是,你迟早会遇到空引用问题。

在返回类型为 Optional 的方法中返回 null

在描述最后一个也是最奇怪的例子之前,我们先来看看它是如何发生的。让我们回到之前的例子。假设findByCellNumber函数是这样的:

public Optional<User> findByCellNumber(String cellNumber) {
    //some code here
    if (someCondition) {
        return null;
    }
    //some code here
}
Enter fullscreen mode Exit fullscreen mode

如您所见,此方法在特定情况下会返回null。假设数据库宕机,作者处理了异常并在这种情况下返回null(这是不良做法,请勿在家尝试)。因此,在某些情况下,当您尝试获取可选值时,会遇到NullPointerException。请看这里:

User user = userDao.findByCellNumber(cellNumber).orElseThrow(UserNotFoundException::new);
Enter fullscreen mode Exit fullscreen mode

我们期望该方法返回一个Optional 类型,然后我们使用orElseThrow;然而,它返回的是null。因此,我们的 try 语句导致了异常。如何避免这个问题?关键在于:如前所述,Optional类型绝对不能为null

快速回顾

  • 每次显式调用可选变量的get方法都可能是一种代码异味。
  • orElseorElseGet并不等价。
  • Java 引入Optional 类型是为了帮助开发者避免引用。因此,在代码中使用Optional变量时,任何返回 null或使用orElse(null) 的语句都是一种不好的代码习惯。

牢记这些要点,您在使用Optional时可以避免大多数常见陷阱。

文章来源:https://dev.to/dante0747/how-to-make-your-reviewer-cry-using-java-optional-5go3