[TOC]java
咱們知道,若是一個對象須要序列化,那麼須要實現Serilizable
接口,那麼這個類的全部非靜態屬性,都會被序列化。數組
注意:上面說的是非靜態屬性,由於靜態屬性是屬於類的,而不是屬於類對象的,而序列化是針對類對象的操做,因此這個根本不會序列化。下面咱們能夠實驗一下:
實體類Teacher.class
:ide
import java.io.Serializable; class Teacher implements Serializable { public int age; public static String SchoolName; public Teacher(int age) { this.age = age; } @Override public String toString() { return "Teacher{" + "age=" + age + '}'; } }
測試代碼SerialTest.java
,基本思路就是初始化的時候,靜態屬性SchoolName
爲"東方小學",序列化對象以後,將靜態屬性修改,而後,反序列化,發現其實靜態變量仍是修改以後的,說明靜態變量並無被序列化。學習
import java.io.*; public class SerialTest { public static void main(String[] args) { Teacher.SchoolName = "東方小學"; serial(); Teacher.SchoolName = "西方小學"; deserial(); System.out.println(Teacher.SchoolName); } // 序列化 private static void serial(){ try { Teacher teacher = new Teacher(9); FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt"); ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(teacher); objectOutputStream.flush(); } catch (Exception exception) { exception.printStackTrace(); } } // 反序列化 private static void deserial() { try { FileInputStream fis = new FileInputStream("Teacher.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Teacher teacher = (Teacher) ois.readObject(); ois.close(); System.out.println(teacher.toString()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
輸出的結果,證實靜態變量沒有被序列化!!!測試
Teacher{age=9} 西方小學
Serilizable
接口?忽然想到一個問題,若是有些屬性是對象,而不是基本類型,需不須要改屬性的類型也實現Serilizable
呢?this
問題的答案是:須要!!!code
下面是實驗過程:對象
首先,有一個Teacher.java
,實現了Serializable
,裏面有一個屬性是School
類型:接口
import java.io.Serializable; class Teacher implements Serializable { public int age; public School school; public Teacher(int age) { this.age = age; } @Override public String toString() { return "Teacher{" + "age=" + age + '}'; } }
School
類型,不實現Serializable
:ci
public class School { public String name; public School(String name) { this.name = name; } @Override public String toString() { return "School{" + "name='" + name + '\'' + '}'; } }
測試代碼,咱們只測試序列化:
import java.io.*; public class SerialTest { public static void main(String[] args) { serial(); } private static void serial(){ try { Teacher teacher = new Teacher(9); teacher.school = new School("東方小學"); FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt"); ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(teacher); objectOutputStream.flush(); } catch (Exception exception) { exception.printStackTrace(); } } }
會發現報錯了,報錯的緣由是:School不能被序列化,也就是沒有實現序列化接口,因此若是咱們想序列化一個對象,那麼這個對象的屬性也必須是可序列化的,或者它是transient
修飾的。
java.io.NotSerializableException: com.aphysia.transienttest.School at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at com.aphysia.transienttest.SerialTest.serial(SerialTest.java:18) at com.aphysia.transienttest.SerialTest.main(SerialTest.java:9)
當咱們將School
實現序列化接口的時候,發現一切就正常了...問題完美解決
可是若是有一個變量不是靜態變量,可是咱們也不想序列化它,由於它多是一些密碼等敏感的字段,或者它是不那麼重要的字段,咱們不但願增長報文大小,因此想在序列化報文中排除該字段。或者改字段存的是引用地址,不是真正重要的數據,好比ArrayList
裏面的elementData
。
這個時候就須要使用transient
關鍵字,將改字段屏蔽。
當咱們用transient
修飾School
的時候:
import java.io.Serializable; class Teacher implements Serializable { public int age; public transient School school; public Teacher(int age) { this.age = age; } @Override public String toString() { return "Teacher{" + "age=" + age + ", school=" + school + '}'; } }
import java.io.Serializable; public class School implements Serializable { public String name; public School(String name) { this.name = name; } @Override public String toString() { return "School{" + "name='" + name + '\'' + '}'; } }
執行下面序列化和反序列化的代碼:
import java.io.*; public class SerialTest { public static void main(String[] args) { serial(); deserial(); } private static void serial(){ try { Teacher teacher = new Teacher(9); teacher.school = new School("東方小學"); FileOutputStream fileOutputStream = new FileOutputStream("Teacher.txt"); ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(teacher); objectOutputStream.flush(); } catch (Exception exception) { exception.printStackTrace(); } } private static void deserial() { try { FileInputStream fis = new FileInputStream("Teacher.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Teacher teacher = (Teacher) ois.readObject(); ois.close(); System.out.println(teacher.toString()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
執行結果以下,能夠看到teacher
字段反序列化出來,實際上是null,這也是transient
起做用了。
可是注意,transient
只能修飾變量,可是不能修飾類和方法,
ArrayList
裏面的elementData
都被transient
關鍵字修飾了,爲何ArrayList
還能夠序列化呢?這裏提一下,既然transient
修飾了ArrayList
的數據節點,那麼爲何序列化的時候咱們仍是能夠看到ArrayList
的數據節點呢?
這是由於序列化的時候:
若是僅僅實現了Serializable
接口,那麼序列化的時候,確定是調用java.io.ObjectOutputStream.defaultWriteObject()
方法,將對象序列化。而後若是是transient
修飾了該屬性,確定該屬性就不能序列化。
可是,若是咱們雖然實現了Serializable
接口,也transient
修飾了該屬性,該屬性確實不會在默認的java.io.ObjectOutputStream.defaultWriteObject()
方法裏面被序列化了,可是咱們能夠重寫一個writeObject()
方法,這樣一來,序列化的時候調用的就是writeObject()
,而不是java.io.ObjectOutputStream.defaultWriteObject()
。
下面的源碼是ObjectInputStream.writeObject(Object obj)
,裏面底層其實會有反射的方式調用到重寫的對象的writeObject()
方法,這裏不作展開。
public final void writeObject(Object obj) throws IOException { // 若是能夠被重寫,那麼就會調用重寫的方法 if (enableOverride) { writeObjectOverride(obj); return; } try { writeObject0(obj, false); } catch (IOException ex) { if (depth == 0) { writeFatalException(ex); } throw ex; } }
ArrayList
重寫的writeOject()
方法以下:
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff int expectedModCount = modCount; // 默認的序列化對象的方法 s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { // 序列化對象的值 s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
咱們能夠看到,writeOject()
裏面其實在裏面調用了默認的方法defaultWriteObject()
,defaultWriteObject()
底層實際上是調用改了writeObject0()
。ArrayList
重寫的writeOject()
的思路主要是先序列化默認的,而後序列化數組大小,再序列化數組elementData
裏面真實的元素。這就達到了序列化元素真實內容的目的。
且慢,問出這個問題,答案確定是有的!!!那就是Externalizable接口。
具體狀況:Externalizable
意思就是,類裏面有不少不少屬性,可是我只想要一部分,要屏蔽大部分,那麼我不想在大部分的屬性前面加關鍵字transient
,我只想標識一下本身序列化的字段,這個時候就須要使用Externalizable
接口。
show me the code!
首先定義一個Person.java
,裏面有三個屬性
transient
修飾)實現了Externalizable
接口,就必須實現writeExternal()
和readExternal()
方法。
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class Person implements Externalizable { public int age; public transient String name; public int score; // 必須實現無參構造器 public Person() { } public Person(int age, String name, int score) { this.age = age; this.name = name; this.score = score; } @Override public void writeExternal(ObjectOutput out) throws IOException { /* * 指定序列化時候寫入的屬性。這裏不寫入score */ out.writeObject(age); out.writeObject(name); out.writeObject(score); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { /* * 指定序列化時候寫入的屬性。這裏仍然不寫入年齡 */ this.age = (int)in.readObject(); this.name = (String)in.readObject(); } @Override public String toString() { return "Person{" + "age=" + age + ", name='" + name + '\'' + ", score='" + score + '\'' + '}'; } }
上面的代碼,咱們能夠看出,序列化的時候,將三個屬性都寫進去了,可是反序列化的時候,咱們僅僅還原了兩個,那麼咱們來看看測試的代碼:
import java.io.*; public class ExternalizableTest { public static void main(String[] args) { serial(); deserial(); } private static void serial(){ try { Person person = new Person(9,"Sam",98); FileOutputStream fileOutputStream = new FileOutputStream("person.txt"); ObjectOutputStream objectOutputStream= new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); objectOutputStream.flush(); } catch (Exception exception) { exception.printStackTrace(); } } private static void deserial() { try { FileInputStream fis = new FileInputStream("person.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Person person = (Person) ois.readObject(); ois.close(); System.out.println(person); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
測試結果以下,就能夠發現其實前面兩個都反序列化成功了,後面那個是由於咱們重寫的時候,沒有自定義該屬性的反序列化,因此沒有是正常的啦...
Person{age=9, name='Sam', score='0'}
若是細心點,能夠發現,有一個字段是transient
修飾的,不是說修飾了,就不會被序列化麼,怎麼序列化出來了。
沒錯,只要實現了Externalizable
接口,其實就不會被transient
左右了,只會按照咱們自定義的字段進行序列化和反序列化,這裏的transient
是無效的...
關於序列化的transient
暫時到這,keep going~
此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~
技術之路不在一時,山高水長,縱使緩慢,馳而不息。
公衆號:秦懷雜貨店