1. 程式人生 > >Think In Java——序列化和反序列化

Think In Java——序列化和反序列化

1)Java中的Serializable介面和Externalizable介面有什麼區別?

這個是面試中關於Java序列化問的最多的問題。我的回答是,Externalizable介面提供了兩個方法writeExternal()和readExternal()。這兩個方法給我們提供了靈活處理Java序列化的方法,通過實現這個介面中的兩個方法進行物件序列化可以替代Java中預設的序列化方法。正確的實現Externalizable介面可以大幅度的提高應用程式的效能。

2)Serializable介面中有借個方法?如果沒有方法的話,那麼這麼設計Serializable介面的目的是什麼?

Serializable介面在java.lang包中,是Java序列化機制的核心組成部分。它裡面沒有包含任何方法,我們稱這樣的介面為標識介面。如果你的類實現了Serializable介面,這意味著你的類被打上了“可以進行序列化”的標籤,並且也給了編譯器指示,可以使用序列化機制對這個物件進行序列化處理。

3)什麼是serialVersionUID?如果你沒有定義serialVersionUID意味著什麼?

SerialVersionUID應該是你的類中的一個publicstatic final型別的常量,如果你的類中沒有定義的話,那麼編譯器將丟擲警告。如果你的類中沒有制定serialVersionUID,那麼Java編譯器會根據類的成員變數和一定的演算法生成用來表達物件的serialVersionUID ,通常是用來表示類的雜湊值(hash code)。結論是,如果你的類沒有實現SerialVersionUID,那麼如果你的類中如果加入或者改變成員變數,那麼已經序列化的物件將無法反序列化。這是以為,類的成員變數的改變意味這編譯器生成的SerialVersionUID的值不同。Java序列化過程是通過正確SerialVersionUID來對已經序列化的物件進行狀態恢復。

4)當物件進行序列化的時候,如果你不希望你的成員變數進行序列化,你怎麼辦?

這個問題也會這麼問,如何使用暫態型別的成員變數?暫態和靜態成員變數是否會被序列化等等。如果你不希望你的物件中的成員變數的狀態得以儲存,你可以根據需求選擇transient或者static型別的變數,這樣的變數不參與Java序列化處理的過程。

[ 但可能出現一些詭異的事情,現象和解釋如下 ]

在讀原始碼的時候看到了一個 transient 修飾的變數 ,字面意思是瞬變的。在以前的開發過程中也沒用到過這個修飾語,查了一下這個修飾語的作用為使被 transient 修飾的變數在序列化的時候不會被

儲存到檔案中,也就是通過序列化後再被反序列化後讀取這個變數不會有值,下面是演示例項:

  實體類:   public class User implements Serializable{         private static final long serialVersionUID = 1L;         private String name;         private transient String password;   //被transient修飾的變數         private static String age;          }

  測試類:

  public class Test {            public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException {           User user = new User();           user.setAge("22");           user.setName("小明");           user.setPassword("admin");           System.out.println(user.getAge()+"\t"+user.getName()+"\t"+user.getPassword());           ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("e:/user.txt"));           oos.writeObject(user);           oos.flush();           oos.close();                    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/user.txt"));           User users = (User) ois.readObject();                    System.out.println(users.getAge()+"\t"+users.getName()+"\t"+users.getPassword());        }   }

從執行結果可以看出用  transient 修飾的變數在反序列化後值為 null

 被static修飾的變數應該也是不會被序列化的,因為只有堆記憶體會被序列化.所以靜態變數會天生不會被序列化。

那這裡被static修飾的變數反序列化後有值又是什麼鬼 這是因為    靜態變數在方法區,本來流裡面就沒有寫入靜態變數,我們列印靜態變數當然會去方法區查詢,我們當前 jvm 中有所以靜態變數在序列化後任然有值。

  接著進行對 static  修飾的變數的驗證:

public class Test {          public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException {         User user = new User();         user.setAge("22");         user.setName("小明");         user.setPassword("admin");         System.out.println(user.getAge()+"\t"+user.getName()+"\t"+user.getPassword());         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("e:/user.txt"));         user.setAge("33"); //在序列化後在對static修飾的變數進行一次賦值操作         oos.writeObject(user);         oos.flush();         oos.close();                  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/user.txt"));         User users = (User) ois.readObject();                  System.out.println(users.getAge()+"\t"+users.getName()+"\t"+users.getPassword());              } }

執行結果如下:

    可以看到在序列化前 static 修飾的變數賦值為22,而反序列化後讀取的這個變數值為33,由此可以看出 static 修飾的變數本身是不會被序列化的

  我們讀取的值是當前jvm中的方法區對應此變數的值,所以最後輸出的值為我們對static 變數後賦的值

]

5)如果一個類中的成員變數是其它符合型別的Java類,而這個類沒有實現Serializable介面,那麼當物件序列化的時候會怎樣?

如果你的一個物件進行序列化,而這個物件中包含另外一個引用型別的成員程式設計,而這個引用的類沒有實現Serializable介面,那麼當物件進行序列化的時候會丟擲“NotSerializableException“的執行時異常。

6)如果一個類是可序列化的,而他的超類沒有,那麼當進行反序列化的時候,那些從超類繼承的例項變數的值是什麼?

