Java基礎22--物件序列化和反序列化

喵言喵語~發表於2020-09-27

Java基礎22–物件的輸出與輸入

物件序列化和反序列化

序列:排隊、
把物件轉為位元組序列,序列化的過程

ObjectOutputStream:用於輸出物件,把物件轉成位元組資料輸出,物件的輸出過程稱為序列化。
ObjectOutputStream比OutputStream多了很多方法,其中一個是 writeObject(obj)

只能將支援 java.io.Serializable 介面的物件寫入流中。每個 serializable 物件的類都被編碼,編碼內容包括類名和類簽名、物件的欄位值和陣列值,以及從初始物件中引用的其他所有物件的閉包。
writeObject 方法用於將物件寫入流中。所有物件(包括 String 和陣列)都可以通過 writeObject 寫入。可將多個物件或基元寫入流中。必須使用與寫入物件時相同的型別和順序從相應 ObjectInputstream 中讀回物件。

ObjectInputstream:用於輸入物件,把位元組序列轉為物件讀取,物件的讀取過程稱為反序列化。
ObjectInputstream比InputStream多了很多方法,其中一個是 Object readObject()

在這裡插入圖片描述
Serializable介面中沒抽象方法,得實現該方法才能ObjectOutputStream輸出

User類

public class User implements Serializable{
	private String name;
	private String password;
	private int age;
	}//物件需要實現序列化
@Test
	public void test01()throws IOException{
		User u = new User("chailinyan","123456",28);
		
		FileOutputStream fos = new FileOutputStream("obj.dat");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		//資料:程式-->oos --> fos -->obj.dat
		oos.writeObject(u);
		/*物件是沒辦法直接變為位元組的,沒有getBytes()方法
		 * 如果沒有實現Serializable介面的型別,在序列化時,報錯誤NotSerializableException,不能序列化。
		 * 如果要解決問題,User類需要實現java.io.Serializable介面
		 */
		
		oos.close();
		fos.close();
	}

讀取物件

	@Test
	public void test02()throws IOException, ClassNotFoundException{
		FileInputStream fis = new FileInputStream("obj.dat");
		ObjectInputStream ois = new ObjectInputStream(fis);
		Object obj = ois.readObject();
		//把位元組流中的資料,轉為一個物件,讀取過程中會建立物件,new物件時需要找物件的型別,若user.java檔案不存在會報錯找不到類
		System.out.println(obj);
		
		ois.close();
		fis.close();
	}

在這裡插入圖片描述

序列化版本ID:serialVersionUID

將物件寫到.dat檔案中去了,然後User類發生改變,如下:

public class User implements Serializable{
	private String name;
	private String password;
	private int age;
	private char gender;
	private String address;
}

這時再執行之前讀物件得程式碼

@Test
	public void test02()throws IOException, ClassNotFoundException{
		FileInputStream fis = new FileInputStream("obj.dat");
		ObjectInputStream ois = new ObjectInputStream(fis);
		
		/*
		 * 當物件已經輸出到檔案中後,修改了類,再次讀取這個檔案時,報InvalidClassException。
		 * 報這個錯的原因是:流中關於類的serialVersionUID與本地類的serialVersionUID對不上,就會報InvalidClassException錯誤,
		 * 如何解決?
		 * (1)修改本地的serialVersionUID為流中的serialVersionUID
		 * (2)或者,在當初實現Serializable介面時,就固定一個serialVersionUID,這樣每次編譯就不會自動生成一個新的serialVersionUID
		 */
		Object obj = ois.readObject();
		//把位元組流中的資料,轉為一個物件,讀取過程中會建立物件,new物件時需要找物件的型別
		System.out.println(obj);
		
		ois.close();
		fis.close();
	}

在這裡插入圖片描述
報錯:無效的類
在這裡插入圖片描述
流裡面得序列化版本和本地的類得序列化版本對不上,因為改變類後本地序列化版本更新了

** 如何解決?**
(1)修改本地的serialVersionUID為流中的serialVersionUID
(2)或者,在當初實現Serializable介面時,就固定一個serialVersionUID,這樣每次編譯就不會自動生成一個新的serialVersionUID
法一:
在這裡插入圖片描述
修改本地的serialVersionUID為流中的serialVersionUID
在這裡插入圖片描述
再次執行能返回值,gender沒有值是因為之前沒寫
法二:

public class User implements Serializable{
	private static final long serialVersionUID = 1L;
	private String name;
	private String password;
	private int age;

在當初實現Serializable介面時,就固定一個serialVersionUID(值隨便取,一般加L,因為數字比較大,為long,user實現序列化時類名下有黃色警告線就是因為一般建議固定一個serialVersionUID),這樣每次編譯就不會自動生成一個新的serialVersionUID,一開始就固定一個serialVersionUID,無論怎麼修改都能找到user

回顧:講自定義異常時:

自定義異常時的要求:
(1)繼承Throwable或它的子類
一般是Exception和RuntimeException比較多
(2)建議保留兩個構造器,一個無參,一個可以message賦值的構造器
(3)建議增加serialVersionUID

public class TestException {

}
class MyException extends Exception{
//繼承異常時也會有黃色線提示,建議固定序列化版本ID
//為什麼這個也會這樣警告:因為Exception繼承了Throwable,而Throwable實現了序列化,所以自定義類也要加序列化版本ID,這樣省的自定義異常類更改會導致一些問題,比如像一些異常會記錄到日誌檔案裡,下次可能要從日誌檔案中恢復一些異常資訊。類更改後可能會導致日誌檔案更改不回來,所以在定義異常時,也加入序列化版本ID
	private static final long serialVersionUID = 1L;

