Проблема с поведением Objects.NonNull()

#java

#java

Вопрос:

Использование Java 11. Замечаю странное поведение с очень простой функциональностью. В следующем коде, если дата истечения срока действия не равна нулю, только тогда он должен попытаться извлечь sql.Timestamp из данного Instant поля.

 preparedStatement.setTimestamp(expirationDateParameterIndex,
                            Objects.nonNull(memberReward.getExpirationDate())
                            ? Timestamp.from(memberReward.getExpirationDate())
                            : null);
  

Проблема в том, что, несмотря на то, что дата истечения срока действия установлена null , Timestamp.from(..) вызывается и выбрасывается NullPointerException .

 java.lang.NullPointerException: null
at java.sql/java.sql.Timestamp.from(Timestamp.java:545)
  

Проблема не воспроизводима за пределами проекта.

Вот скриншоты отладки: введите описание изображения здесь


введите описание изображения здесь


введите описание изображения здесь

Комментарии:

1. Можете ли вы попытаться извлечь значение, возвращаемое by memberReward.getExpirationDate() , в локальную переменную?

2. Почему вы бы использовали Objects.nonNull в этом случае вместо !=null ?

3. @Turing85 Objects::nonNull может быть полезен в качестве предиката (но не в этом случае).

4. Я рекомендую вам не использовать Timestamp . Этот класс плохо разработан и давно устарел. Вместо этого, начиная с JDBC 4.2, в ненулевом случае попробуйте memberReward.getExpirationDate().atOffset(ZoneOffset.UTC) передать результат OffsetDateTime using preparedStatement.setObject() . OffsetDateTime это класс из java.time, современного Java date и time API .

5. @OleV.V. Спасибо за рекомендацию и за то, что поделились ссылками. Я собираюсь прочитать об этом и внести необходимые изменения в код.

Ответ №1:

Ну, ваш отладочный скриншот понятен: null был передан Timestamp.from() , что означает, что равно memberReward.getExpirationDate() нулю.

Это странно, потому что прямо перед этим вы проверяете это условие! Таким образом, мы переходим к этим объяснениям, все довольно экзотические:

  • getExpirationDate() нестабилен: каждый раз возвращает разные значения. Представьте, что это было реализовано следующим образом: return random.coinFlip() == HEADS ? null : someDateObj; — тогда это может произойти. Один из способов исправить это — вызвать его один раз, сохранить в локальной переменной и продолжить с этим.
  • ExpirationDate не является неизменяемым, и какой-то другой поток устанавливает значение между вашей проверкой и вашим чтением. Это маловероятно, но технически возможно, и предполагает, что вам нужно серьезно переписать все это, наличие такого случайного общего изменяемого состояния между потоками означает, что это всего лишь одно из множества условий гонки.
  • Код, который вы видите, — это не тот код, который выполняется.
  • Вы получаете Timestamp.from из другого кода, отличного от того, который вы вставили.
  • То есть не объекты java.util , а метод NonNull для него сломан.

Все это звучит экзотично, но это должно быть одной из этих вещей.

Комментарии:

1. Третье случается чаще, чем можно было бы подумать.

2. Извините, что трачу здесь время каждого. Приложение было запущено в Docker. После вашего сообщения я обновил все изображения, и это решило проблему. Ваш третий пункт был очень полезен. Спасибо.