Java Serializable: явно пустой интерфейс

Java

Для сериализации в Java я застрял на самом простом познании — реализовать сериализуемый классSerializbaleИнтерфейс в порядке. Я не хочу проводить более глубокие исследования, потому что я могу это использовать.

Но со временем, увидевSerializbaleВсе чаще и чаще я проявлял к ней сильный интерес. Время потратить некоторое время на исследования.

01. Начнем с теории

Сериализация Java — это новаторский набор функций, представленных в JDK 1.1 для преобразования объектов Java в массивы байтов для удобного хранения или передачи. После этого массив байтов все еще может быть преобразован обратно в исходное состояние объекта Java.

Идея сериализации заключается в том, чтобы «заморозить» состояние объекта, а затем записать его на диск или передать по сети; идея десериализации заключается в том, чтобы «разморозить» состояние объекта и восстановить доступное Java-объект.

Давайте посмотрим на сериализациюSerializbaleОпределение интерфейса:

public interface Serializable {
}

Очевидно, что пустой интерфейс может гарантировать, что реализующий его «объект класса» будет сериализован и десериализован?

02. Чуть более практичный бой

Прежде чем ответить на приведенные выше вопросы, давайте создадим класс (только с двумя полями и соответствующимиgetter/setter) для сериализации и десериализации.

class Wanger {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Затем создайте тестовый класс, пройдитеObjectOutputStreamЗапись «18-летнего Ван Эра» в файл на самом деле является процессом сериализации;ObjectInputStreamЧтение «18-летнего Ван Эра» из файла на самом деле является процессом десериализации.

public class Test {

    public static void main(String[] args) {
      // 初始化
        Wanger wanger = new Wanger();
        wanger.setName("王二");
        wanger.setAge(18);
        System.out.println(wanger);

        // 把对象写到文件中
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){
            oos.writeObject(wanger);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 从文件中读出对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){
            Wanger wanger1 = (Wanger) ois.readObject();
            System.out.println(wanger1);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

Однако из-заWangerне реализованыSerializbaleинтерфейс, поэтому при запуске тестового класса будет выдано исключение.Информация о стеке выглядит следующим образом:

java.io.NotSerializableException: com.cmower.java_demo.xuliehua.Wanger
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.cmower.java_demo.xuliehua.Test.main(Test.java:21)

Следуя информации о стеке, давайте посмотримObjectOutputStreamизwriteObject0()метод. Часть его исходного кода выглядит следующим образом:

if (obj instanceof String) {
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
} else {
    if (extendedDebugInfo) {
        throw new NotSerializableException(
            cl.getName() + "\n" + debugInfoStack.toString());
    } else {
        throw new NotSerializableException(cl.getName());
    }
}

Это,ObjectOutputStreamПри сериализации он будет определять, какой тип объекта будет сериализован, string? множество? перечислить? все ещеSerializable, если ни один из них, броситьNotSerializableException.

еслиWangerДостигнутоSerializableинтерфейс, вы можете сериализовать и десериализовать.

class Wanger implements Serializable{
    private static final long serialVersionUID = -2095916884810199532L;
    
    private String name;
    private int age;
}

Как его сериализовать?

отObjectOutputStreamНапример, он будет вызываться последовательно при сериализации.writeObject()writeObject0()writeOrdinaryObject()writeSerialData()invokeWriteObject()defaultWriteFields().

private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        Class<?> cl = desc.forClass();
        desc.checkDefaultSerialize();

        int primDataSize = desc.getPrimDataSize();
        desc.getPrimFieldValues(obj, primVals);
        bout.write(primVals, 0, primDataSize, false);

        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        desc.getObjFieldValues(obj, objVals);
        for (int i = 0; i < objVals.length; i++) {
          
            try {
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            }
        }
    }

Как его десериализовать?

отObjectInputStreamНапример, он будет вызываться по очереди при десериализацииreadObject()readObject0()readOrdinaryObject()readSerialData()defaultReadFields().

private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        Class<?> cl = desc.forClass();
        desc.checkDefaultSerialize();

        int primDataSize = desc.getPrimDataSize();
        desc.getPrimFieldValues(obj, primVals);
        bout.write(primVals, 0, primDataSize, false);

        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        desc.getObjFieldValues(obj, objVals);
        for (int i = 0; i < objVals.length; i++) {
          
            try {
                writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            }
        }
    }

Я хочу увидеть это, вы должны вдруг осознать "о".SerializableПричина, по которой интерфейс определяется как пустой, заключается в том, что он служит только идентификатором, сообщая программе, что объект, который его реализует, может быть сериализован, но настоящие операции сериализации и десериализации не требуют его завершения.

03. Еще немного внимания

Давайте сразу к делу,staticиtransientИзмененные поля не сериализуются.

Зачем? Сначала докажем, а потом объясним почему.

Первый вWangerДобавьте два поля в класс.

class Wanger implements Serializable {
    private static final long serialVersionUID = -2095916884810199532L;

