Рубрики
Без рубрики

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

Взгляните на внешний интерфейс Java для пользовательской сериализации и десериализации.

Автор оригинала: baeldung.

1. введение

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

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

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

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

Externalizable расширяется из интерфейса java.io.Serializable marker. Любой класс, реализующий интерфейс 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.

Далее, для десериализации объекта мы читаем из Object Input stream , используя методы readout(), gradient() для чтения свойств в том же точном порядке, в котором они были записаны.

Рекомендуется добавить 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
}

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

3. Externalizable vs Serializable

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

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

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

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

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

  • Представление

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

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

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

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

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

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

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

4. Заключение

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

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