2015年7月30日 星期四

Java上課練習:物件的序列化與反序列化

物件的序列化與反序列化


物件必須實作 Serializable 介面 ( 介面中沒有任何抽象方法需要實作 )

將 Student 類別 加入 Serializable 介面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.Serializable;


public class Student implements Serializable{
    private String name;
    private int eng;
    private int math;

    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + '}';
    }


    }

將 Student 物件 序列化,儲存檔案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class ObjectOutputStreamTest {

    public static void main(String[] args) {
        Student st=new Student("Tom",100,99);
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("C:/Users/Administrator/Desktop/MyJava/student.obj"))){
            oos.writeObject(st);
            System.out.println("Student物件儲存成功");
        }catch(IOException e){
            System.out.println(e);
        }

    }

}

將 Student 物件 反序列化,讀取檔案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStreamTest {

    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:/Users/Administrator/Desktop/MyJava/student.obj"))) {
            Student st = (Student) ois.readObject(); //Object轉型回Student
            System.out.println(st.toString());
        } catch (IOException e) {
            System.out.println(e);
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }

    }

}

若修改Student類別,新增 avg 欄位,計算平均,執行 ObjectInputStreamTest,會出現版號不一致的問題
注意:每個類別都有自己的版本編號
修改後的類別,編譯器自動產生的版本編號, 與先前序列化物件的版本編號不一致,導致反序列化失敗

如何避免版號不一致的情況? 在程式自行指定版號

修改Student類別,加入版號

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.Serializable;


public class Student implements Serializable{
    private static final long serialVersionUID = 1L; //自行指定版號
    private String name;
    private int eng;
    private int math;

    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + '}';
    }
    }

刪除先前儲存的Student物件
重新執行序列化 ObjectOutputStreamTest,產生新的學生物件 (沒有平均成績)
此時修改學生類別,加入平均的程式碼

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.Serializable;


public class Student implements Serializable{ //實作序列化界面
    private static final long serialVersionUID = 1L; //自行指定版號
    private String name;
    private int eng;
    private int math;
    private float avg;
    
    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
        this.avg=(eng+math)/2.0f;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + ", avg=" + avg + '}';
//        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math  + '}';
    }
    }
執行反序列化 ObjectInputStreamTest,讀取先前儲存的學生物件
反序列化成功,但由於先前儲存的學生物件沒有平均,反序列化自動將 avg 設為 0

再度執行序列化 ObjectOutputStreamTest,現在儲存的學生有平均
執行反序列化 ObjectInputStreamTest,即可成功讀取平均成績

因平均不需要設定欄位,可以直接計算,所以使用 transient 宣告,並自定反序列化行為,在 Studnet 類別撰寫自定 readObject( ),反序列化物件時,系統會自動執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;


public class Student implements Serializable{ //實作序列化界面
    private static final long serialVersionUID = 1L; //自行指定版號
    private String name;
    private int eng;
    private int math;
    transient private float avg;//宣告transient,不需要反序列化
    
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException{
        stream.defaultReadObject();//執行反序列化
        this.avg=(eng+math)/2.0f;
    }

    public Student(String name) throws StudentException{
        if(name.length()<2){
            throw new StudentException("名字不能少於兩個字");
        }
        this.name = name;
    }

    public Student(String name, int eng, int math) {
        this(name);//呼叫另一個建構子
        this.eng = eng;
        this.math = math;
//        this.avg=(eng+math)/2.0f;
    }

    @Override
    public String toString() {
        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math + ", avg=" + avg + '}';
//        return "Student{" + "name=" + name + ", eng=" + eng + ", math=" + math  + '}';
    }
    }

沒有留言:

張貼留言