Изменение дженериков Java на поле класса и сериализацию - PullRequest
4 голосов
/ 14 июня 2019

У меня есть вопрос о сериализации Java, который не сообщается и не отрицается как проблемный в Документах Java (для JDK8 ).

Итак, предположим, у меня есть класс, который реализует Serializable

private static final long serialVersionUID = 1L;

private List<CharSequence> values;

Если я изменю values на

private List<String> values;

я должен изменить serialVersionUID?

Ответы [ 3 ]

4 голосов
/ 14 июня 2019

Еще один способ думать об этой операции сериализации / десериализации состоит в том, что она немного похожа на приведение.

Можете ли вы разыграть List<CharSequence> до List<String>? Не напрямую: компилятор остановит вас, потому что генерики инвариантны. Вы можете заставить его, однако:

List<String> list = (List<String>) (List<?>) listOfCharSequences;
String entry = list.get(0);

и это может работать большую часть времени, потому что String является наиболее распространенным CharSequence.

Но возможно, что там есть что-то еще, CharSequence, но не String, например StringBuilder. Приведенный выше код вылетает с ClassCastException.

Итак, если вы можете быть уверены, что все ваши CharSequence s действительно String s, безопасно внести это изменение. В противном случае, вы должны либо защититься от этого, изменив идентификатор серийной версии; или, в случае String, вы можете просто вызвать toString() для всех элементов:

List<String> listOfStrings = listOfCharSequences.stream()
    .map(cs -> Objects.toString(cs, null))
    .collect(toList())

(String.toString() возвращает себя, так что это не вернет новый экземпляр для элементов, которые уже String s)

2 голосов
/ 14 июня 2019

Если вы хотите сделать несовместимое изменение, вы должны изменить serialVersionUID, чтобы вы получили соответствующую (если не приятную) ошибку вместо, скажем, ClassCastException в какой-то момент в дальнейшем.

Если вы хотите сохранить совместимость, сохраните serialVersionUID и добавьте пользовательский метод readObject для преобразования List<CharSequence> в List<String>.

Редактировать: Чтобы расширить этоЕсли вам нужна совместимость, ваш код должен выглядеть примерно так:

// final - for one thing I don't want to implement readObjectNoData
public final class JeanObject {

Если этот класс был добавлен в качестве подкласса к другому классу, в устаревших сериализованных потоках не было бы «данных».

    private static final long serialVersionUID = 1L;

    // Mutable field for Java Serialization, but treat as final.
    private List<String> values = new ArrayList<>();

Если вы хотите, чтобы values было final, вам понадобится вырожденный Serialization Proxy хак, где объект прокси того же типа.

Я собираюсьпредположить, что вы хотите, чтобы объект коллекции был изменяемым.Вы, вероятно, не должны сериализовать изменяемые объекты, но именно так они обычно используются и как выглядит пример OP.

    private void readObject(
        ObjectInputStream in
    ) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField fields = in.readFields();
        @SuppressWarnings("unchecked")
        var oldValues = (List<CharSequence>)fields.get("values", null);
        // Don't bother with nice error message.
        //   NPE if missing or null elements.
        this.values = oldValues.stream().collect(
            ArrayList::new,
            (values, value) -> values.add(value.toString()),
            List::addAll
        );
    }
    // ...

Альтернатива (очень функционально выглядящему ) 3-аргументуStream.collect должен использовать Collectors.mapping.Старомодный фешенебельный цикл был бы менее запутанным со всех сторон, но это не хвастается в Интернете - вы бы выглядели так, как будто кто-то только что закончил.

Collectors.toList, вероятно, не подходит как,скажем, добавление элементов в список каким-либо образом вниз по строке может привести к ошибке.Может не сегодня.Может не завтра.Но когда ваш преемник автоматически обновляет реализацию.И они будут ненавидеть тебя всю оставшуюся жизнь.Вот документация по API:

Нет никаких гарантий относительно типа, изменчивости, сериализуемости или поточной безопасности возвращаемого List

Кто этого хочет?

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

(код компилируется, но, как всегда, не проверен.)

0 голосов
/ 14 июня 2019

Это полностью зависит от вас. Когда вы указываете serialVersionUID, вы берете на себя ответственность за его изменение при необходимости. Если ваша система может столкнуться с различными версиями этого объекта, когда вам нужно его изменить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...