在持久化數據對象的時候咱們不多使用Java序列化,而是使用數據庫等方式來實現。可是在我看來,Java 序列化是一個很重要的內容,序列化不只能夠保存對象到磁盤進行持久化,還能夠經過網絡傳輸。在平時的面試當中,序列化也是常常被談及的一塊內容。java
談到序列化時,你們可能知道將類實現Serializable接口就能夠達到序列化的目的,但當看到關於序列化的面試題時咱們卻經常一臉懵逼。 程序員
1)可序列化接口和可外部接口的區別是什麼?面試
2)序列化時,你但願某些成員不要序列化?該如何實現?數據庫
3)什麼是 serialVersionUID ?若是不定義serialVersionUID,會發生什麼?網絡
是否是忽然發現咱們對這些問題其實都還存在不少疑惑?本文將總結一些Java序列化的常見問題,而且經過demo來進行測試和解答。ide
序列化是把對象改爲能夠存到磁盤或經過網絡發送到其它運行中的 Java 虛擬機的二進制格式的過程,並能夠經過反序列化恢復對象狀態。Java 序列化API給開發人員提供了一個標準機制:經過實現 java.io.Serializable 或者 java.io.Externalizable 接口,ObjectInputStream 及ObjectOutputStream 處理對象序列化。實現java.io.Externalizable 接口的話,Java 程序員可自由選擇基於類結構的標準序列化或是它們自定義的二進制格式,一般認爲後者纔是最佳實踐,由於序列化的二進制文件格式成爲類輸出 API的一部分,可能破壞 Java 中私有和包可見的屬性的封裝。測試
序列化到底有什麼用?this
實現 java.io.Serializable。spa
定義用戶類:code
class User implements Serializable { private String username; private String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } }
咱們把對象序列化,經過ObjectOutputStream存儲到txt文件中,再經過ObjectInputStream讀取txt文件,反序列化成User對象。
public class TestSerialize { public static void main(String[] args) { User user = new User(); user.setUsername("hengheng"); user.setPasswd("123456"); System.out.println("read before Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); try { ObjectOutputStream os = new ObjectOutputStream( new FileOutputStream("/Users/admin/Desktop/test/user.txt")); os.writeObject(user); // 將User對象寫進文件 os.flush(); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream is = new ObjectInputStream(new FileInputStream( "/Users/admin/Desktop/test/user.txt")); user = (User) is.readObject(); // 從流中讀取User的數據 is.close(); System.out.println("\nread after Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
運行結果以下:
序列化前數據: username: hengheng password: 123456 序列化後數據: username: hengheng password: 123456
到這裏,咱們大概知道了什麼是序列化。
答案:聲明該成員爲靜態或瞬態,在 Java 序列化過程當中則不會被序列化。
咱們先嚐試把變量聲明爲瞬態。
class User implements Serializable { private String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; }
在密碼字段前加上了transient關鍵字再運行。運行結果:
序列化前數據: username: hengheng password: 123456 序列化後數據: username: hengheng password: null
經過運行結果發現密碼沒有被序列化,達到了咱們的目的。
再嘗試在用戶名前加static關鍵字。
class User implements Serializable { private static String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; }
運行結果:
序列化前數據: username: hengheng password: 123456 序列化後數據: username: hengheng password: null
咱們發現運行後的結果和預期的不同,按理說username也應該變爲null纔對。是什麼緣由呢?
緣由是:反序列化後類中static型變量username的值爲當前JVM中對應的靜態變量的值,而不是反序列化得出的。
咱們來證實一下:
public class TestSerialize { public static void main(String[] args) { User user = new User(); user.setUsername("hengheng"); user.setPasswd("123456"); System.out.println("序列化前數據: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); try { ObjectOutputStream os = new ObjectOutputStream( new FileOutputStream("/Users/admin/Desktop/test/user.txt")); os.writeObject(user); // 將User對象寫進文件 os.flush(); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } User.username = "小明"; try { ObjectInputStream is = new ObjectInputStream(new FileInputStream( "/Users/admin/Desktop/test/user.txt")); user = (User) is.readObject(); // 從流中讀取User的數據 is.close(); System.out.println("\n序列化後數據: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class User implements Serializable { public static String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } }
在反序列化前把靜態變量username的值改成『小明』。
User.username = "小明";
再運行一次:
序列化前數據: username: hengheng password: 123456 序列化後數據: username: 小明 password: null
果真,這裏的username是JVM中靜態變量的值,並非反序列化獲得的值。
咱們常常會在類中自定義一個serialVersionUID:
private static final long serialVersionUID = 8294180014912103005L
這個serialVersionUID有什麼用呢?若是不設置的話會有什麼後果?
serialVersionUID 是一個 private static final long 型 ID,當它被印在對象上時,它一般是對象的哈希碼。serialVersionUID能夠本身定義,也能夠本身去生成。
不指定 serialVersionUID的後果是:當你添加或修改類中的任何字段時,已序列化類將沒法恢復,由於新類和舊序列化對象生成的 serialVersionUID 將有所不一樣。Java 序列化的過程是依賴於正確的序列化對象恢復狀態的,並在序列化對象序列版本不匹配的狀況下引起 java.io.InvalidClassException 無效類異常。
舉個例子你們就明白了:
咱們保持以前保存的序列化文件不變,而後修改User類。
class User implements Serializable { public static String username; private transient String passwd; private String age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
加了一個屬性age,而後單另寫一個反序列化的方法:
public static void main(String[] args) { try { ObjectInputStream is = new ObjectInputStream(new FileInputStream( "/Users/admin/Desktop/test/user.txt")); User user = (User) is.readObject(); // 從流中讀取User的數據 is.close(); System.out.println("\n修改User類以後的數據: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
報錯了,咱們發現以前的User類生成的serialVersionUID和修改後的serialVersionUID不同(由於是經過對象的哈希碼生成的),致使了InvalidClassException異常。
自定義serialVersionUID:
class User implements Serializable { private static final long serialVersionUID = 4348344328769804325L; public static String username; private transient String passwd; private String age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
再試一下:
序列化前數據: username: hengheng password: 123456 序列化後數據: username: 小明 password: null
運行結果無報錯,因此通常都要自定義serialVersionUID。
答案固然是能夠的。
以前咱們介紹了序列化的第二種方式:
實現Externalizable接口,而後重寫writeExternal() 和readExternal()方法,這樣就能夠自定義序列化。
好比咱們嘗試把變量設爲瞬態。
public class ExternalizableTest implements Externalizable { private transient String content = "我是被transient修飾的變量哦"; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(content); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { content = (String) in.readObject(); } public static void main(String[] args) throws Exception { ExternalizableTest et = new ExternalizableTest(); ObjectOutput out = new ObjectOutputStream(new FileOutputStream( new File("test"))); out.writeObject(et); ObjectInput in = new ObjectInputStream(new FileInputStream(new File( "test"))); et = (ExternalizableTest) in.readObject(); System.out.println(et.content); out.close(); in.close(); } }
運行結果:
我是被transient修飾的變量哦
這裏實現的是Externalizable接口,則沒有任何東西能夠自動序列化,須要在writeExternal方法中進行手工指定所要序列化的變量,這與是否被transient修飾無關。
經過上述介紹,是否是對Java序列化有了更多的瞭解?
做者:楊亨來源:宜信技術學院