	public MyException() {
		super();
		
	}

	public MyException(String message) {
		super(message);
		
	}
	
}

不序列的欄位:transient和static

需求:
不是物件中的所有屬性都需要序列化的。

如果某個屬性不需要序列化,可以在屬性的前面加一個關鍵字:transient
如果某個屬性是static,那麼也不會序列化。因為靜態的變數不屬於某個物件,而是整個類的,所以不需要隨著物件的序列化而序列化。

transient

示例:
若是隻想把當時的價格和名稱序列化,但當時的銷量不需要序列化,因為如果反序列化回來以後,銷量應該是目前最新的銷量,跟檔案裡的銷量沒關係,不希望銷量序列化

public class Goods implements Serializable{
	private static final long serialVersionUID = 1L;
	private String name;
	private double price;
	private transient int sale;//transient表示sale屬性不需要序列化
	}
@Test
	public void test01()throws IOException{
		//現在要序列化這個產品的資訊,但是希望銷量100不序列化
		Goods g = new Goods("裙子",88.8,100);
		
		FileOutputStream fos = new FileOutputStream("goods.dat");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		oos.writeObject(g);
		
		oos.close();
		fos.close();
	}

讀取物件

	@Test
	public void test02()throws IOException, ClassNotFoundException{
		FileInputStream fis = new FileInputStream("goods.dat");
		ObjectInputStream ois = new ObjectInputStream(fis);
		
		Object obj = ois.readObject();
		System.out.println(obj);
		
		ois.close();
		fis.close();
	}

在這裡插入圖片描述
讀回來了,銷量是0,因為銷量屬性沒有序列化,所以之前的銷量100是沒有寫進去的,銷量沒有序列化讀出來就預設值0

static

加一個靜態屬性品牌

public class Goods implements Serializable{
	private static final long serialVersionUID = 1L;
	private static String brand = "尚矽谷";
	private String name;
	private double price;
	private transient int sale;//transient表示sale屬性不需要序列化

物件寫進去再讀出來結果(反序列化結果):
在這裡插入圖片描述
這時序列化時再改品牌這個屬性:

	Goods.setBrand("atguigu");
@Test
	public void test01()throws IOException{
		Goods.setBrand("atguigu");
		//現在要序列化這個產品的資訊,但是希望銷量100不序列化
		Goods g = new Goods("裙子",88.8,100);
		
		FileOutputStream fos = new FileOutputStream("goods.dat");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		oos.writeObject(g);
		
		oos.close();
		fos.close();
	}

在這裡插入圖片描述
屬性的值沒改變,還是和之前的一樣

物件的引用資料型別屬性都要實現Serializable

在序列化Husband物件,要求Wife序列化。
這裡發現String型別也實現序列化介面了。

結論:
序列化一個物件時,要求它的屬性要麼是基本資料型別,如果是引用資料型別,這個引用資料型別也必須實現Serializable介面。
序列化一個陣列,要求元素型別實現Serializable介面。

示例:

public class Husband implements Serializable{
	private static final long serialVersionUID = 1L;
	private String name;
	private Wife wife;
	public Husband(String name, Wife wife) {
		super();
		this.name = name;
		this.wife = wife;
	}
public class Wife {
	private String name;
	private Husband husband;
	public Wife(String name, Husband husband) {
		super();
		this.name = name;
		this.husband = husband;
	}
	@Test
	public void test01()throws Exception {
		Husband h = new Husband();
		Wife wife = new Wife();
		
		h.setName("崔志恆");
		wife.setName("石榴");
		
		h.setWife(wife);
		wife.setHusband(h);
		
		FileOutputStream fos = new FileOutputStream("marry.dat");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		
		oos.writeObject(h);
		/*
		 * java.io.NotSerializableException: com.atguigu.test10.Wife
		 * 雖然Husband實現了Serializable,但是因為在序列化Husband過程中,包含wife物件,所以要求Wife類也要實現Serializable介面
		 */
		oos.close();
		fos.close();
	}
	報錯:java.io.NotSerializableException: com.atguigu.test10.Wife
	  **雖然Husband實現了Serializable,但是因為在序列化Husband過程中,包含wife物件,所以要求Wife類也要實現Serializable介面**

test2

@Test
	public void test02(){
		Husband h = new Husband();
		Wife wife = new Wife();
		
		h.setName("崔志恆");
		wife.setName("石榴");
		
		h.setWife(wife);
		wife.setHusband(h);
		
		System.out.println(h);
	}

列印husband物件是報錯:StackOverflowError,棧溢位
列印husband物件物件呼叫了toString方法,包含了wife物件,wife物件會呼叫wife的toString方法,wife的toString方法裡又有huaband物件,形成了一個死迴圈
在這裡插入圖片描述

在這裡插入圖片描述
所以toString’方法裡不能直接寫物件,改為husband.getName()和wife.getName()
在這裡插入圖片描述在這裡插入圖片描述

String也是引用資料型別,它也實現了序列化
在這裡插入圖片描述

相關文章