serializzazione - serialversionuid java




Che cos'è un serialVersionUID e perché dovrei usarlo? (14)

Che cos'è un serialVersionUID e perché dovrei usarlo?

SerialVersionUID è un identificatore univoco per ogni classe, JVM utilizza per confrontare le versioni della classe assicurando che sia stata utilizzata la stessa classe durante il caricamento della serializzazione durante la deserializzazione.

Specificarne uno dà più controllo, anche se JVM ne genera uno se non lo si specifica. Il valore generato può variare tra diversi compilatori. Inoltre, a volte vuoi solo per qualche motivo proibire la deserializzazione di vecchi oggetti serializzati [ backward incompatibility ], e in questo caso devi solo cambiare serialVersionUID.

I javadoc per Serializable dicono :

il calcolo predefinito di serialVersionUID è estremamente sensibile ai dettagli della classe che possono variare a seconda delle implementazioni del compilatore e può pertanto provocare InvalidClassException s inattesi durante la deserializzazione.

Pertanto, è necessario dichiarare serialVersionUID perché ci dà più controllo .

Questo articolo ha alcuni punti positivi sull'argomento.

Eclipse emette avvisi quando manca un serialVersionUID .

La classe serializzabile Foo non dichiara un campo seriale serialVersionUID finale di tipo lungo

Cos'è serialVersionUID e perché è importante? Si prega di mostrare un esempio in cui manca serialVersionUID causerà un problema.


Come per un esempio in cui il serialVersionUID mancante potrebbe causare un problema:

Sto lavorando a questa applicazione Java EE che è composta da un modulo Web che utilizza un modulo EJB . Il modulo web chiama il modulo EJB remoto e passa un POJO che implementa Serializable come argomento.

POJO's classe di questo POJO's stata impacchettata all'interno del jar EJB e all'interno del proprio jar nel WEB-INF / lib del modulo web. In realtà sono della stessa classe, ma quando impacchetta il modulo EJB, spacchetto questo jar di POJO per comprarlo insieme al modulo EJB.

La chiamata al EJB stava fallendo con l'Eccezione di seguito perché non avevo dichiarato il suo serialVersionUID :

Caused by: java.io.IOException: Mismatched serialization UIDs : Source
 (Rep.
 IDRMI:com.hordine.pedra.softbudget.domain.Budget:5CF7CE11E6810A36:04A3FEBED5DA4588)
 = 04A3FEBED5DA4588 whereas Target (Rep. ID RMI:com.hordine.pedra.softbudget.domain.Budget:7AF5ED7A7CFDFF31:6227F23FA74A9A52)
 = 6227F23FA74A9A52

I dati del campo rappresentano alcune informazioni memorizzate nella classe. La classe implementa l'interfaccia Serializable , quindi eclipse viene automaticamente offerto per dichiarare il campo serialVersionUID . Iniziamo con il valore 1 impostato lì.

Se non vuoi che questo avviso venga, usa questo:

@SuppressWarnings("serial")

I documenti per java.io.Serializable sono probabilmente una spiegazione valida per te:

Il runtime di serializzazione associa a ciascuna classe serializzabile un numero di versione, chiamato serialVersionUID, utilizzato durante la deserializzazione per verificare che il mittente e il destinatario di un oggetto serializzato abbiano caricato classi per quell'oggetto che sono compatibili con la serializzazione. Se il destinatario ha caricato una classe per l'oggetto che ha un serialVersionUID diverso da quello della classe del mittente corrispondente, la deserializzazione produrrà una InvalidClassException . Una classe serializzabile può dichiarare il proprio serialVersionUID esplicitamente dichiarando un campo denominato " serialVersionUID " che deve essere statico, definitivo e di tipo long :

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

Se una classe serializzabile non dichiara esplicitamente un serialVersionUID, il runtime di serializzazione calcolerà un valore serialVersionUID predefinito per quella classe in base a vari aspetti della classe, come descritto nella specifica di serializzazione dell'oggetto Java (TM). Tuttavia, si consiglia vivamente che tutte le classi serializzabili dichiarino esplicitamente i valori serialVersionUID, poiché il calcolo serialVersionUID predefinito è estremamente sensibile ai dettagli della classe che possono variare a seconda delle implementazioni del compilatore e può pertanto comportare imprevisti InvalidClassExceptions durante la deserializzazione. Pertanto, per garantire un valore serialVersionUID coerente tra diverse implementazioni del compilatore java, una classe serializzabile deve dichiarare un valore serialVersionUID esplicito. È inoltre vivamente consigliato che le dichiarazioni esplicite serialVersionUID utilizzino il modificatore privato laddove possibile, poiché tali dichiarazioni si applicano solo alla classe immediatamente dichiarante - i campi serialVersionUID non sono utili come membri ereditati.


