Понимание этого предупреждения: Сериализуемый класс не объявляет статический финальный serialVersionUID

У меня есть статический код инициализатора:

someMethodThatTakesAHashMap(new HashMap<K, V>() {
{
  put("a","value-a"); 
  put("c","value-c");}
});

По какой-то причине я получаю предупреждение от Eclipse: сериализуемый класс не объявляет статический финальный serialVersionUID.

Это жалуется на анонимный класс? Что я могу сделать с этим, или я должен просто подавить это.

6 ответов

Решение

Синтаксис, который вы используете, называется двойной инициализацией - это фактически " блок инициализации экземпляра, который является частью анонимного внутреннего класса " (конечно, не хак). Таким образом, при использовании этой нотации вы фактически определяете новый класс (!).

"Проблема" в вашем случае заключается в том, что HashMap инвентарь Serializable, Этот интерфейс не имеет никаких методов и служит только для определения семантики сериализации. Другими словами, это интерфейс маркера, и вам конкретно не нужно ничего реализовывать. Но во время десериализации Java использует номер версии, называемый serialVersionUID чтобы убедиться, что сериализованная версия совместима с целью. Если вы не предоставите это serialVersionUID, будет рассчитано. И, как задокументировано в Javadoc Serializable вычисленное значение является чрезвычайно чувствительным, и поэтому рекомендуется явно объявить его, чтобы избежать каких-либо проблем десериализации. И это то, на что "Затмение" "Затмение" (обратите внимание, что это всего лишь предупреждение).

Таким образом, чтобы избежать этого предупреждения, вы можете добавить serialVersionUID к вашему анонимному внутреннему классу:

someMethodThatTakesAHashMap(new HashMap<String, String>() {
    private static final long serialVersionUID = -1113582265865921787L;

    {
        put("a", "value-a");
        put("c", "value-c");
    }
});

Но вы теряете краткость синтаксиса (и он может вам даже не понадобиться).

Таким образом, другим вариантом будет игнорирование предупреждения путем добавления @SuppressWarnings("serial") к методу, по которому вы звоните someMethodThatTakesAHashMap(Map), Это кажется более подходящим в вашем случае.

При этом, несмотря на то, что этот синтаксис лаконичен, у него есть некоторые недостатки. Во-первых, если вы удерживаете ссылку на объект, инициализированный с помощью двойной скобки, вы неявно удерживаете ссылку на внешний объект, который не будет пригоден для сборки мусора. Так что будьте осторожны. Во-вторых (хотя это звучит как микрооптимизация), инициализация двойной фигурной скобки приводит к небольшим накладным расходам. В-третьих, эта техника на самом деле использует анонимные внутренние классы, как мы видели, и, таким образом, потребляет немного пространства permgen (но я сомневаюсь, что это действительно проблема, если вы действительно не злоупотребляете ими). Наконец - и это, пожалуй, самый важный момент - я не уверен, что это делает код более читабельным (это не очень известный синтаксис).

Поэтому, хотя мне нравится использовать его в тестах (для краткости), я стараюсь избегать его использования в "обычном" коде.

Да, вы могли бы подавить предупреждение, но я бы переписал его так:

HashMap<String, String> map  = new HashMap<String, String>();
map.put("a","value-a"); 
map.put("c","value-c");
someMethodThatTakesAHashMap(map);

Не нужно подавлять, и гораздо лучше читать, ИМО.

ImmutableMap класс из библиотеки Google Collections полезен для этой ситуации. например

someMethodThatTakesAHashMap(ImmutableMap.<K, V>builder().put("a","value-a").put("c","value-c").build());

или же

someMethodThatTakesAHashMap(ImmutableMap.of("a","value-a","c","value-c"));

Чтобы ответить на другую половину вашего вопроса, "я должен подавить это?" -

Да. На мой взгляд, это ужасное предупреждение. serialVersionUID по умолчанию не должен использоваться, а не наоборот.

Если вы не добавите serialVersionUID, самое худшее, что происходит, - это то, что две версии объекта, которые на самом деле совместимы с сериализацией, считаются несовместимыми. serialVersionUID - это способ объявить, что совместимость сериализации не изменилась, переопределяя оценку Java по умолчанию.

При использовании serialVersionUID самое худшее, что происходит, - это то, что вы случайно не смогли обновить идентификатор, когда сериализованная форма класса изменяется несовместимым образом. В лучшем случае вы также получаете ошибку во время выполнения. В худшем случае случается нечто худшее. И представьте, как легко это не обновить.

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

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

Поскольку код делает больше, чем предполагалось, я бы назвал это хаком.

Что мы действительно хотим, это что-то вроде этого:

foo(new HashMap<String, String>({"a", "value-a"}, {"c", "value-c"}));

Но, увы, это не относится к Java. Не существует способа сделать что-либо подобное безопасным для типов способом с использованием массива пар ключ / значение. Java просто не обладает выразительной силой.

Статические методы ImmutableMap.of в Google Collection близки, но это означает создание версии фабричного метода для различного числа пар ключ / значение. (См. Ответ finnw.)

Так что будь проще. Придерживайтесь решения Bart K, если ваш код не засорен этой инициализацией. Если это так, используйте ImmutableMap. Или сверните свой собственный подкласс HashMap с помощью методов фабрики стиля "of". Или создайте эти фабричные методы стиля "из" в служебном классе. Вот один для двух пар ключ / значение:

public final MapUtil {
    public static <K,V> Map<K,V> makeMap(K k1, V v1, K k2, V v2) {
        Map<K,V> m = new HashMap<K,V>();
        m.put(k1, v1);
        m.put(k2, v2);
        return m;
    }
}

Принимайте во внимание многословие и утешайтесь знаниями о том, что ваши корпоративные коллеги носят те же кандалы, что и вы.

Я в целом согласен с Бартом К., но в ознакомительных целях:
Предупреждение также можно устранить, добавив поле, которое может быть автоматически сгенерировано нажатием Ctrl+1.
Предупреждение также может быть подавлено добавлением аннотации @SuppressWarnings("serial") перед определением.
Анонимный класс реализует Serializeable, а Serializeable требует это статическое поле, чтобы можно было различать версии при сериализации и десериализации. Больше информации здесь:
http://www.javablogging.com/what-is-serialversionuid/

Другие вопросы по тегам