Перейти к основному содержимому

Руководство по внешнему интерфейсу в Java

· 4 мин. чтения

1. Введение

В этом руководстве мы кратко рассмотрим интерфейс Java java.io.Externalizable . Основная цель этого интерфейса — упростить пользовательскую сериализацию и десериализацию.

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

После этого мы собираемся обсудить ключевые отличия от интерфейса java.io.Serializable .

2. Внешний интерфейс

Externalizable расширяется от интерфейса маркера java.io.Serializable . Любой класс, реализующий интерфейс Externalizable , должен переопределять методы writeExternal() , readExternal() . Таким образом, мы можем изменить поведение сериализации JVM по умолчанию.

2.1. Сериализация

Давайте посмотрим на этот простой пример:

public class Country implements Externalizable {

private static final long serialVersionUID = 1L;

private String name;
private int code;

// getters, setters

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

@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
this.name = in.readUTF();
this.code = in.readInt();
}
}

Здесь мы определили класс Country , который реализует интерфейс Externalizable и реализует два упомянутых выше метода.

В методе writeExternal() мы добавляем свойства объекта в поток ObjectOutput . У него есть стандартные методы, такие как writeUTF() для String и writeInt() для значений int.

Затем, для десериализации объекта, мы читаем из потока ObjectInput , используя методы readUTF(), readInt() для чтения свойств в том же порядке, в котором они были записаны.

Хорошей практикой является добавление serialVersionUID вручную. Если он отсутствует, JVM автоматически добавит его.

Автоматически сгенерированный номер зависит от компилятора. Это означает, что это может вызвать маловероятное исключение InvalidClassException .

Давайте проверим поведение, которое мы реализовали выше:

@Test
public void whenSerializing_thenUseExternalizable()
throws IOException, ClassNotFoundException {

Country c = new Country();
c.setCode(374);
c.setName("Armenia");

FileOutputStream fileOutputStream
= new FileOutputStream(OUTPUT_FILE);
ObjectOutputStream objectOutputStream
= new ObjectOutputStream(fileOutputStream);
c.writeExternal(objectOutputStream);

objectOutputStream.flush();
objectOutputStream.close();
fileOutputStream.close();

FileInputStream fileInputStream
= new FileInputStream(OUTPUT_FILE);
ObjectInputStream objectInputStream
= new ObjectInputStream(fileInputStream);

Country c2 = new Country();
c2.readExternal(objectInputStream);

objectInputStream.close();
fileInputStream.close();

assertTrue(c2.getCode() == c.getCode());
assertTrue(c2.getName().equals(c.getName()));
}

В этом примере мы сначала создаем объект Country и записываем его в файл. Затем мы десериализуем объект из файла и проверяем правильность значений.

Вывод напечатанного объекта c2 :

Country{name='Armenia', code=374}

Это показывает, что мы успешно десериализовали объект.

2.2. Наследование

Когда класс наследуется от интерфейса Serializable , JVM также автоматически собирает все поля из подклассов и делает их сериализуемыми.

Имейте в виду, что мы можем применить это и к Externalizable . Нам просто нужно реализовать методы чтения/записи для каждого подкласса иерархии наследования.

Давайте посмотрим на класс Region ниже, который расширяет наш класс Country из предыдущего раздела:

public class Region extends Country implements Externalizable {

private static final long serialVersionUID = 1L;

private String climate;
private Double population;

// getters, setters

@Override
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
out.writeUTF(climate);
}

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

super.readExternal(in);
this.climate = in.readUTF();
}
}

Здесь мы добавили два дополнительных свойства и сериализовали первое.

Обратите внимание, что мы также вызывали super.writeExternal(out), super.readExternal(in) в методах сериализатора для сохранения/восстановления полей родительского класса .

Давайте запустим модульный тест со следующими данными:

Region r = new Region();
r.setCode(374);
r.setName("Armenia");
r.setClimate("Mediterranean");
r.setPopulation(120.000);

Вот десериализованный объект:

Region{
country='Country{
name='Armenia',
code=374}'
climate='Mediterranean',
population=null
}

Обратите внимание: поскольку мы не сериализовали поле населения в классе Region , значение этого свойства равно null.

3. Экстернализуемые и сериализуемые

Давайте рассмотрим ключевые различия между двумя интерфейсами:

  • Ответственность за сериализацию

Ключевое отличие здесь заключается в том, как мы обрабатываем процесс сериализации. Когда класс реализует интерфейс java.io.Serializable , JVM берет на себя полную ответственность за сериализацию экземпляра класса. В случае с Externalizable именно программист должен позаботиться обо всей сериализации, а также о процессе десериализации.

  • Пример использования

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

  • Производительность

Интерфейс java.io.Serializable использует отражение и метаданные, что приводит к относительно низкой производительности. Для сравнения, интерфейс Externalizable дает вам полный контроль над процессом сериализации.

  • Порядок чтения

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

Например, если мы изменим порядок чтения свойств кода и имени в классе Country , будет сгенерировано исключение java.io.EOFException .

Между тем, интерфейс Serializable не имеет этого требования.

  • Пользовательская сериализация

Мы можем добиться пользовательской сериализации с помощью интерфейса Serializable , пометив поле ключевым словом transient . JVM не будет сериализовать конкретное поле, но добавит это поле в хранилище файлов со значением по умолчанию . Вот почему рекомендуется использовать Externalizable в случае пользовательской сериализации.

4. Вывод

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

Как обычно, полный исходный код руководства доступен на GitHub .