Несовместимость сериализации между классом, сгенерированным ajc и javac

Недавно я обнаружил, что некоторые классы, скомпилированные Java (Java 8) и ajc (v.1.9.2), несовместимы с сериализация. Под совместимостью с сериализацией я подразумеваю, что рассчитанные значения serialVersionUID по умолчанию не совпадают.

Пример:

public class Markup implements Serializable {
    private final MyUnit unit;
    public Markup(MyUnit unit) { this.unit = unit; }

    public enum MyUnit { DOUBLE, STRING }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Path path = Paths.get("markup.bin");
        if (args.length == 0) {
            try (OutputStream fileOutput = Files.newOutputStream(path);
                 ObjectOutputStream objectOutput = new ObjectOutputStream(fileOutput))
            {
                objectOutput.writeObject(new Markup(MyUnit.STRING));
            }
        } else {
            try (InputStream fileInput = Files.newInputStream(path);
                 ObjectInputStream objectInput = new ObjectInputStream(fileInput))
            {
                System.out.println(objectInput.readObject());
            }
        }
    }

    static String switchType(MyUnit unit) {
        switch (unit) {
            case STRING: return "%";
            case DOUBLE: return "p";
            default: return "Undefined";
        }
    }
}

Когда я компилирую этот класс с помощью ajc и запускаю, а затем компилирую этот класс с помощью javac и запускаю, я получаю исключение о несовместимости формата сериализации:

Exception in thread "main" java.io.InvalidClassException: Markup; local class incompatible: stream classdesc serialVersionUID = -1905477862550005139, local class serialVersionUID = 793529206923536473

Я также узнал, что это из-за генератора кода переключения ajc. Это создает дополнительное поле в классе private static int[] $SWITCH_TABLE$Markup$MyUnit

Поля, созданные javac: Несовместимость сериализации между классом, сгенерированным ajc и javac Поля, генерируемые ajc: Несовместимость сериализации между классом, сгенерированным ajc и javac

Мои вопросы:

  1. Разрешено ли спецификацией для java-компилятора генерировать поля, которые не определены в классе?
  2. Почему ajc генерирует дополнительное поле? Какая-то оптимизация производительности?
  3. Есть ли способ заставить ajc не генерировать дополнительное поле?
  4. По какой причине private static влияет на генерацию serialVersionUID?
  5. Знают ли разработчики аспекта об этом поведении? Если да, то почему они все равно выбирают поле генерации?
  6. Существуют ли какие-либо гарантии того, что класс Java будет сериализован JLS?
  7. Как код, сгенерированный Javac, работает без этого поля?

1. Да. 2. Похоже на таблицу переключения. 3. Нет. 4. Непонятно, что делает. Вам нужно указать serialVersionUID самостоятельно, чтобы решить эту проблему.

user207421 29.04.2019 01:45

5. См. 4. 6. Да, существует целая Спецификация сериализации объектов. 7. Слишком широкий. Вы всегда можете попробовать, как я предложил, вместо того, чтобы просто добавлять больше бессмысленных вопросов. Один вопрос на пост — это обычный максимум.

user207421 29.04.2019 14:08

>6. Да, существует целая Спецификация сериализации объектов. Почему в данном случае это не работает? Если есть спецификация - значит какой-то компилятор ее игнорирует. Который из? Я хочу знать более глубокие подробности. Не только ответы Да/Нет.

turbanoff 29.04.2019 14:41

Не думаю, что уточняющие вопросы бессмысленны. Они помогают объяснить контекст - то, что я не хочу знать.

turbanoff 29.04.2019 15:08

Зачем усложнять простое? Укажите свой собственный serialVersionUID! Вы действительно хотите полагаться на внутренности компилятора? И, конечно же, ajc создает другой код, потому что (а) он вплетает аспекты в ваши классы и (б) является ответвлением от ejc (компилятор Eclipse Java), а не от javac. Кстати, этот вопрос, возможно, страдает от проблема XY.

kriegaex 30.04.2019 06:45

@kriegaex Я хочу знать, указано ли поведение компилятора? да - поэтому у нас есть JLS. Указано поведение сериализации - да. Вот почему у нас есть спецификация сериализации. Это не просто «внутренности компилятора». Это спецификация языка/платформы. Я знаю, что добавление serialVersionUID поможет с InvalidClassException. На самом деле для меня это не "проблема". >Зачем усложнять простое? Потому что, знаете ли, мы инженеры и хотим знать, как все устроено :)

turbanoff 30.04.2019 07:18

@turbanoff Этот вопрос даже не имеет смысла, и вы можете найти это docs.oracle.com/javase/tutorial/jndi/objects/serial.html. Вы путаете внутреннее использование IDE для компиляции. Пожалуйста, поймите интерпретацию класса, прежде чем спрашивать о чем-то, что не имеет ничего общего с плохой практикой кодирования, которая вызывает подобные ошибки.

April_Nara 04.05.2019 18:12

@April_Nara, не могли бы вы быть более конкретными? Я понимаю, что не определять serialversionUIS — плохая практика, потому что любое изменение в классе будет несовместимым. Я не меняю класс в своем вопросе. Компиляция и сериализация стандартизированы. Так что, на мой взгляд, все должно работать хорошо.

turbanoff 04.05.2019 19:07

См. §4.6 Спецификация сериализации объектов Java; он явно предупреждает о том, что вычисление по умолчанию serialVersionUID очень чувствительно к реализациям компилятора. Это также объясняет, как вычисляется значение по умолчанию. Обратите внимание, что документация Serializable также содержит такое же предупреждение.

Slaw 05.05.2019 11:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
9
142
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

1. Разрешено ли компилятору Java по спецификации генерировать поля, которые не определены в классе?

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

2. Почему ajc генерирует дополнительное поле? Какая-то оптимизация производительности?

Похоже на плохую оптимизацию производительности. Возможно, это как-то связано с эффективным добавлением аспектов.

3. Есть ли способ заставить ajc не генерировать дополнительное поле?

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

4. По какой причине private static влияет на генерацию serialVersionUID?

Сериализация Java была создана в «интернет-время». Для совместимости нам оставили то, что делала первая версия. Мораль: если вы создаете что-то в Интернете, выбросьте это.

5. Знают ли разработчики аспекта об этом поведении? Если да, то почему они все равно выбирают поле генерации?

Я бы на это надеялся. Ожидается, что компиляторы создадут синтетику.

6. Существуют ли какие-либо гарантии того, что класс Java будет сериализован JLS?

Существует спецификация сериализации Java (хотя я не ожидаю, что многие люди ее прочитают).

7. Как код, сгенерированный Javac, работает без этого поля?

Относительно легко увидеть, как можно написать строку включения без массива. Точные детали уродливой оптимизированной версии будут беспорядочными. Вы можете увидеть, что на самом деле происходит с javap -private -c.

Заключение

Рекомендуется добавить serialVersionUID, если ожидается, что данные будут использоваться между разными версиями класса. OTOH, также рекомендуется не использовать сериализацию Java.

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