บทนำสู่ Java Serialization

1. บทนำ

Serialization คือการแปลงสถานะของวัตถุเป็นไบต์สตรีม deserialization จะตรงกันข้าม ระบุไว้แตกต่างกันการทำให้เป็นอนุกรมคือการแปลงออบเจ็กต์ Java เป็นสตรีมแบบคงที่ (ลำดับ) ของไบต์ซึ่งสามารถบันทึกลงในฐานข้อมูลหรือถ่ายโอนผ่านเครือข่าย

2. Serialization และ Deserialization

กระบวนการทำให้เป็นอนุกรมนั้นไม่ขึ้นกับอินสแตนซ์กล่าวคืออ็อบเจ็กต์สามารถต่ออนุกรมบนแพลตฟอร์มหนึ่งและ deserialized บนอีกแพลตฟอร์มได้ ชั้นเรียนที่มีสิทธิ์สำหรับการทำให้เป็นอนุกรมจำเป็นต้องใช้อินเทอร์เฟซเครื่องหมายพิเศษต่ออนุกรมได้

ทั้งObjectInputStreamและObjectOutputStreamเป็นคลาสระดับสูงที่ขยายjava.io.InputStreamและjava.io.OutputStreamตามลำดับ ObjectOutputStreamสามารถเขียนประเภทดั้งเดิมและกราฟของออบเจ็กต์ไปยังOutputStreamเป็นสตรีมไบต์ ลำธารเหล่านี้สามารถต่อมาอ่านได้โดยใช้ObjectInputStream

วิธีที่สำคัญที่สุดในObjectOutputStreamคือ:

public final void writeObject(Object o) throws IOException;

ซึ่งนำอ็อบเจ็กต์ที่ต่ออนุกรมได้และแปลงเป็นลำดับ (สตรีม) ของไบต์ ในทำนองเดียวกันวิธีที่สำคัญที่สุดในObjectInputStreamคือ:

public final Object readObject() throws IOException, ClassNotFoundException;

ซึ่งสามารถอ่านสตรีมไบต์และแปลงกลับเป็นวัตถุ Java จากนั้นสามารถส่งกลับไปยังวัตถุเดิมได้

มาแสดงการทำให้เป็นอนุกรมด้วยคลาสPerson โปรดทราบว่าสาขาคงอยู่ในชั้นเรียน (เมื่อเทียบกับวัตถุ) และไม่ต่อเนื่อง นอกจากนี้โปรดทราบว่าเราสามารถใช้คีย์เวิร์ดชั่วคราวเพื่อละเว้นฟิลด์คลาสระหว่างการทำให้เป็นอนุกรม:

