【问题标题】:Why is Idea lint warning about missing `isPresent()` check in `orElseGet`?为什么在 `orElseGet` 中缺少有关缺少`isPresent()` 检查的 Idea lint 警告?
【发布时间】:2017-06-20 19:56:01
【问题描述】:

假设我不知道一个Optional 是空的,还是两者都存在。在后一种情况下,我总是更喜欢a 而不是b

final Optional<String> a = Optional.of("1");
final Optional<String> b = Optional.empty();
if (a.isPresent() || b.isPresent()) {
  // prefer a over b
  Integer result = a
      .map(s -> s + "0")
      .map(Integer::parseInt)
      .orElseGet(() -> Integer.parseInt(b.get())); // <-- warning for b.get()
  System.out.println(result);
}

在我的真实代码中,Idea 在这一点上警告我:

'Optional.get()' 没有 'isPresent()' 检查。

这是为什么呢?我会事先检查ab 是否存在。此外,此代码按预期工作,输出为10。如果我输入b = Optional.of("2"),输出仍然是10,因为我更喜欢a。如果我然后输入a = Optional.empty(),则输出如预期的那样是2

是我做错了什么,还是 Idea 的 linter 错了?

【问题讨论】:

  • a.isPresent() || b.isPresent()条件弄糊涂了,想不通这种情况下b一定存在。
  • @LouisWasserman,实际上条件处理得很好。 IDEA 应该知道 !a.isPresent() 意味着 !a.map(anything).isPresent() (它现在不知道)。它还必须查看 lambdas/methodref 并了解它们永远不会返回 null(否则警告是正确的)。分析的太多了,但以后可能会这样做。

标签: java java-8 optional


【解决方案1】:

这里的想法很混乱,它的内部规则可能假设在 same 可选链接中对isPresent 的检查。

由于(a.isPresent() || b.isPresent()),这显然不在Optional 检查本身;由于这些检查,无法判断没有办法在空的可选b 上调用orElseGet...

【讨论】:

  • 我会假设 linter 更智能并且能够检测到这个OR。如果我只检查b.isPresent(),警告就会消失。不过,谢谢您的确认!
  • @Blacklight 正确的做法是为此填写一个错误并查看它们指向什么;那里的开发人员通常反应迅速。您可以通过这种方式改进工具
  • 会的,只是想先检查一下我的想法。
  • 检查这段代码的正确性远比仅仅考虑|| 操作符的逻辑要复杂得多。毕竟,您不是a 上调用orElseGet,而是在map 返回的可选项上调用它,它已在另一个map 调用的结果上调用。为了证明生成的可选项永远不会为空,它必须证明s -&gt; s + "0"Integer::parseInt 都不能评估为null,否则map 将返回一个空的可选项,尽管a非空。
  • 在 Java 9 中,您只需编写 a.map(s -&gt; s + "0").or(() -&gt; b).map(Integer::parseInt) .ifPresent(System.out::println); 而不使用任何 if 语句,就完成了……
【解决方案2】:

长话短说:已在 IDEA 2017.3 中修复(请参阅 IDEA-174759)。

是的,保证b 出现在b.get() 点。是的,IDEA 警告是错误的。原因是当前版本的 IDEA 分析不够复杂,无法理解可选链内部发生的情况。请注意,结果还取决于Integer::parseInt 方法引用永远不会产生null 的事实。否则Optional.map 可能会产生空的可选,即使a 存在。例如,考虑以下代码:

private static Integer myParseInt(String s) {
    try {
        return Integer.parseInt(s);
    } catch (NumberFormatException e) {
        return null;
    }
}

...

if (a.isPresent() || b.isPresent()) {
    // prefer a over b
    Integer result = a.map(s -> s + "0").map(MyClass::myParseInt)
                      .orElseGet(() -> Integer.parseInt(b.get()));
    System.out.println(result);
}

在这种情况下,如果a 包含非数字字符串,b 可能不存在,因此此处需要警告。

我们不断改进 IDEA 静态分析。在 2017.3 中,它将了解这些构造:

正如您现在看到的,警告不会显示在原始代码中,而是显示为可为空的myParseInt

IDEA 2017.3 尚不可用。但是,由于此功能是 IDEA 社区的一部分,您可以使用 master 分支中的GitHub sources 自行构建。

免责声明:我是 IntelliJ IDEA 开发人员,我正在努力解决此问题。

【讨论】:

  • 谢谢Tagir,非常感谢和非常快速的反应。我不确定有多少人会遇到这种情况,但我确实遇到了,很高兴看到 Idea 根据开发人员的反馈不断改进。
【解决方案3】:

在这种情况下,IDEA 的 linting 似乎是错误的,但这可能与决定 b.isPresent() 是否在 a.orElseGet(...) 后备中得到保证的复杂性有关。

一般来说,我发现如果 IDE 警告某事,修复它通常是一个好主意(双关语),因为下一个查看代码的开发人员也将难以破译代码背后的意图。

由于您已经在使用Optional,我会考虑完全不使用isPresent 方法并使用api 为您完成工作。我可以看到两个应该满足警告的选项(双关语)。

如果两者都不存在,您可以提供合适的后备:

final Optional<String> a = Optional.of("1");
final Optional<String> b = Optional.empty();

// prefer a over b
Integer result = a
    .map(s -> s + "0")
    .map(Integer::parseInt)
    .orElseGet(() -> b.map(Integer::parseInt).orElse(0));

System.out.println(result);

如果您不希望两者都为空,则可以抛出 IllegalStateException 而不是回退:

final Optional<String> a = Optional.of("1");
final Optional<String> b = Optional.empty();

// prefer a over b
Integer result = a
    .map(s -> s + "0")
    .map(Integer::parseInt)
    .orElseGet(() -> b.map(Integer::parseInt).orElseThrow(IllegalStateException::new));

System.out.println(result);

【讨论】:

  • 我喜欢你的建议,谢谢。他们确实摆脱了警告。我实际上认为我的初始代码并不太复杂。我现在需要一个 if 子句来检查结果是否为空(整数仅用于示例),而不是在整个代码周围使用 if 子句,或者一个 try/catch 块。而且我没有发现代码更清晰或更易于理解。不过,还是不错的选择,谢谢。
【解决方案4】:

您正在检查 a.isPresent() 和 b.isPresent() 或条件。您会收到警告,因为条件的第一部分可能会变成 true "a.isPresent()" 但 b.isPresent() 是 false 。但是,由于 ||条件块内的代码将开始执行。在这种情况下,b.get 将因“NoSuchElementException”而失败。

尽管根据您的逻辑,如果 b 不存在或为空,则无法访问 b.get(),因为您更喜欢 a 而不是 b。但是 IDE 会主动分析并告诉您所有可能出错的地方或代码中的不良做法。

尝试做类似的事情:

if(a.isPresent()){
//logic if a is there
}
else if(b.isPresent){
//logic if a not there and b is there
}
else{
logic if both are not there
}

【讨论】:

  • 我当然可以这样做只是为了避免 linter 警告,但这并不是重点。问题更多地指向为什么在 if 子句和终端操作都知道时会发出 Idea 警告。
  • 我多次观察到,IDEA 不仅基于当前变量值发出警告,而且有时它会智能地告诉您可能出现的问题。这是两者的结合,因为它知道 b.get() 将无法访问,但它显示错误,因为它知道 b 为空。
猜你喜欢
  • 2013-07-14
  • 2017-04-10
  • 2019-07-09
  • 2012-12-16
  • 2015-11-20
  • 1970-01-01
  • 2013-05-16
  • 2015-10-28
  • 2017-06-28
相关资源
最近更新 更多