#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
usingpreparedStatement.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. После вашего сообщения я обновил все изображения, и это решило проблему. Ваш третий пункт был очень полезен. Спасибо.