java - type - 可序列化類別未宣告long類型的static final serialversionuid欄位




什麼是serialVersionUID,我為什麼要使用它? (14)

當缺少serialVersionUID時,Eclipse會發出警告。

可序列化類Foo不聲明long類型的靜態最終serialVersionUID字段

什麼是serialVersionUID ,為什麼它很重要? 請顯示缺少serialVersionUID會導致問題的示例。


什麼是serialVersionUID ,我為什麼要使用它?

SerialVersionUID是每個類的唯一標識符, JVM使用它來比較類的版本,確保在反序列化期間加載序列化期間使用相同的類。

指定一個可以提供更多控制,但如果您未指定,JVM會生成一個控件。 生成的值可能因不同編譯器而異。 此外,有時您只是出於某種原因禁止對舊的序列化對象進行反序列化[ backward incompatibility ],在這種情況下,您只需要更改serialVersionUID即可。

Serializablejavadoc

默認的serialVersionUID計算對類詳細信息高度敏感,這些詳細信息可能因編譯器實現而異,因此在反序列化期間可能會導致意外的InvalidClassException

因此,您必須聲明serialVersionUID,因為它為我們提供了更多控制權

本文對該主題有一些好處。


java.io.Serializable的文檔可能與您將得到的解釋一樣好:

序列化運行時將每個可序列化類與版本號相關聯,稱為serialVersionUID ,在反序列化期間使用該版本號來驗證序列化對象的發送方和接收方是否已加載與該序列化兼容的該對象的類。 如果接收者為對象加載了一個類,該類具有與相應發送者類不同的serialVersionUID ,則反序列化將導致InvalidClassException 。 可序列化類可以通過聲明名為serialVersionUID的字段來顯式聲明其自己的serialVersionUID ,該字段必須是static,final和long類型:

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

如果可序列化類未顯式聲明serialVersionUID ,則序列化運行時將基於類的各個方面計算該類的默認serialVersionUID值,如Java(TM)對象序列化規範中所述。 但是, 強烈建議所有可序列化類顯式聲明serialVersionUID值,因為默認的serialVersionUID計算對類詳細信息高度敏感,這些詳細信息可能因編譯器實現而異,因此在反序列化期間可能會導致意外的InvalidClassExceptions 。 因此,為了保證跨不同java編譯器實現的一致serialVersionUID值,可序列化類必須聲明顯式serialVersionUID值。 強烈建議顯式serialVersionUID聲明盡可能使用private修飾符,因為此類聲明僅適用於立即聲明的類serialVersionUID字段作為繼承成員無用。


為什麼在Java中使用SerialVersionUID內部Serializable類?

在此期間serialization,Java運行時為類創建版本號,以便稍後可以對其進行反序列化。此版本號SerialVersionUID在Java中稱為。

SerialVersionUID用於版本化序列化數據。如果類SerialVersionUID與序列化實例匹配,則只能對類進行反序列化。當我們不在SerialVersionUID我們的類中聲明時,Java運行庫為我們生成它,但不推薦它。建議將其聲明SerialVersionUIDprivate static final long變量以避免默認機制。

當您Serializable通過實現標記接口聲明類java.io.Serializable時,Java運行時通過使用默認的序列化機制將該類的實例持久保存到磁盤中,前提是您尚未使用Externalizable接口自定義該過程。

另請參見為什麼在Java中的Serializable類中使用SerialVersionUID

代碼:javassist.SerialVersionUID


不要打擾,默認計算非常好,足以滿足99,9999%的情況。 如果你遇到問題,你可以 - 正如已經說明的那樣 - 引入UID作為需求出現(這是非常不可能的)


如果你在類上得到這個警告,你從未想過序列化,並且你沒有聲明自己implements Serializable ,那通常是因為你繼承了一個實現Serializable的超類。 通常,最好委託給這樣的對象而不是使用繼承。

所以,而不是

public class MyExample extends ArrayList<String> {

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

public class MyExample {
    private List<String> myList;

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

並在相關方法中調用myList.foo()而不是this.foo() (或super.foo() )。 (這並不適用於所有情況,但仍然經常使用。)

我經常看到人們擴展JFrame等,當他們真的只需要委託給它時。 (這也有助於在IDE中自動完成,因為JFrame有數百種方法,當您想在課堂上調用自定義方法時,您不需要這些方法。)

警告(或serialVersionUID)不可避免的一種情況是,從AbstractAction擴展(通常在匿名類中),只添加actionPerformed-method。 我認為在這種情況下不應該有警告(因為你通常無法在類的不同版本中對這些匿名類進行可靠的序列化和反序列化),但我不確定編譯器如何識別它。


如果你的序列化只是因為你必須為了實現而序列化(誰關心你是否為HTTPSession序列化,例如......如果它存儲與否,你可能不關心反序列化表單對象)那麼你可以忽略這個。

如果您實際使用的是序列化,那麼只有在您計劃直接使用序列化存儲和檢索對象時才有意義。 serialVersionUID表示您的類版本,如果您的類的當前版本與其先前版本不向後兼容,則應增加它。

大多數情況下,您可能不會直接使用序列化。 如果是這種情況,請單擊快速修復選項生成默認的可序列化uid,不要擔心。


字段數據表示存儲在類中的一些信息。 Class實現了Serializable接口,因此eclipse自動提供聲明serialVersionUID字段。 讓我們從那裡開始設置值1。

如果您不希望發出此警告,請使用以下命令:

@SuppressWarnings("serial")

您可以告訴Eclipse忽略這些serialVersionUID警告:

窗口>首選項> Java>編譯器>錯誤/警告>潛在的編程問題

如果你不知道,你可以在本節中啟用很多其他警告(甚至有一些報告為錯誤),許多非常有用:

