Java: как избежать NPE в тернарном операторе, вообще как элегантно выполнять нулевые проверки?

Вчера мне пришлось написать уродливый кусок кода, чтобы выполнить множество нулевых проверок полей объекта, чтобы избежать 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*/);

Я получаю NPE на defaultPerContainer.getCpu(), потому что поле 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 в первом варианте с тернарным оператором? Потому что этот фрагмент работает: !rt.getCpu().equals("")) ? Long.parseLong(rt.getCpu()) : null т.е. если я явно поставил null в качестве значения, я получаю null при выполнении условия.

В общем, есть ли какой-нибудь элегантный способ Java 8+ справиться с этим?

Вопрос 2: как оптимизировать великолепную конструкцию if-else для проверки на null?


person gai-jin    schedule 21.01.2021    source источник
comment
@DawoodibnKareem да   -  person gai-jin    schedule 21.01.2021
comment
@DawoodibnKareem java.lang.Long   -  person gai-jin    schedule 21.01.2021
comment
Хорошо, я думаю, что знаю, что происходит. У вас все еще возникает проблема, если вы вообще меняете часть между ? и : на new Long(rt.getCpu())? Если это исправит ситуацию, то это потому, что тернарная операция приводит к тому, что и истинная, и ложная части интерпретируются как long вместо Long.   -  person Dawood ibn Kareem    schedule 21.01.2021
comment
Я все еще получаю NPE, логически правильным третьим операндом будет ((Long) defaultPerContainer.getCpu()). Я вообще думаю, что null нельзя бросать? ( defaultPerContainer.getCpu() возвращает null!)   -  person gai-jin    schedule 21.01.2021


Ответы (2)


Проблема заключается в том, что в троичном выражении A ? B : C, если B и C оба являются совместимыми числовыми типами, но один из них является упакованным объектом, а другой является примитивом, большинство людей думаю, что результат упакован, автоматически упаковав примитив.

Это не относится к делу. Вместо этого тернарный оператор распаковывает объект, поэтому они оба являются примитивами, а результат — примитивом.

Это означает, что следующие одинаковы:

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.

В случае, указанном в вопросе, B равно Long.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*/);
person Andreas    schedule 21.01.2021

  1. Во фрагменте кода нет проверок null.
  2. Лучше реализовать простой служебный метод и использовать его при настройке cpu и memory, а также использовать условие Joda "".equals(val) для предотвращения NPE, если val равно null
  3. Чтобы избежать распаковки, используйте Long.valueOf вместо Long.parseLong, который возвращает примитив long:
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);
person Alex Rudenko    schedule 21.01.2021
comment
NullPointerException когда defaultValue = null. --- Перемещение кода во вспомогательный метод ничего не даст, если вы не устраните реальную проблему. См. мой ответ для реальной проблемы. - person Andreas; 21.01.2021
comment
Согласитесь, предотвратил NPE, избегая распаковки. Надеюсь, defaultPerContainer в вопросе не равно нулю. - person Alex Rudenko; 21.01.2021
comment
Любопытно: Какой смысл в Supplier версии? - person Andreas; 21.01.2021
comment
Если defaultPerContainer равно нулю, то исправление в вопросе с использованием операторов if не сработает, так что это не так. - person Andreas; 21.01.2021
comment
@Andreas, просто пример того, что вместо значения можно использовать функцию - person Alex Rudenko; 21.01.2021