Serializable
ทำหน้าที่ของมัน และอะไรที่ไม่ชอบเกี่ยวกับการนำกระบวนการทั้งหมดไปใช้โดยอัตโนมัติ และตัวอย่างที่เราดูก็ไม่ซับซ้อนเช่นกัน แล้วปัญหาคืออะไร? เหตุใดเราจึงต้องการอินเทอร์เฟซอื่นสำหรับงานเดียวกัน ความจริงก็คือSerializable
มีข้อบกพร่องหลายประการ เราแสดงรายการบางส่วน:
-
ผลงาน. อิน
Serializable
เทอร์เฟซมีข้อดีมากมาย แต่ประสิทธิภาพสูงนั้นไม่ใช่หนึ่งในนั้นอย่างชัดเจนขั้นแรก
Serializable
การดำเนินการภายในของ ' จะสร้างข้อมูลบริการจำนวนมากและข้อมูลชั่วคราวทุกประเภทประการที่สอง
Serializable
ใช้ Reflection API (ตอนนี้คุณไม่ต้องลงลึกในเรื่องนี้แล้ว คุณสามารถอ่านเพิ่มเติมได้ในยามว่าง หากคุณสนใจ) สิ่งนี้ช่วยให้คุณทำสิ่งที่ดูเหมือนจะเป็นไปไม่ได้ใน Java เช่น เปลี่ยนค่าของฟิลด์ส่วนตัว CodeGym มีบทความที่ยอดเยี่ยมเกี่ยวกับ Reflection API คุณสามารถอ่านเกี่ยวกับเรื่องนี้ได้ที่นั่น -
ความยืดหยุ่น เราไม่ได้ควบคุมกระบวนการซีเรียลไลเซชัน-ดีซีเรียลไลเซชันเมื่อเราใช้
Serializable
อินเทอร์เฟซในแง่หนึ่ง มันสะดวกมาก เพราะหากเราไม่กังวลเรื่องประสิทธิภาพเป็นพิเศษ ก็ดูเหมือนจะดีที่ไม่ต้องเขียนโค้ด แต่จะเป็นอย่างไรหากเราต้องการเพิ่มคุณสมบัติบางอย่างของเราเอง (เราจะให้ตัวอย่างด้านล่าง) ให้กับตรรกะการทำให้เป็นอนุกรม
โดยพื้นฐานแล้ว สิ่งที่เราต้องควบคุมกระบวนการคือ
transient
คีย์เวิร์ดเพื่อยกเว้นข้อมูลบางอย่าง แค่นั้นแหละ. นั่นคือกล่องเครื่องมือทั้งหมดของเรา :/ -
ความปลอดภัย. รายการนี้มีส่วนมาจากรายการที่แล้ว
เราไม่ได้ใช้เวลามากในการคิดเรื่องนี้มาก่อน แต่จะเป็นอย่างไรหากข้อมูลบางอย่างในชั้นเรียนของคุณไม่ได้มีไว้สำหรับสอดรู้สอดเห็น ตัวอย่างง่ายๆ เช่น รหัสผ่านหรือข้อมูลส่วนตัวอื่นๆ ของผู้ใช้ ซึ่งในโลกปัจจุบันอยู่ภายใต้กฎหมายหลายฉบับ
ถ้าเราใช้ เรา
Serializable
ไม่สามารถทำอะไรกับมันได้เลย เราทำให้ทุกอย่างเป็นอนุกรมตามที่เป็นอยู่แต่ถ้าเราทำอย่างถูกวิธี เราจะต้องเข้ารหัสข้อมูลประเภทนี้ก่อนที่จะเขียนลงไฟล์หรือส่งผ่านเครือข่าย แต่
Serializable
ไม่ทำให้สิ่งนี้เป็นไปได้