Non posso perdere questa occasione per collegare il libro di Josh Bloch, Effective Java (2nd Edition). Il capitolo 11 è una risorsa indispensabile sulla serializzazione Java.

Per Josh, l'UID generato automaticamente viene generato sulla base di un nome di classe, interfacce implementate e tutti i membri pubblici e protetti. Modificando uno di questi in qualsiasi modo cambierà serialVersionUID . Quindi non è necessario pasticciarli solo se si è certi che non verrà serializzata più di una versione della classe (tra processi o recuperati dallo storage in un momento successivo).

Se li ignori per ora, e scopri in seguito che hai bisogno di cambiare la classe in qualche modo, ma mantieni la compatibilità con la vecchia versione della classe, puoi usare lo strumento JDK serialver per generare serialVersionUID sulla vecchia classe ed impostare esplicitamente quello sulla nuova classe. (A seconda delle modifiche, potrebbe essere necessario implementare anche la serializzazione personalizzata aggiungendo i metodi writeObject e readObject : vedere javadoc Serializable o capitolo 11 precedente).


Non preoccupatevi, il calcolo predefinito è veramente buono e sufficiente per il 99,9999% dei casi. E se ti imbatti in problemi, puoi - come già affermato - introdurre gli UID come il bisogno di arrise (che è altamente improbabile)


Per prima cosa ho bisogno di spiegare la serializzazione.
La serializzazione consente di convertire l'oggetto nello stream, per inviare quell'oggetto alla rete OPPURE Salva nel file OPPURE salvare nel DB per l'utilizzo della lettera.