  • 潛在的編程問題:可能的意外布爾賦值
  • 潛在的編程問題:空指針訪問
  • 不必要的代碼:永遠不會讀取局部變量
  • 不必要的代碼:冗餘空值檢查
  • 不必要的代碼:不必要的演員或'instanceof'

還有很多。


我通常在一個上下文中使用serialVersionUID :當我知道它將離開Java VM的上下文時。

當我為我的應用程序使用ObjectInputStreamObjectOutputStream時,我知道這一點,或者如果我知道我使用的庫/框架將使用它。 serialVersionID確保不同版本或供應商的不同Java VM將正確地互操作,或者如果它在VM外部存儲和檢索,例如HttpSession ,即使在重新啟動和升級應用程序服務器期間,會話數據也可以保留。

對於所有其他情況,我使用

@SuppressWarnings("serial")

因為大多數時候默認的serialVersionUID就足夠了。 這包括ExceptionHttpServlet


至於缺少serialVersionUID可能導致問題的示例:

我正在研究這個Java EE應用程序,它由一個使用EJB模塊的Web模塊組成。 Web模塊遠程調用EJB模塊並傳遞實現Serializable作為參數的POJO

這個POJO's類被封裝在EJB jar中,並且在web模塊的WEB-INF / lib中的自己的jar中。 它們實際上是同一個類,但是當我打包EJB模塊時,我解壓縮這個POJO的jar以將它與EJB模塊一起打包。

由於我沒有聲明它的serialVersionUID ,因此對EJB的調用失敗了,因為我沒有聲明它的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

首先,我需要解釋序列化。
序列化允許將對象轉換為流,通過網絡發送該對像或保存到文件或保存到DB以供字母使用。

序列化有一些規則

  • 僅當對象的類或其超類實現Serializable接口時,該對象才可序列化

  • 對像是可序列化的(本身實現了Serializable接口),即使它的超類不是。 但是,可序列化類的層次結構中的第一個超類(不實現Serializable接口)必須具有無參數構造函數。 如果違反了此規則,readObject()將在運行時生成java.io.InvalidClassException

  • 所有原始類型都是可序列化的。

  • 瞬態字段(具有瞬態修飾符)未被序列化(即,未保存或恢復)。 實現Serializable的類必須標記不支持序列化的類的瞬態字段(例如,文件流)。

  • 靜態字段(帶有靜態修飾符)未序列化。

對象序列化時JAVA Runtime關聯稱為serialVersionID的序列版本號。

我們需要serialVersionID的地方:在反序列化期間驗證發送方和接收方是否與序列化兼容。如果接收方加載了具有不同serialVersionID的類,則反序列化將以InvalidClassCastException結束。
可序列化類可以通過聲明名為“serialVersionUID”的字段來顯式聲明其自己的serialVersionUID,該字段必須是static,final和long類型:

讓我們試試這個例子吧。

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

創建序列化對象

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對象

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

注意:現在更改Employee類的serialVersionUID並保存:

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

並執行Reader類。不執行Writer類,您將獲得異常。

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)

SerialVersionUID用於對象的版本控制。您也可以在類文件中指定serialVersionUID。不指定serialVersionUID的後果是,當您在類中添加或修改任何字段時,已經序列化的類將無法恢復,因為為新類和舊序列化對像生成的serialVersionUID將不同。Java序列化過程依賴於正確的serialVersionUID來恢復序列化對象的狀態,並在serialVersionUID不匹配的情況下拋出java.io.InvalidClassException

閱讀更多:http://javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZhttp://javarevisited.blogspot.com/2011/04/top-10-java-serialization-interview.html#ixzz3VQxnpOPZ


如果你想修改大量沒有設置serialVersionUID的類,同時保持與舊類的兼容性,那麼像IntelliJ Idea,Eclipse這樣的工具會因為生成隨機數而無法在一堆文件上工作。一氣呵成。我提出了以下bash腳本(我很抱歉Windows用戶,考慮購買Mac或轉換為Linux)以輕鬆修改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

你保存這個腳本,比如說add_serialVersionUID.sh給〜/ bin。然後在Maven或Gradle項目的根目錄中運行它,如:

add_serialVersionUID.sh < myJavaToAmend.lst

此.lst包含以下列格式添加serialVersionUID的java文件列表:

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

此腳本使用引擎蓋下的JDK serialVer工具。因此,請確保您的$ JAVA_HOME / bin位於PATH中。


每次對像被序列化時,對像都會標記對像類的版本ID號。此ID稱為serialVersionUID,它是根據有關類結構的信息計算的。假設您創建了一個Employee類,它的版本號為#333(由JVM分配),現在當您序列化該類的對象(假設Employee對象)時,JVM將為它分配UID為#333。

考慮一種情況 - 將來您需要編輯或更改您的類,在這種情況下,當您修改它時,JVM將為其分配一個新的UID(假設#444)。現在,當您嘗試反序列化employee對象時,JVM會將序列化對象的(Employee對象)版本ID(#333)與類的名稱進行比較,即#444(因為它已更改)。相比之下,JVM會發現兩個版本的UID都不同,因此反序列化將失敗。因此,如果每個類的serialVersionID由程序員自己定義。即使該類在將來進化,它也是相同的,因此即使類被更改,JVM也總是會發現該類與序列化對象兼容。有關更多信息,請參閱HEAD FIRST JAVA的第14章。





serialversionuid