การทำงานของตัวแปร serialVersionUID บทที่ 2

เมื่อวานผมได้ยินปัญหาจากคนรู้จักว่าเขาเจอเรื่อง Call EJB แล้วมีการส่ง Object ที่มีฟิลด์ไม่เท่ากัน ผลคือไม่สามารถแปลงกลับมาเป็น Object ได้ ทำให้ผมนึกถึงกาลครั้งหนึ่งเมื่อหกปีก่อน ผมเคยเขียนบทความเรื่องการทำงานของตัวแปร serialVersionUID แต่ต่างจากบทความผมตรงที่เขาไม่ได้มีการประกาศตัวแปรไว้ ผมก็เลยตั้งใจจะเรียบเรียงบทความนี้ใหม่ แล้วเพิ่มเนื้อหาในส่วนที่ผมเพิ่งเจอเข้าไปด้วยเพื่อเป็นบันทึกเก็บไว้อ้างอิงสำหรับกรณี

  • Bean มีฟิลด์ไม่เท่ากันและไม่ได้มีการประกาศตัวแปร serialVersionUID
  • Bean มีฟิลด์ไม่เท่ากันแต่มีการประกาศตัวแปร serialVersionUID

ปรับปรุงโค้ดใหม่

คลาส Charactor ผมยังใช้ของเดิมอยู่

package test;

import java.io.Serializable;

public class Character implements Serializable {

	public String name;
	public int score;
	
}

และแก้โค้ดคลาส Main เนื่องจากโค้ดเดิมมันยาวเพราะมี try-catch ผมจึงรื้อใหม่ได้ดังนี้

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main {

	public static void main(String[] args) throws Exception {
		// สร้าง object ตัวละคร
		Character character = new Character();
		character.name = "magicalcyber";
		character.score = 108;

		saveCharacter(character);

		loadCharacter();
	}

	public static void saveCharacter(Character character) throws Exception {
		System.out.println("Saving...");
		System.out.println("name: " + character.name + "\tScore: " + character.score);

		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("output", "save.dat")));
		oos.writeObject(character);
		oos.close();
	}

	public static void loadCharacter() throws Exception {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("output", "save.dat")));
		Character character = (Character) ois.readObject();
		ois.close();

		System.out.println("Loading...");
		System.out.println("name: " + character.name + "\tScore: " + character.score);

	}
}

กรณี Bean มีฟิลด์ไม่เท่ากันและไม่ได้มีการประกาศตัวแปร serialVersionUID

  1. ทำการ run โปรแกรมเพื่อให้มีการบันทึกข้อมูลลงไฟล์
  2. ทำการเพิ่มตัวแปรในคลาส Charactor ขึ้นมาหนึ่งตัว โดยสมมุติว่าผมเพิ่มค่าสถานะความแข็งแกร่งของตัวละครดังนี้
    package test;
    
    import java.io.Serializable;
    
    public class Character implements Serializable {
    
    	public String name;
    	public int score;
    	public int str;
    	
    }
    
  3. ทำการ comment ส่วนของการบันทึกค่า เพราะเราต้องการทดสอบว่าเราจะสามารถโหลดไฟล์เดิมขึ้นมา แล้วแปลงเป็น Object ที่เราเพิ่มตัวแปรใหม่ได้หรือเปล่า

    จากนั้นทำการ run อีกครั้ง ผลที่ได้คือไม่สามารถแปลงได้ครับ โดยขึ้น Error ว่า

    Exception in thread “main” java.io.InvalidClassException: test.Character; local class incompatible: stream classdesc serialVersionUID = 2049378085544501727, local class serialVersionUID = 7288918648091706575

    error แจ้งว่าหมายเลข serialVersionUID ตอนบันทึกไม่ตรงกับ object ที่เราประกาศมารับ โจทย์คือมันเอาหมายเลขนี้มาได้อย่างไร?
    ซึ่งคำตอบของเราได้เฉลยให้แล้วใน Stacktrace อันสุดท้าย

    1. มันมีการ compare ค่าตัวแปร serialVersionUID อยู่ข้างในนั้น

    2. เมื่อเรียกเมธอดเพื่อดึงค่าตัวแปร หากไม่ได้ประกาศไว้ มันก็จะ generate ให้ใหม่

กรณี Bean มีฟิลด์ไม่เท่ากันแต่มีการประกาศตัวแปร serialVersionUID

จากบทสรุปหัวข้อก่อนหน้านี้คงจะพอเดาได้ไม่ยากว่า หากเรา fix ค่า serialVersionUID เอาไว้ ต่อให้เราเพิ่มลบตัวแปรเท่าไหร่มันก็ยังเป็นค่าเดิม ก็ยังคงโหลดและแปลงเป็นน Object ได้เสมอ โดยจะมีการกำหนดค่าเริ่มต้นเป็น default value ของแต่ละ type

  1. เปิดคลาส Character ทำการ comment ตัวแปรใหม่ไว้ก่อน แล้วเพิ่มตัวแปร serialVersionUID ดังนี้
    package test;
    
    import java.io.Serializable;
    
    public class Character implements Serializable {
    
    	private static final long serialVersionUID = 1L;
    	
    	public String name;
    	public int score;
    //	public int str;
    	
    }
    
  2. เปิดคลาส Main ทำการยกเลิก comment บรรทัด saveCharacter แล้วทำการรันเพื่อบันทึกข้อมูลลงไฟล์แบบที่มีตัวแปร serialVersionUID และยังไม่ได้เพิ่มตัวแปรใหม่

  3. เปิด comment ตัวแปรในคลาส Character เพื่อให้มีตัวแปรใหม่เพิ่มขึ้นมาจากเดิม และปิด comment เมธอด saveCharacter ในคลาส Main เพื่อทำการโหลดอย่างเดียวแล้วรัน ก็จะสามารถโหลดได้ตามปกติ

สรุป

กฎเหล็กของการเขียนโปรแกรมข้าม network คือ

ทุก Class ที่ต้องมีการส่ง object ข้าม network จะต้องระบุค่าตัวแปร serialVersionUID เสมอ

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s