Ci sono alcune regole per la serializzazione .

  • Un oggetto è serializzabile solo se la sua classe o la sua superclasse implementa l'interfaccia Serializable

  • Un oggetto è serializzabile (implementa di per sé l'interfaccia Serializable) anche se la sua superclasse non lo è. Tuttavia, la prima superclasse nella gerarchia della classe serializzabile, che non implementa un'interfaccia serializzabile, DEVE avere un costruttore no-arg. Se questo viene violato, readObject () produrrà una java.io.InvalidClassException in runtime

  • Tutti i tipi primitivi sono serializzabili.

  • I campi transitori (con modificatore transitorio) NON sono serializzati, (cioè non salvati o ripristinati). Una classe che implementa Serializable deve contrassegnare i campi transitori di classi che non supportano la serializzazione (ad esempio, un flusso di file).

  • I campi statici (con modificatore statico) non sono serializzati.

Quando l'oggetto viene serializzato, JAVA Runtime associa il numero di versione seriale chiamato serialVersionID.

Dove è necessario serialVersionID: durante la deserializzazione per verificare che mittente e destinatario siano compatibili rispetto alla serializzazione. Se il ricevitore ha caricato la classe con un serialVersionID diverso, la deserializzazione terminerà con InvalidClassCastException .
Una classe serializzabile può dichiarare esplicitamente il proprio serialVersionUID dichiarando un campo denominato "serialVersionUID" che deve essere statico, finale e di tipo long :.

Proviamo questo con un esempio.

import java.io.Serializable;    
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String empname;
private byte empage;

public String getEmpName() {
    return name;
}
public void setEmpName(String empname) {
    this.empname = empname;
}
public byte getEmpAge() {
    return empage;
}
public void setEmpAge(byte empage) {
    this.empage = empage;
}

public String whoIsThis() {
    StringBuffer employee = new StringBuffer();
    employee.append(getEmpName()).append(" is ).append(getEmpAge()).append("
years old  "));
    return employee.toString();
}
}

Crea oggetto serializza

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Writer {
public static void main(String[] args) throws IOException {
    Employee employee = new Employee();
    employee.setEmpName("Jagdish");
    employee.setEmpAge((byte) 30);

    FileOutputStream fout = new 
FileOutputStream("/users/Jagdish.vala/employee.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fout);
    oos.writeObject(employee);
    oos.close();
    System.out.println("Process complete");
}
}

Deserializ l'oggetto

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Reader {
public static void main(String[] args) throws ClassNotFoundException, 
IOException {
    Employee employee = new Employee();
    FileInputStream fin = new 
    FileInputStream("/users/Jagdish.vala/employee.obj");
    ObjectInputStream ois = new ObjectInputStream(fin);
    employee = (Employee) ois.readObject();
    ois.close();
    System.out.println(employee.whoIsThis());
 }
}    

NOTA: modifica il serialVersionUID della classe Employee e salva:

private static final long serialVersionUID = **4L**;

Ed esegui la classe Reader. Non eseguire la classe Writer e otterrai l'eccezione.

Exception in thread "main" java.io.InvalidClassException: 
com.jagdish.vala.java.serialVersion.Employee; local class incompatible: 
stream classdesc serialVersionUID = 1, local class serialVersionUID = 4
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.krishantha.sample.java.serialVersion.Reader.main(Reader.java:14)

Puoi dire a Eclipse di ignorare questi avvisi di serialVersionUID:

Finestra> Preferenze> Java> Compilatore> Errori / Avvisi> Potenziali problemi di programmazione

Nel caso in cui non lo sapessi, ci sono molti altri avvisi che puoi abilitare in questa sezione (o anche alcuni di quelli segnalati come errori), molti sono molto utili:

  • Potenziali problemi di programmazione: Possibile assegnazione booleana accidentale
  • Potenziali problemi di programmazione: accesso al puntatore nullo
  • Codice non necessario: la variabile locale non viene mai letta
  • Codice non necessario: controllo null ridondante
  • Codice non necessario: cast non necessario o 'instanceof'

e tanti altri.


Se ricevi questo avvertimento in una classe che non hai mai pensato di serializzare, e che non hai dichiarato di implements Serializable , è spesso perché hai ereditato da una superclasse, che implementa Serializable. Spesso quindi sarebbe meglio delegare a tale oggetto invece di usare l'ereditarietà.

Quindi, invece di

public class MyExample extends ArrayList<String> {

    public MyExample() {
        super();
    }
    ...
}

fare

public class MyExample {
    private List<String> myList;

    public MyExample() {
         this.myList = new ArrayList<String>();
    }
    ...
}

e nei metodi rilevanti chiama myList.foo() invece di this.foo() (o super.foo() ). (Questo non si adatta in tutti i casi, ma ancora abbastanza spesso.)

Vedo spesso persone che estendono JFrame o simili, quando hanno solo bisogno di delegare a questo. (Questo aiuta anche a completare automaticamente in un IDE, poiché JFrame ha centinaia di metodi, che non ti servono quando vuoi chiamare quelli personalizzati nella tua classe.)

Un caso in cui l'avvertenza (o serialVersionUID) è inevitabile è quando si estrae da AbstractAction, normalmente in una classe anonima, solo aggiungendo il metodo actionPerformed. Penso che non ci dovrebbe essere un avvertimento in questo caso (dato che normalmente non è possibile serializzare e deserializzare tali classi anonime in tutte le versioni della tua classe), ma non sono sicuro di come il compilatore possa riconoscerlo.


Se stai serializzando solo perché devi serializzare per il bene dell'implementazione (a chi importa se serializzi per un HTTPSession, per esempio ... se è memorizzato o meno, probabilmente non ti interessa la serializzazione di un oggetto modulo) , quindi puoi ignorarlo.

Se si utilizza effettivamente la serializzazione, è importante solo se si pianifica di archiviare e recuperare oggetti utilizzando direttamente la serializzazione. SerialVersionUID rappresenta la versione della tua classe e dovresti incrementarla se la versione corrente della tua classe non è retrocompatibile con la sua versione precedente.

La maggior parte delle volte, probabilmente non utilizzerai la serializzazione direttamente. In questo caso, genera un UID serializzabile predefinito facendo clic sull'opzione di correzione rapida e non preoccuparti.


Perché usare la classe SerialVersionUIDinterna Serializablein Java?

Durante serialization, Java runtime crea un numero di versione per una classe, in modo che possa declassificarlo in un secondo momento. Questo numero di versione è noto come SerialVersionUIDin Java.

SerialVersionUIDè usato per la versione dei dati serializzati. È possibile declassare una classe solo se SerialVersionUIDcorrisponde all'istanza serializzata. Quando non lo dichiariamo SerialVersionUIDnella nostra classe, Java runtime lo genera per noi ma non è raccomandato. Si consiglia di dichiarare SerialVersionUIDcome private static final longvariabile per evitare il meccanismo predefinito.

Quando si dichiara una classe come Serializableimplementando l'interfaccia marker java.io.Serializable, Java runtime persiste l'istanza di tale classe nel disco utilizzando il meccanismo di serializzazione predefinito, a condizione che non si sia personalizzato il processo utilizzando l' Externalizableinterfaccia.

vedi anche Perché usare SerialVersionUID all'interno della classe Serializable in Java

Codice: javassist.SerialVersionUID


Ogni volta che un oggetto viene serializzato, l'oggetto viene timbrato con un numero ID di versione per la classe dell'oggetto. Questo ID è chiamato serialVersionUID ed è calcolato in base alle informazioni sulla struttura della classe. Supponete di aver creato una classe Employee e che abbia l'ID di versione # 333 (assegnato da JVM), Ora quando serializzerete l'oggetto di quella classe (Suppose Employee object), JVM assegnerà l'UID ad esso come # 333.

Considera una situazione: in futuro dovrai modificare o cambiare la tua classe e in tal caso quando la modifichi, JVM assegnerà un nuovo UID (Supponiamo # 444). Ora, quando si tenta di deserializzare l'oggetto dipendente, JVM confronterà l'ID versione dell'oggetto (Employee) dell'oggetto serializzato (n. 333) con quello della classe cioè # 444 (Poiché è stato modificato). A confronto, JVM troverà che entrambi gli UID di versione sono diversi e quindi la deserializzazione fallirà. Quindi se serialVersionID per ogni classe è definito dal programmatore stesso. Sarà lo stesso anche se la classe si evolve in futuro e quindi JVM troverà sempre che la classe è compatibile con l'oggetto serializzato anche se la classe è cambiata. Per ulteriori informazioni è possibile consultare il capitolo 14 di HEAD FIRST JAVA.


Sarebbe bello se CheckStyle potesse verificare che il serialVersionUID su una classe che implementa Serializable abbia un buon valore, cioè che corrisponda a ciò che il generatore di id della versione seriale produrrebbe. Se si dispone di un progetto con molti DTO serializzabili, ad esempio, ricordare di eliminare il serialVersionUID esistente e rigenerarlo è un problema, e attualmente l'unico modo (che io sappia) per verificarlo è quello di rigenerare per ogni classe e confrontare con quello vecchio. Questo è molto molto doloroso.


Se si desidera modificare un numero enorme di classi che non hanno impostato serialVersionUID in primo luogo mantenendo la compatibilità con le classi precedenti, strumenti come IntelliJ Idea, Eclipse non sono sufficienti in quanto generano numeri casuali e non funzionano su un gruppo di file in un colpo solo. Vengo al seguente script bash (mi dispiace per gli utenti di Windows, considera l'acquisto di un Mac o la conversione a Linux) per modificare facilmente il problema serialVersionUID:

base_dir=$(pwd)                                                                  
src_dir=$base_dir/src/main/java                                                  
ic_api_cp=$base_dir/target/classes                                               

while read f                                                                     
do                                                                               
    clazz=${f//\//.}                                                             
    clazz=${clazz/%.java/}                                                       
    seruidstr=$(serialver -classpath $ic_api_cp $clazz | cut -d ':' -f 2 | sed -e 's/^\s\+//')
    perl -ni.bak -e "print $_; printf qq{%s\n}, q{    private $seruidstr} if /public class/" $src_dir/$f
done

si salva questo script, ad esempio add_serialVersionUID.sh ~ / bin. Quindi lo esegui nella directory principale del tuo progetto Maven o Gradle come:

add_serialVersionUID.sh < myJavaToAmend.lst

Questo .lst include l'elenco dei file java per aggiungere serialVersionUID nel seguente formato:

com/abc/ic/api/model/domain/item/BizOrderTransDO.java
com/abc/ic/api/model/domain/item/CardPassFeature.java
com/abc/ic/api/model/domain/item/CategoryFeature.java
com/abc/ic/api/model/domain/item/GoodsFeature.java
com/abc/ic/api/model/domain/item/ItemFeature.java
com/abc/ic/api/model/domain/item/ItemPicUrls.java
com/abc/ic/api/model/domain/item/ItemSkuDO.java
com/abc/ic/api/model/domain/serve/ServeCategoryFeature.java
com/abc/ic/api/model/domain/serve/ServeFeature.java
com/abc/ic/api/model/param/depot/DepotItemDTO.java
com/abc/ic/api/model/param/depot/DepotItemQueryDTO.java
com/abc/ic/api/model/param/depot/InDepotDTO.java
com/abc/ic/api/model/param/depot/OutDepotDTO.java

Questo script utilizza lo strumento JDK serialVer sotto cappa. Quindi assicurati che il tuo $ JAVA_HOME / bin sia nel PERCORSO.







serialversionuid