Guida all'interfaccia esternalizzabile in Java

1. Introduzione

In questo tutorial, avremo un rapido sguardo a Java java.io.Externalizable interfaccia . L'obiettivo principale di questa interfaccia è facilitare la serializzazione e la deserializzazione personalizzate.

Prima di andare avanti, assicurati di controllare la serializzazione nell'articolo Java. Il prossimo capitolo tratta di come serializzare un oggetto Java con questa interfaccia.

Dopodiché, discuteremo le principali differenze rispetto all'interfaccia java.io.Serializable .

2. L' interfaccia esternalizzabile

Externalizable si estende dall'interfaccia marker java.io.Serializable . Ogni classe che implementa Externalizable interfaccia dovrebbe ignorare le writeExternal () , readExternal () metodi . In questo modo possiamo modificare il comportamento di serializzazione predefinito della JVM.

2.1. Serializzazione

Diamo un'occhiata a questo semplice esempio:

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(); } }

Qui, abbiamo definito una classe Country che implementa l' interfaccia Externalizable e implementa i due metodi sopra menzionati.

Nel metodo writeExternal () , stiamo aggiungendo le proprietà dell'oggetto al flusso ObjectOutput . Questo ha metodi standard come writeUTF () per String e writeInt () per i valori int.

Successivamente, per deserializzare l'oggetto, stiamo leggendo dal flusso ObjectInput utilizzando i metodi readUTF (), readInt () per leggere le proprietà nello stesso esatto ordine in cui sono state scritte.

È buona norma aggiungere manualmente serialVersionUID . Se questo è assente, la JVM ne aggiungerà automaticamente uno.

Il numero generato automaticamente dipende dal compilatore. Ciò significa che potrebbe causare un'eccezione InvalidClassException improbabile .

Testiamo il comportamento che abbiamo implementato sopra:

@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())); }

In questo esempio, creiamo prima un oggetto Country e lo scriviamo su un file. Quindi deserializziamo l'oggetto dal file e verifichiamo che i valori siano corretti.

L'output dell'oggetto c2 stampato :

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

Questo mostra che abbiamo deserializzato con successo l'oggetto.

2.2. Eredità

Quando una classe eredita dall'interfaccia Serializable , la JVM raccoglie automaticamente anche tutti i campi dalle sottoclassi e li rende serializzabili.

Tieni presente che possiamo applicarlo anche a Externalizable . Abbiamo solo bisogno di implementare i metodi di lettura / scrittura per ogni sottoclasse della gerarchia di ereditarietà.

Diamo un'occhiata alla classe Region di seguito che estende la nostra classe Country dalla sezione precedente:

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(); } }

Qui, abbiamo aggiunto due proprietà aggiuntive e serializzato la prima.

Nota che abbiamo anche chiamato super.writeExternal (out), super.readExternal (in) all'interno dei metodi serializer per salvare / ripristinare anche i campi della classe genitore .

Eseguiamo lo unit test con i seguenti dati:

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

Ecco l'oggetto deserializzato:

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

Si noti che poiché non abbiamo serializzato il campo della popolazione nella classe Region , il valore di quella proprietà è nullo.

3. Esternalizzabile vs Serializzabile

Esaminiamo le principali differenze tra le due interfacce:

  • Responsabilità di serializzazione

La differenza fondamentale qui è come gestiamo il processo di serializzazione. Quando una classe implementa l' interfaccia java.io.Serializable , la JVM si assume la piena responsabilità della serializzazione dell'istanza della classe. In caso di Externalizable, è il programmatore che dovrebbe occuparsi dell'intero processo di serializzazione e deserializzazione.

  • Caso d'uso

Se abbiamo bisogno di serializzare l'intero oggetto, l' interfaccia Serializable è più adatta. D'altra parte, per la serializzazione personalizzata, possiamo controllare il processo utilizzando Externalizable .

  • Prestazione

L' interfaccia java.io.Serializable utilizza la riflessione e i metadati che causano prestazioni relativamente lente. In confronto, l' interfaccia Externalizable ti dà il pieno controllo sul processo di serializzazione.

  • Ordine di lettura

Durante l'utilizzo di Externalizable , è obbligatorio leggere tutti gli stati dei campi nell'ordine esatto in cui sono stati scritti. Altrimenti, otterremo un'eccezione.

For example, if we change the reading order of the code and name properties in the Country class, a java.io.EOFException will be thrown.

Meanwhile, the Serializable interface doesn't have that requirement.

  • Custom Serialization

We can achieve custom serialization with the Serializable interface by marking the field with transient keyword. The JVM won't serialize the particular field but it'll add up the field to file storage with the default value. That's why it's a good practice to use Externalizable in case of custom serialization.

4. Conclusion

In questa breve guida all'interfaccia Externalizable , abbiamo discusso le caratteristiche principali, i vantaggi e gli esempi dimostrati di semplice utilizzo. Abbiamo anche fatto un confronto con l' interfaccia Serializable .

Come al solito, il codice sorgente completo del tutorial è disponibile su GitHub.