    private String name;
    private int age;

    public static String pre = "沉默";
    transient String meizi = "王三";

    @Override
    public String toString() {
        return "Wanger{" + "name=" + name + ",age=" + age + ",pre=" + pre + ",meizi=" + meizi + "}";
    }
}

Во-вторых, распечатайте объект перед сериализацией и десериализацией в тестовом классе и измените его после сериализации и до десериализации.staticзначение поля. Конкретный код выглядит следующим образом:

// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);

// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));){
        oos.writeObject(wanger);
    } catch (IOException e) {
        e.printStackTrace();
    }
   
    // 改变 static 字段的值
Wanger.pre ="不沉默";

// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));){
    Wanger wanger1 = (Wanger) ois.readObject();
    System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}
// Wanger{name=王二,age=18,pre=沉默,meizi=王三}
// Wanger{name=王二,age=18,pre=不沉默,meizi=null}

Сравнивая результаты, мы можем обнаружить, что:

1) Перед сериализациейpreЗначение "silent" после сериализации,preЗначение изменено на «не молчать» после десериализации,preЗначение «не тихо», а не состояние «молчание» до сериализации.

Зачем? Поскольку сериализация сохраняет состояние объекта, аstaticДекорированное поле принадлежит состоянию класса, поэтому можно доказать, что сериализация не сохраняетstaticИзмененное поле.

2) Перед сериализациейmeiziЗначение «Ван Сан» после десериализацииmeiziзначениеnull, вместо состояния "король три" перед сериализацией.

Зачем?transientКитайское значение слова «временный» (о важности английского языка), оно может предотвратить сериализацию полей в файл после десериализации,transientЗначение поля устанавливается равным начальному значению, напримерintНачальное значение типа равно 0, начальное значение типа объекта равно 0null.

Если вы хотите углубиться в исходный код, вы можете перейти кObjectStreamClassНашел следующий код в:

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
    Field[] clFields = cl.getDeclaredFields();
    ArrayList<ObjectStreamField> list = new ArrayList<>();
    int mask = Modifier.STATIC | Modifier.TRANSIENT;

    int size = list.size();
    return (size == 0) ? NO_FIELDS :
        list.toArray(new ObjectStreamField[size]);
}

ВидетьModifier.STATIC | Modifier.TRANSIENT, стало лучше?

04. Еще немного галантереи

КромеSerializableКроме того, Java также предоставляет интерфейс сериализации.Externalizable(звучит немного неудобно читать).

Есть ли разница между двумя интерфейсами? Просто попробуйте и узнайте.

Во-первых, поставитьWangerИнтерфейс, который реализует классSerializableзаменитьExternalizable.

class Wanger implements Externalizable {
	private String name;
	private int age;

	public Wanger() {

	}

	public String getName() {
		return name;
	}

	
	@Override
	public String toString() {
		return "Wanger{" + "name=" + name + ",age=" + age + "}";
	}

	@Override
	public void writeExternal(ObjectOutput out) throws IOException {

	}

	@Override
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

	}

}

выполнитьExternalizableинтерфейсWangerкласс и реализацияSerializableинтерфейсWangerКлассы немного отличаются:

1) Добавлен конструктор без параметров.

использоватьExternalizableПри десериализации вызывается конструктор без аргументов сериализованного класса для создания нового объекта, а затем копируются значения полей сохраненного объекта. В противном случае будет выдано следующее исключение:

java.io.InvalidClassException: com.cmower.java_demo.xuliehua1.Wanger; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:790)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1782)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
	at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)

2) Добавлено два новых методаwriteExternal()иreadExternal(),выполнитьExternalizableтребует интерфейс.

Затем мы печатаем предварительно сериализованные и десериализованные объекты в тестовом классе.

// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);

// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {
	oos.writeObject(wanger);
} catch (IOException e) {
	e.printStackTrace();
}

// 从文件中读出对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {
	Wanger wanger1 = (Wanger) ois.readObject();
	System.out.println(wanger1);
} catch (IOException | ClassNotFoundException e) {
	e.printStackTrace();
}
// Wanger{name=王二,age=18}
// Wanger{name=null,age=0}

Из результатов вывода поля объекта, полученные после десериализации, стали значениями по умолчанию, то есть состояние объекта до сериализации не было «заморожено».