還是超類的值

7)你能夠自定義序列化處理的程式碼嗎或者你能過載Java中預設的序列化方法嗎?

答案是肯定的,可以。我們都知道可以通過ObjectOutputStream中的writeObject()方法寫入序列化物件,通過ObjectInputStream中的readObject()讀入反序列化的物件。這些都是Java虛擬機器提供給你的兩個方法。如果你在你的類中定義了這兩個方法,那麼JVM就會用你的方法代替原有預設的序列化機制的方法。你可以通過這樣的方式類自定義序列化和反序列化的行為。需要注意的一點是,最好將這兩個方法定義為private,以防止他們被繼承、重寫和過載。也只有JVM可以訪問到你的類中所有的私有方法,你不用擔心方法私有不會被呼叫到,Java序列化過程會正常工作。

8)假設一個新的類的超類實現了Serializable介面,那麼如何讓這個新的子類不被序列化?

如果一個超類已經序列化了,那麼無法通過是否實現什麼介面的方式再避免序列化的過程了,但是也還有一種方式可以使用。那就是需要你在你的類中重新實現writeObject()和readObject()方法,並在方法實現中通過丟擲NotSerializableException。

9)在Java進行序列化和反序列化處理的時候,哪些方法被使用了?

這個是面試中常見的問題,主要用來考察你是否對readObject()、writeObject()、readExternal()和writeExternal()方法的使用熟悉。Java序列化是通過java.io.ObjectOutputStream這個類來完成的。這個類是一個過濾器流,這個類完成對底層位元組流的包裝來進行序列化處理。我們通過ObjectOutputStream.writeObject(obj)進行序列化,通過ObjectInputStream.readObject()進行反序列化。對writeObject()方法的呼叫會觸發Java中的序列化機制。readObject()方法用來將已經持久化的位元組資料反向建立Java物件,該方法返回Object型別,需要強制轉換成你需要的正確型別。

10)假設你有一個類並且已經將這個類的某一個物件序列化儲存了,那麼如果你在這個類中加入了新的成員變數,那麼在反序列化剛才那個已經存在的物件的時候會怎麼樣?

這個取決於這個類是否有serialVersionUID成員。通過上面的,我們已經知道如果你的類沒有提供serialVersionUID,那麼編譯器會自動生成,而這個serialVersionUID就是物件的hash code值。那麼如果加入新的成員變數,重新生成的serialVersionUID將和之前的不同,那麼在進行反序列化的時候就會產生java.io.InvalidClassException的異常。這就是為什麼要建議為你的程式碼加入serialVersionUID的原因所在了。

11)JAVA反序列化時會將NULL值變成""字元!!