Externalizable
อินเทอร์เฟซ
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long SERIAL_VERSION_UID = 1L;
// ...constructor, getters, setters, toString()...
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
}
อย่างที่คุณเห็น เรามีการเปลี่ยนแปลงที่สำคัญ! สิ่งหลักนั้นชัดเจน: เมื่อใช้Externalizable
อินเทอร์เฟซคุณต้องใช้วิธีที่จำเป็นสองวิธี: writeExternal()
และreadExternal()
. อย่างที่เราได้กล่าวไปก่อนหน้านี้ ความรับผิดชอบในการซีเรียลไลเซชันและดีซีเรียลไลเซชันจะอยู่กับโปรแกรมเมอร์ แต่ตอนนี้คุณสามารถแก้ปัญหาที่ไม่สามารถควบคุมกระบวนการได้แล้ว! กระบวนการทั้งหมดได้รับการตั้งโปรแกรมโดยตรงจากคุณ โดยธรรมชาติแล้วสิ่งนี้ทำให้กลไกมีความยืดหยุ่นมากขึ้น นอกจากนี้ยังแก้ปัญหาเกี่ยวกับความปลอดภัย อย่างที่คุณเห็น ชั้นเรียนของเรามีช่องข้อมูลส่วนบุคคลที่ไม่สามารถจัดเก็บโดยไม่เข้ารหัสได้ ตอนนี้เราสามารถเขียนโค้ดที่ตอบสนองข้อจำกัดนี้ได้อย่างง่ายดาย ตัวอย่างเช่น เราสามารถเพิ่มวิธีการส่วนตัวอย่างง่ายสองวิธีในชั้นเรียนของเราเพื่อเข้ารหัสและถอดรหัสข้อมูลที่ละเอียดอ่อน เราจะเขียนข้อมูลลงในไฟล์และอ่านจากไฟล์ในรูปแบบการเข้ารหัส ข้อมูลที่เหลือจะถูกเขียนและอ่านเหมือนเดิม :) ด้วยเหตุนี้ คลาสของเราจึงมีลักษณะดังนี้:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;
public class UserInfo implements Externalizable {
private String firstName;
private String lastName;
private String superSecretInformation;
private static final long serialVersionUID = 1L;
public UserInfo() {
}
public UserInfo(String firstName, String lastName, String superSecretInformation) {
this.firstName = firstName;
this.lastName = lastName;
this.superSecretInformation = superSecretInformation;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.getFirstName());
out.writeObject(this.getLastName());
out.writeObject(this.encryptString(this.getSuperSecretInformation()));
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
firstName = (String) in.readObject();
lastName = (String) in.readObject();
superSecretInformation = this.decryptString((String) in.readObject());
}
private String encryptString(String data) {
String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
System.out.println(encryptedData);
return encryptedData;
}
private String decryptString(String data) {
String decrypted = new String(Base64.getDecoder().decode(data));
System.out.println(decrypted);
return decrypted;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSuperSecretInformation() {
return superSecretInformation;
}
}
เราใช้สองวิธีที่ใช้เหมือนกันObjectOutput
และObjectInput
พารามิเตอร์ที่เราพบแล้วในบทเรียนเกี่ยวSerializable
กับ ในเวลาที่เหมาะสม เราเข้ารหัสหรือถอดรหัสข้อมูลที่จำเป็น และเราใช้ข้อมูลที่เข้ารหัสเพื่อทำให้วัตถุของเราเป็นอนุกรม มาดูกันว่าสิ่งนี้มีลักษณะอย่างไรในทางปฏิบัติ:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
UserInfo userInfo = new UserInfo("Paul", "Piper", "Paul Piper's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
ในencryptString()
และdecryptString()
วิธีการ เราได้เพิ่มเอาต์พุตคอนโซลโดยเฉพาะเพื่อตรวจสอบรูปแบบที่จะเขียนและอ่านข้อมูลลับ รหัสด้านบนแสดงบรรทัดต่อไปนี้: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh การเข้ารหัสสำเร็จ! เนื้อหาทั้งหมดของไฟล์มีลักษณะดังนี้: ¬н sr UserInfoГ!}ҐџC'ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx ทีนี้มาลองใช้ตรรกะการแยกซีเรียลไลเซชันกัน
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
ดูเหมือนไม่มีอะไรซับซ้อนที่นี่ มันควรจะทำงาน! เราเรียกใช้และได้รับ... ข้อยกเว้นในเธรด "หลัก" java.io.InvalidClassException: UserInfo; ไม่มีตัวสร้างที่ถูกต้อง 
Serializable
เราได้มาโดยปราศจากหนึ่ง... :/ ที่นี่เราพบความแตกต่างที่สำคัญอีกประการหนึ่ง ความแตกต่างระหว่างSerializable
และExternalizable
ไม่ได้อยู่ที่การเข้าถึง 'ขยาย' ของโปรแกรมเมอร์และความสามารถในการควบคุมกระบวนการที่ยืดหยุ่นมากขึ้นเท่านั้น แต่ยังรวมถึง กระบวนการด้วย เหนือสิ่งอื่นใดในกลไกการดีซีเรียลไลเซชัน เมื่อใช้Serializable
หน่วยความจำจะถูกจัดสรรให้กับออบเจกต์ จากนั้นค่าจะถูกอ่านจากสตรีมและใช้เพื่อตั้งค่าฟิลด์ของออบเจ็กต์ ถ้าเราใช้Serializable
ตัวสร้างของวัตถุจะไม่ถูกเรียก! งานทั้งหมดเกิดขึ้นผ่านการไตร่ตรอง (Reflection API ซึ่งเราได้กล่าวถึงสั้นๆ ในบทเรียนที่แล้ว) ด้วยExternalizable
กลไกการดีซีเรียลไลเซชันจะแตกต่างกัน ตัวสร้างเริ่มต้นถูกเรียกก่อน หลังจากนั้นจะเรียกวิธีการUserInfo
ของวัตถุ ที่สร้างขึ้น readExternal()
มีหน้าที่ในการตั้งค่าฟิลด์ของวัตถุ นั่นคือเหตุผลที่คลาสใด ๆ ที่ใช้Externalizable
อินเทอร์เฟซต้องมีตัวสร้างเริ่มต้น มาเพิ่มในUserInfo
ชั้นเรียนของเราและรันโค้ดอีกครั้ง:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
UserInfo userInfo = (UserInfo) objectInputStream.readObject();
System.out.println(userInfo);
objectInputStream.close();
}
}
เอาต์พุตของคอนโซล: ข้อมูลหนังสือเดินทางของ Paul Piper UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'ข้อมูลหนังสือเดินทางของ Paul Piper' } ตอนนี้เป็นสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง! ขั้นแรก สตริงที่ถอดรหัสพร้อมข้อมูลลับจะแสดงบนคอนโซล จากนั้นวัตถุที่เรากู้คืนจากไฟล์จะแสดงเป็นสตริง! ดังนั้นเราจึงแก้ไขปัญหาทั้งหมดได้สำเร็จ :) หัวข้อของการทำให้เป็นอนุกรมและการดีซีเรียลไลเซชันดูเหมือนง่าย แต่อย่างที่คุณเห็น บทเรียนมีมานานแล้ว และยังมีอีกมากมายที่เรายังไม่ได้กล่าวถึง! ยังมีรายละเอียดปลีกย่อยมากมายที่เกี่ยวข้องเมื่อใช้แต่ละอินเทอร์เฟซเหล่านี้ แต่เพื่อหลีกเลี่ยงการระเบิดสมองของคุณจากข้อมูลใหม่ที่มากเกินไป ฉันจะทำรายการประเด็นสำคัญสั้นๆ สองสามข้อและให้ลิงก์ไปยังการอ่านเพิ่มเติมแก่คุณ แล้วคุณต้องรู้อะไรอีกบ้าง? ขั้นแรกในระหว่างการทำให้เป็นอนุกรม (ไม่ว่าคุณจะใช้Serializable
หรือ ก็ตามExternalizable
) ให้ใส่ใจกับ static
ตัวแปร เมื่อคุณใช้Serializable
ฟิลด์เหล่านี้จะไม่ถูกทำให้เป็นอนุกรมเลย (และดังนั้น ค่าของฟิลด์จะไม่เปลี่ยนแปลง เนื่องจากstatic
ฟิลด์เป็นของคลาส ไม่ใช่วัตถุ) แต่เมื่อคุณใช้Externalizable
คุณเป็นผู้ควบคุมกระบวนการด้วยตนเอง ดังนั้นในทางเทคนิคแล้ว คุณสามารถทำให้เป็นอนุกรมได้ แต่เราไม่แนะนำ เนื่องจากการกระทำดังกล่าวมีแนวโน้มที่จะสร้างข้อบกพร่องเล็กน้อยมากมาย ประการที่สองคุณควรใส่ใจกับตัวแปรที่มีการfinal
ปรับเปลี่ยน ด้วย เมื่อคุณใช้Serializable
ตัวแปรเหล่านี้จะถูกทำให้เป็นซีเรียลไลซ์และดีซีเรียลไลซ์ตามปกติ แต่เมื่อคุณใช้Externalizable
จะไม่สามารถทำการดีซีเรียลไลซ์final
ตัวแปร ได้ ! เหตุผลนั้นง่ายมาก: final
ฟิลด์ทั้งหมดจะถูกเตรียมใช้งานเมื่อมีการเรียกใช้ตัวสร้างเริ่มต้น — หลังจากนั้นจะไม่สามารถเปลี่ยนแปลงค่าได้ ดังนั้น ในการทำให้วัตถุมีฟิลด์เป็นอนุกรมfinal
ให้ใช้การทำให้เป็นอันดับมาตรฐานที่มีให้Serializable
โดย ประการที่สามเมื่อคุณใช้การสืบทอด คลาสลูกหลานทั้งหมดที่สืบทอดมาบางส่วนExternalizable
คลาสต้องมีตัวสร้างเริ่มต้นด้วย นี่คือลิงค์ไปยังบทความที่ดีเกี่ยวกับกลไกการออกซีเรียล:
จนกว่าจะถึงครั้งต่อไป! :)
GO TO FULL VERSION