【问题标题】:Java: how to avoid NPE in ternary operator, in general how to perform null-checks elegantly? [duplicate]Java:如何避免三元运算符中的 NPE,通常如何优雅地执行空检查? [复制]
【发布时间】:2021-04-25 13:37:32
【问题描述】:

昨天我不得不写一段丑陋的代码,为了对一个对象的字段执行许多空检查,以避免来自三元运算符构造的 NPE。

有问题的代码:

ResourceThresholds rt = getThresholdsFromConfig();
Thresholds defaultPerContainer = getDefaultThresholds();
    
return new Thresholds((!rt.getCpu().equals("")) ? Long.parseLong(rt.getCpu()) : defaultPerContainer.getCpu(),
           (!rt.getMemory().equals("")) ? Long.parseLong(rt.getMemory())  : defaultPerContainer.getMemory(),/*omitted for brevity*/);

我在 defaultPerContainer.getCpu() 上获得了 NPE,因为字段 cpu = null。 这很好,Java 按照它的工作方式工作。 为什么我不只是默认字段 Long cpu = 0L; ?因为我需要null 值作为我们不设置任何值的指标。

这段特定代码的最终功能变体是:

        Long cpuVal;
        if (!rt.getCpu().equals("")) {
            cpuVal = Long.parseLong(rt.getCpu());
        } else {
            cpuVal = defaultPerContainer.getCpu();
        }
        Long memory;
        if (!rt.getMemory().equals("")) {
            memory = Long.parseLong(rt.getMemory());
        } else {
           memory = defaultPerContainer.getMemory();
        }
        //... many similar if-elses that give me the desired value;
        //which is really ugly, and I believe I am not the only one hitting this.
        return new Thresholds(cpuVal, memory..);

这段代码可以按我的需要工作,但它很丑!

Q1:有人可以提示我是否可以找到使用Optional<T> 的方法来解决带有三元运算符的第一个变体中的 NPE?因为这个 sn-p 有效:!rt.getCpu().equals("")) ? Long.parseLong(rt.getCpu()) : null 即如果我明确地将null 作为一个值,我会在满足条件时得到null

一般来说,有没有优雅的 Java 8+ 方法来处理这个问题?

Q2:您如何优化出色的 if-else 构造以进行空值检查?

【问题讨论】:

  • @DawoodibnKareem 是的
  • @DawoodibnKareem java.lang.Long
  • 好的,我想我知道发生了什么。如果您将?: 之间的部分完全更改为new Long(rt.getCpu()),您是否仍然遇到问题?如果这样可以解决,那是因为三元运算导致“真”和“假”部分都被解释为long 而不是Long
  • 我仍然得到 NPE,逻辑上正确的第三个操作数是 ((Long) defaultPerContainer.getCpu())。一般来说,我认为你不能投空? (defaultPerContainer.getCpu() 返回 null!)

标签: java nullpointerexception null-check


【解决方案1】:
  1. 代码 sn-p 中没有 null 检查。
  2. 最好实现一个简单的实用方法,在设置cpumemory时使用,如果valnull,使用Joda条件"".equals(val)防止NPE
  3. 为避免拆箱,请使用Long.valueOf 而不是返回原始longLong.parseLong
public static Long getValue(String val, Long defaultValue) {
    return "".equals(val) ? defaultValue : Long.valueOf(val);
}

Long cpuVal = getValue(rt.getCpu(), defaultPerContainer.getCpu());
Long memory = getValue(rt.getMemory(), defaultPerContainer.getMemory());

也可以使用参数提供者提供重载实用方法,然后将方法引用传递给它:

public static Long getValue(Supplier<String> str, Supplier<Long> defVal) {
    return getValue(str.get(), defVal.get()); // calling implementation above 
}

Long cpuVal = getValue(rt::getCpu, defaultPerContainer::getCpu);
Long memory = getValue(rt::getMemory, defaultPerContainer::getMemory);

【讨论】:

  • NullPointerExceptiondefaultValue = null。 --- 如果您不解决实际问题,将代码移动到辅助方法不会做任何事情。实际问题见my answer
  • 同意,通过避免拆箱来防止 NPE。希望问题中的defaultPerContainer 不为空。
  • 好奇:Supplier 版本有什么意义?
  • 如果defaultPerContainer 为空,那么使用if 语句的问题中的修复将不起作用,因此情况并非如此。
  • @Andreas,只是一个可以用函数代替值的例子
【解决方案2】:

问题是在三元表达式A ? B : C中,如果BC都是兼容的数值类型,但是一个是boxed object,另一个是原语,大多数人会认为结果是通过自动装箱原语来装箱的。

事实并非如此。相反,三元运算符将对象拆箱,因此它们都是原语,结果是原语。

这意味着以下内容相同:

long B = ...;
Long C = ...;

Long R = ... ? B : C;

Long R = (Long) (... ? B : (long) C);

结果是如果C 为空,则得到NPE。

解决它的一种方法是强制B 自动装箱:

Long R = ... ? (Long) B : C;

通过该更改,一个空的C 值将简单地设置R = null

在问题的情况下,BLong.parseLong(rt.getCpu()),因此不要添加强制转换来强制自动装箱,请使用 long.valueOf(String s)

另外,不相关的,使用isEmpty() 而不是equals(""),并且A 周围不需要括号。

将代码改为:

return new Thresholds(!rt.getCpu().isEmpty() ? Long.valueOf(rt.getCpu()) : defaultPerContainer.getCpu(),
                      !rt.getMemory().isEmpty() ? Long.valueOf(rt.getMemory())  : defaultPerContainer.getMemory(),
                      /*omitted for brevity*/);

【讨论】:

    猜你喜欢
    • 2019-04-12
    • 2019-05-24
    • 2020-02-01
    • 2021-07-11
    • 2010-11-01
    • 1970-01-01
    • 2014-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多