NPE в Java HashSet.isEmpty () - PullRequest
       11

NPE в Java HashSet.isEmpty ()

0 голосов
/ 26 сентября 2018

Я получил NPE в java.util.HashSet.isEmpty () в строке 191. Думаю, эта строка просто вызывает isEmpty () во внутреннем поле карты.

К моему удивлению, это поле является переходным, поэтому после десериализации оно будет нулевым.Но разве это не цель десериализации набора, чтобы вернуть значения?На мой взгляд, десериализация является единственным способом, которым это поле может быть нулевым для экземпляра HashSet.

Возможно, я что-то здесь упускаю.Может кто-нибудь объяснить?

Java 1.8.0_151 в Linux (3.13.0-61-generic) на платформе amd64

Вот реализация HashMap.isEmpty из JDK:

/**
 * Returns <tt>true</tt> if this set contains no elements.
 *
 * @return <tt>true</tt> if this set contains no elements
 */
public boolean isEmpty() {
    return map.isEmpty(); // line 191
}

Изменить / дополнительная информация:

  • К сожалению, я не могу привести минимальный пример, демонстрирующий проблему.Это происходит во время регрессионного теста сложной системы, работающей часами.Итак, я не знаю и не контролирую, что происходит с этим набором.Кроме того, проблема является недетерминированной, то есть мы видим ее время от времени, но не каждый раз
  • Я мог бы предоставить фрагмент нашего кода, который вызывает isEmpty().Но NPE встречается в библиотеке, так что этот метод явно вызывался в существующем экземпляре, т. Е. Не в null.Следовательно, предоставление этого фрагмента не очень поможет
  • , наш код является многопоточным.Но на первый взгляд эта часть имеет только однопотоковый доступ
  • , наш код ловит и проглатывает исключения во многих местах.Это может скрыть настоящего виновника

1 Ответ

0 голосов
/ 26 сентября 2018

Поскольку поле map не равно final, нет гарантии, что оно будет отображаться в его инициализированном состоянии при неправильной публикации недавно созданного экземпляра HashSet в многопоточном коде.Принимая во внимание, что «вновь созданный» означает, что никаких других действий, обеспечивающих видимость памяти между вовлеченными потоками, не происходило с момента создания, что в принципе может быть произвольным длительным временем.

Во время десериализации объекта метод readObject заботитсяповторной инициализации поля map и возврата элементов.Это необходимо, чтобы скрыть эти детали реализации от постоянной формы.Кроме того, десериализованные объекты могут иметь другой хеш-код, чем при сериализации (например, взять хеш-код, унаследованный от java.lang.Object).Таким образом, они должны быть вставлены в любом случае.Это общий принцип большинства коллекций - скрывать детали реализации и просто сериализовать и десериализовать каждый содержащийся элемент один за другим в выделенных методах writeObject и readObject.

Так что будущая реализация HashSet может бытьреальный хэш, без использования HashMap за кулисами, без ущерба для совместимости сериализации.

...