Зачем? потому что мы неWangerкласс переопределяет конкретныйwriteExternal()иreadExternal()метод. Как его переписать?

@Override
public void writeExternal(ObjectOutput out) throws IOException {
	out.writeObject(name);
	out.writeInt(age);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
	name = (String) in.readObject();
	age = in.readInt();
}

1) позвонитьObjectOutputизwriteObject()метод преобразует строковый типnameзаписать в выходной поток;

2) позвонитьObjectOutputизwriteInt()метод преобразует целое числоageзаписать в выходной поток;

3) позвонитьObjectInputизreadObject()метод преобразует строковый типnameчитать во входной поток;

4) позвонитьObjectInputизreadInt()метод преобразует строковый типageчитать во входной поток;

Запустите тестовый класс еще раз, и вы увидите, что объект сериализуется и десериализуется нормально.

До сериализации: Вангер{name=Ван Эр,возраст=18} После сериализации: Вангер{name=Ван Эр,возраст=18}

05. Больше десерта

позвольте мне сначала спросить вас, вы знаетеprivate static final long serialVersionUID = -2095916884810199532L;Этот код работает?

В порядке......

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

Когда класс реализуетSerializableПосле интерфейса IDE напомнит классу, что лучше сгенерировать сериализованный идентификатор, например:

1) Добавьте версию идентификатора сериализации по умолчанию:

private static final long serialVersionUID = 1L

2) Добавьте случайно сгенерированный неповторяющийся сериализованный идентификатор.

private static final long serialVersionUID = -2095916884810199532L;

3) Добавить@SuppressWarningsаннотация.

@SuppressWarnings("serial")

Как выбрать?

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

class Wanger implements Serializable {
	private static final long serialVersionUID = -2095916884810199532L;
	
	private String name;
	private int age;

	// 其他代码忽略
}

Затем сериализоватьWangerвозражать против файла.

// 初始化
Wanger wanger = new Wanger();
wanger.setName("王二");
wanger.setAge(18);
System.out.println(wanger);

// 把对象写到文件中
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("chenmo"));) {
	oos.writeObject(wanger);
} catch (IOException e) {
	e.printStackTrace();
}

В это время мы тихоWangerСериализованный идентификатор класса — это кража, хе-хе.

// private static final long serialVersionUID = -2095916884810199532L;
private static final long serialVersionUID = -2095916884810199533L;

Хорошо, готов к десериализации.

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("chenmo")));) {
	Wanger wanger = (Wanger) ois.readObject();
	System.out.println(wanger);
} catch (IOException | ClassNotFoundException e) {
	e.printStackTrace();
}

Упс! Что-то пошло не так.

java.io.InvalidClassException:  local class incompatible: stream classdesc 
serialVersionUID = -2095916884810199532,
local class serialVersionUID = -2095916884810199533
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)

Информация стека исключений сообщает нам, что сериализованный идентификатор, считанный из постоянного файла, несовместим с локальным сериализованным идентификатором и не может быть десериализован.

Тогда, если мы используем третий метод,Wangerдобавить класс@SuppressWarnings("serial")А аннотации?

@SuppressWarnings("serial")
class Wanger3 implements Serializable {
// 省略其他代码
}

Что ж, давайте снова десериализуем. К сожалению, все равно выдает ошибку.

java.io.InvalidClassException:  local class incompatible: stream classdesc 
serialVersionUID = -2095916884810199532, 
local class serialVersionUID = -3818877437117647968
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
	at com.cmower.java_demo.xuliehua1.Test.main(Test.java:27)

Информация о стеке исключений сообщает нам, что локальный идентификатор сериализации равен -3818877437117647968, что по-прежнему несовместимо с идентификатором сериализации, прочитанным в постоянном файле, и не может быть десериализовано. На что это указывает? использовать@SuppressWarnings("serial")При аннотации аннотация автоматически генерирует случайный идентификатор сериализации для сериализованного класса.

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

То есть, если нет особых требований, можно использовать идентификатор сериализации по умолчанию (1L), который может гарантировать, что десериализация будет успешной, когда код непротиворечив.

class Wanger implements Serializable {
	private static final long serialVersionUID = 1L;
// 省略其他代码
}

06. Небольшое резюме

Перед написанием этой статьи я действительно не ожидал: "пустое тело"SerializableСтолько всего нужно изучить!

После написания этой статьи я не мог не вспомнить фразу, сказанную Цао Линьцзином, защитником науки: «Независимо от того, насколько мала проблема в обучении, вы должны обобщать каждый пункт знаний» - это правда и правда Да верно!