Java Language Versioning e serialVersionUID


Esempio

Quando si implementa l'interfaccia java.io.Serializable per rendere serializzabile una classe, il compilatore cerca un campo static final denominato serialVersionUID di tipo long . Se la classe non ha dichiarato esplicitamente questo campo, il compilatore crea uno di questi campi e lo assegna con un valore che deriva da un calcolo dipendente serialVersionUID di serialVersionUID . Questo calcolo dipende da vari aspetti della classe e segue le specifiche di serializzazione dell'oggetto fornite da Sun. Ma non è garantito che il valore sia lo stesso in tutte le implementazioni del compilatore.

Questo valore viene utilizzato per verificare la compatibilità delle classi rispetto alla serializzazione e questo viene fatto durante la de-serializzazione di un oggetto salvato. Serialization Runtime verifica che serialVersionUID letto dai dati de-serializzati e che serialVersionUID dichiarato nella classe sia esattamente lo stesso. In caso contrario, genera una InvalidClassException .

Si consiglia vivamente di dichiarare e inizializzare in modo esplicito il campo finale statico di tipo long e denominato 'serialVersionUID' in tutte le classi che si desidera rendere serializzabili invece di fare affidamento sul calcolo predefinito del valore per questo campo anche se non si intende usa il controllo delle versioni. Il calcolo del 'serialVersionUID' è estremamente sensibile e può variare da un'implementazione del compilatore a un altro e quindi è possibile che si InvalidClassException l' InvalidClassException anche per la stessa classe solo perché sono state utilizzate diverse implementazioni del compilatore sul mittente e sul destinatario del processo di serializzazione.

public class Example implements Serializable {          
    static final long serialVersionUID = 1L /*or some other value*/;
    //...
}

Finché serialVersionUID è lo stesso, la serializzazione Java può gestire diverse versioni di una classe. I cambiamenti compatibili e incompatibili sono;

Modifiche compatibili

  • Aggiunta di campi: quando la classe da ricostituire ha un campo che non si verifica nel flusso, quel campo nell'oggetto verrà inizializzato sul valore predefinito per il suo tipo. Se è necessaria l'inizializzazione specifica della classe, la classe può fornire un metodo readObject che può inizializzare il campo con valori non predefiniti.
  • Aggiunta di classi: lo stream conterrà la gerarchia di tipi di ciascun oggetto nello stream. Confrontando questa gerarchia nello stream con la classe corrente è possibile rilevare classi aggiuntive. Poiché non vi sono informazioni nel flusso da cui inizializzare l'oggetto, i campi della classe verranno inizializzati sui valori predefiniti.
  • Rimozione classi: il confronto tra la gerarchia di classi nello stream con quella della classe corrente può rilevare che una classe è stata cancellata. In questo caso, i campi e gli oggetti corrispondenti a quella classe vengono letti dallo stream. I campi primitivi vengono scartati, ma vengono creati gli oggetti a cui fa riferimento la classe eliminata, poiché possono essere indirizzati in seguito nello stream. Verranno raccolti automaticamente quando lo stream viene raccolto o ripristinato.
  • Aggiunta di metodi writeObject / readObject: se la versione che legge il flusso ha questi metodi, readObject è previsto, come al solito, di leggere i dati richiesti scritti nello stream dalla serializzazione predefinita. Dovrebbe chiamare defaultReadObject prima di leggere qualsiasi dato facoltativo. Come al solito, il metodo writeObject richiama defaultWriteObject per scrivere i dati richiesti e quindi può scrivere dati opzionali.
  • Aggiunta di java.io.Serializable: equivale ad aggiungere tipi. Non ci saranno valori nello stream per questa classe, quindi i suoi campi verranno inizializzati su valori predefiniti. Il supporto per sottoclassi di classi non serializzabili richiede che il supertipo della classe abbia un costruttore no-arg e la classe stessa venga inizializzata su valori predefiniti. Se il costruttore no-arg non è disponibile, viene generata l'InvalidClassException.
  • Modifica dell'accesso a un campo: i modificatori di accesso public, package, protected e private non hanno alcun effetto sulla possibilità della serializzazione di assegnare valori ai campi.
  • Modifica di un campo da statico a nonstatic o transiente a non transitorio: quando si utilizza la serializzazione predefinita per calcolare i campi serializzabili, questa modifica equivale all'aggiunta di un campo alla classe. Il nuovo campo verrà scritto nello stream, ma le classi precedenti ignoreranno il valore poiché la serializzazione non assegnerà valori a campi statici o transitori.

Cambiamenti incompatibili

  • Eliminazione di campi: se un campo viene eliminato in una classe, lo stream scritto non conterrà il suo valore. Quando il flusso viene letto da una classe precedente, il valore del campo verrà impostato sul valore predefinito perché non è disponibile alcun valore nel flusso. Tuttavia, questo valore predefinito può compromettere negativamente la capacità della versione precedente di adempiere al proprio contratto.
  • Spostamento delle classi in alto o in basso nella gerarchia: questo non può essere consentito poiché i dati nel flusso vengono visualizzati nella sequenza errata.
  • Modifica di un campo non statico in statico o un campo non transitorio in transitorio: quando si fa affidamento sulla serializzazione predefinita, questa modifica equivale all'eliminazione di un campo dalla classe. Questa versione della classe non scriverà quei dati nello stream, quindi non sarà disponibile per essere letto dalle precedenti versioni della classe. Come quando si elimina un campo, il campo della versione precedente verrà inizializzato sul valore predefinito, il che può causare il fallimento della classe in modi imprevisti.
  • Modifica del tipo dichiarato di un campo primitivo: ogni versione della classe scrive i dati con il suo tipo dichiarato. Le versioni precedenti della classe che tentano di leggere il campo non riusciranno perché il tipo di dati nel flusso non corrisponde al tipo del campo.
  • Modifica del metodo writeObject o readObject in modo che non scriva più né legga i dati di campo predefiniti o non li modifichi in modo che tentino di scriverli o leggerli quando la versione precedente no. I dati di campo predefiniti devono essere costantemente visualizzati o non visualizzati nello stream.
  • La modifica di una classe da Serializable a Externalizable o viceversa è una modifica incompatibile poiché il flusso conterrà dati incompatibili con l'implementazione della classe disponibile.
  • Modifica di una classe da un tipo non enum a un tipo enum o viceversa poiché lo stream conterrà dati che sono incompatibili con l'implementazione della classe disponibile.
  • La rimozione di Serializable o Externalizable è una modifica incompatibile poiché, una volta scritta, non fornirà più i campi necessari per le versioni precedenti della classe.
  • L'aggiunta del metodo writeReplace o readResolve a una classe non è compatibile se il comportamento produce un oggetto incompatibile con qualsiasi versione precedente della classe.