public class Person implements Serializable { private static final long serialVersionUID = 1L; static String country = "ITALY"; private int age; private String name; transient int height; // getters and setters }

การทดสอบด้านล่างแสดงตัวอย่างการบันทึกวัตถุประเภทPersonลงในไฟล์ภายในเครื่องจากนั้นอ่านค่านี้กลับเข้ามา:

@Test public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () throws IOException, ClassNotFoundException { Person person = new Person(); person.setAge(20); person.setName("Joe"); FileOutputStream fileOutputStream = new FileOutputStream("yourfile.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(person); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Person p2 = (Person) objectInputStream.readObject(); objectInputStream.close(); assertTrue(p2.getAge() == p.getAge()); assertTrue(p2.getName().equals(p.getName())); }

เราใช้ObjectOutputStreamสำหรับการบันทึกสถานะของวัตถุนี้ไปยังแฟ้มใช้FileOutputStream ไฟล์“ yourfile.txt”ถูกสร้างขึ้นในไดเร็กทอรีโปรเจ็กต์ จากนั้นไฟล์นี้จะถูกโหลดโดยใช้FileInputStream ObjectInputStreamหยิบกระแสนี้ขึ้นและแปลงเป็นวัตถุที่เรียกว่าใหม่P2

สุดท้ายเราจะทดสอบสถานะของวัตถุที่โหลดและตรงกับสถานะของวัตถุดั้งเดิม

สังเกตว่าวัตถุที่โหลดจะต้องถูกส่งไปยังประเภทบุคคลอย่างชัดเจน

3. Java Serialization Caveats

มีข้อแม้บางประการที่เกี่ยวข้องกับการทำให้เป็นอนุกรมใน Java

3.1. การถ่ายทอดทางพันธุกรรมและองค์ประกอบ

เมื่อคลาสใช้อินเทอร์เฟซjava.io.Serializableคลาสย่อยทั้งหมดจะสามารถต่ออนุกรมได้เช่นกัน ในทางตรงกันข้ามเมื่อวัตถุมีการอ้างอิงไปยังวัตถุอื่นวัตถุเหล่านี้จะต้องใช้อินเทอร์เฟซแบบอนุกรมแยกกันไม่เช่นนั้นจะส่งNotSerializableException

public class Person implements Serializable { private int age; private String name; private Address country; // must be serializable too } 

หากฟิลด์ใดฟิลด์หนึ่งในอ็อบเจ็กต์ที่ทำให้เป็นอนุกรมประกอบด้วยอาร์เรย์ของอ็อบเจ็กต์อ็อบเจ็กต์เหล่านี้ทั้งหมดจะต้องทำให้เป็นอนุกรมได้เช่นกันไม่เช่นนั้นจะมีการโยนNotSerializableException

3.2. UID เวอร์ชันอนุกรม

JVM เชื่อมโยงหมายเลขเวอร์ชัน (แบบยาว ) กับคลาสซีเรียลแต่ละคลาส ใช้เพื่อตรวจสอบว่าอ็อบเจ็กต์ที่บันทึกและโหลดมีแอ็ตทริบิวต์เดียวกันดังนั้นจึงเข้ากันได้กับการทำให้เป็นอนุกรม

หมายเลขนี้สามารถสร้างขึ้นโดยอัตโนมัติโดย IDE ส่วนใหญ่และขึ้นอยู่กับชื่อคลาสแอตทริบิวต์และตัวแก้ไขการเข้าถึงที่เกี่ยวข้อง การเปลี่ยนแปลงใด ๆ ส่งผลให้ในจำนวนที่แตกต่างกันและอาจทำให้เกิดการInvalidClassException

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

3.3. Custom Serialization ใน Java

Java ระบุวิธีดีฟอลต์ที่อ็อบเจ็กต์สามารถต่ออนุกรมได้ คลาส Java สามารถแทนที่ลักษณะการทำงานเริ่มต้นนี้ การทำให้เป็นอนุกรมแบบกำหนดเองมีประโยชน์อย่างยิ่งเมื่อพยายามทำให้เป็นอนุกรมวัตถุที่มีคุณลักษณะบางอย่างที่ไม่สามารถกำหนดค่าได้ สามารถทำได้โดยการจัดเตรียมสองวิธีในคลาสที่เราต้องการทำให้เป็นอนุกรม:

private void writeObject(ObjectOutputStream out) throws IOException;

และ

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

ด้วยวิธีการเหล่านี้เราสามารถทำให้แอตทริบิวต์ที่ไม่สามารถเชื่อมต่อได้เหล่านั้นเป็นรูปแบบอื่น ๆ ที่สามารถทำให้เป็นอนุกรมได้:

public class Employee implements Serializable { private static final long serialVersionUID = 1L; private transient Address address; private Person person; // setters and getters private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeObject(address.getHouseNumber()); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); Integer houseNumber = (Integer) ois.readObject(); Address a = new Address(); a.setHouseNumber(houseNumber); this.setAddress(a); } }
public class Address { private int houseNumber; // setters and getters }

การทดสอบหน่วยต่อไปนี้จะทดสอบการจัดลำดับแบบกำหนดเองนี้:

@Test public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() throws IOException, ClassNotFoundException { Person p = new Person(); p.setAge(20); p.setName("Joe"); Address a = new Address(); a.setHouseNumber(1); Employee e = new Employee(); e.setPerson(p); e.setAddress(a); FileOutputStream fileOutputStream = new FileOutputStream("yourfile2.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(e); objectOutputStream.flush(); objectOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("yourfile2.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Employee e2 = (Employee) objectInputStream.readObject(); objectInputStream.close(); assertTrue( e2.getPerson().getAge() == e.getPerson().getAge()); assertTrue( e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber()); }

ในรหัสนี้เราจะดูวิธีบันทึกแอตทริบิวต์ที่ไม่สามารถตรวจสอบได้โดยการทำให้เป็นอนุกรมที่อยู่ด้วยการทำให้เป็นอนุกรมที่กำหนดเอง โปรดทราบว่าเราต้องทำเครื่องหมายแอตทริบิวต์ที่ไม่สามารถใช้งานได้เป็นชั่วคราวเพื่อหลีกเลี่ยงNotSerializableException

4. สรุป

ในบทช่วยสอนฉบับย่อนี้เราได้ตรวจสอบการทำให้เป็นอนุกรมของ Java โดยกล่าวถึงสิ่งสำคัญที่ควรทราบและได้แสดงวิธีการจัดลำดับแบบกำหนดเอง

เช่นเคยซอร์สโค้ดที่ใช้ในบทช่วยสอนนี้มีให้ใช้งานบน GitHub