Serializable
ฉันรับมือกับงานของฉันและการดำเนินการตามกระบวนการทั้งหมดโดยอัตโนมัติก็อดไม่ได้ที่จะชื่นชมยินดี ตัวอย่างที่เราดูก็ไม่ซับซ้อนเช่นกัน แล้วข้อตกลงคืออะไร? เหตุใดจึงต้องมีอินเทอร์เฟซอื่นสำหรับงานเดียวกันเป็นหลัก ความจริงก็คือSerializable
มันมีข้อเสียหลายประการ เรามาแสดงรายการบางส่วนกัน:
-
ผลงาน. อินเทอร์เฟซมี
Serializable
ข้อดีหลายประการ แต่ประสิทธิภาพสูงไม่ได้เป็นหนึ่งในนั้นอย่างชัดเจน
ประการแรกกลไกภายในSerializable
จะสร้างข้อมูลบริการจำนวนมากและข้อมูลชั่วคราวประเภทต่างๆ ในระหว่างการดำเนินการ
ประการที่สอง (คุณไม่จำเป็นต้องเข้าไปอ่านตอนนี้และอ่านในยามว่างหากคุณสนใจ) งานนี้Serializable
อิงจากการใช้ Reflection API การคุมกำเนิดนี้ช่วยให้คุณทำสิ่งต่าง ๆ ที่ดูเหมือนเป็นไปไม่ได้ใน Java เช่น เปลี่ยนค่าของฟิลด์ส่วนตัว JavaRush มีบทความดีๆเกี่ยวกับ 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;
//...конструктор, геттеры, сеттеры, 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 out
และ เป็นพารามิเตอร์ 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("Ivan", "Ivanov", "Ivan Ivanov's passport data");
objectOutputStream.writeObject(userInfo);
objectOutputStream.close();
}
}
ในencryptString()
และ วิธีการ decryptString()
เราได้เพิ่มเอาต์พุตลงในคอนโซลโดยเฉพาะเพื่อตรวจสอบว่าข้อมูลลับจะถูกเขียนและอ่านในรูปแบบใด รหัสด้านบนส่งออกบรรทัดต่อไปนี้ไปยังคอนโซล: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh Encryption สำเร็จ! เนื้อหาทั้งหมดของไฟล์มีลักษณะดังนี้: ¢н 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();
}
}
ดูเหมือนจะไม่มีอะไรซับซ้อนที่นี่ มันควรจะได้ผล! มาเริ่มกันเลย... ข้อยกเว้นในเธรด "main" java.io.InvalidClassException: UserInfo; ไม่มีตัวสร้างที่ถูกต้อง อ๊ะ :( มันกลายเป็นว่าไม่ง่ายนัก! กลไกการดีซีเรียลไลเซชันมีข้อยกเว้นและกำหนดให้เราต้องสร้างตัวสร้างเริ่มต้น ฉันสงสัยว่าทำไมSerializable
เราจัดการโดยไม่มีมัน... :/ เรามาถึงความแตกต่างที่สำคัญอีกอย่างหนึ่งแล้ว . ความแตกต่างระหว่างSerializable
และExternalizable
ไม่เพียงแต่อยู่ในการเข้าถึงแบบ "ขยาย" สำหรับโปรแกรมเมอร์และความสามารถในการจัดการกระบวนการได้อย่างยืดหยุ่นมากขึ้น แต่ยังอยู่ในกระบวนการด้วย ประการแรก ในกลไกการดีซีเรียลไลซ์เซชัน ... เมื่อใช้Serializable
หน่วยความจำเป็นเพียง จัดสรรให้กับวัตถุหลังจากนั้นจะอ่านค่าจากสตรีมซึ่งกรอกข้อมูลในฟิลด์ทั้งหมด หากเราใช้Serializable
ตัวสร้างวัตถุจะไม่ถูกเรียก!งานทั้งหมดเสร็จสิ้นผ่านการสะท้อนกลับ (Reflection API ซึ่งเรากล่าวถึงสั้น ๆ ในตอนสุดท้าย การบรรยาย) ในกรณีของExternalizable
กลไกการดีซีเรียลไลเซชันจะแตกต่างออกไป ในตอนเริ่มต้น Constructor เริ่มต้นจะถูกเรียก และหลังจากนั้นเท่านั้น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();
}
}
เอาต์พุตคอนโซล: ข้อมูลหนังสือเดินทางของ Ivan Ivanov UserInfo{firstName='Ivan', LastName='Ivanov', superSecretInformation='Ivan Ivanov's Passport data'} แตกต่างไปจากเดิมอย่างสิ้นเชิง! ขั้นแรก สตริงที่ถอดรหัสพร้อมข้อมูลลับจะถูกส่งออกไปยังคอนโซล จากนั้นอ็อบเจ็กต์ของเราจะถูกกู้คืนจากไฟล์ในรูปแบบสตริง! นี่คือวิธีที่เราแก้ไขปัญหาทั้งหมดได้สำเร็จ :) หัวข้อเรื่องซีเรียลไลซ์เซชันและดีซีเรียลไลเซชันดูเหมือนจะง่าย แต่อย่างที่คุณเห็น การบรรยายของเราใช้เวลานาน และนั่นไม่ใช่ทั้งหมด! มีรายละเอียดปลีกย่อยอีกมากมายเมื่อใช้แต่ละอินเทอร์เฟซเหล่านี้ แต่ตอนนี้สมองของคุณไม่ระเบิดจากข้อมูลใหม่จำนวนมาก ฉันจะแสดงรายการประเด็นสำคัญอีกสองสามข้อโดยย่อและให้ลิงก์ไปยังการอ่านเพิ่มเติม แล้วคุณต้องรู้อะไรอีกบ้าง? ประการแรกเมื่อทำให้เป็นอนุกรม (ไม่สำคัญว่าคุณจะใช้Serializable
หรือ ) ให้Externalizable
ใส่ใจกับตัวแปร static
เมื่อใช้Serializable
ฟิลด์เหล่านี้จะไม่ถูกทำให้เป็นอนุกรมเลย (และด้วยเหตุนี้ ค่าของฟิลด์เหล่านี้จึงไม่เปลี่ยนแปลง เนื่องจากstatic
ฟิลด์เป็นของคลาส ไม่ใช่อ็อบเจ็กต์) แต่เมื่อใช้มันExternalizable
คุณจะควบคุมกระบวนการด้วยตัวเอง ดังนั้นในทางเทคนิคจึงสามารถทำได้ แต่ไม่แนะนำ เนื่องจากเต็มไปด้วยข้อผิดพลาดเล็กๆ น้อยๆ ประการที่สองควรให้ความสนใจกับตัวแปรด้วยตัวfinal
แก้ไข เมื่อใช้Serializable
พวกมันจะถูกซีเรียลไลซ์และดีซีเรียลไลซ์ตามปกติ แต่เมื่อใช้แล้ว เป็นไปไม่ได้ Externalizable
ที่จะดีซีเรียลไลซ์final
ตัวแปร ! เหตุผลง่ายๆ คือ - final
ฟิลด์ทั้งหมดจะถูกเตรียมใช้งานเมื่อมีการเรียกคอนสตรัคเตอร์เริ่มต้น และหลังจากนั้นค่าจะไม่สามารถเปลี่ยนแปลงได้ ดังนั้นในการซีเรียลไลซ์อ็อบเจ็กต์ที่มีfinal
-fields ให้ใช้การทำให้ซีเรียลไลซ์มาตรฐานผ่านSerializable
. ประการที่สามเมื่อใช้การสืบทอด คลาสที่สืบทอดทั้งหมดจากบางExternalizable
คลาสจะต้องมีตัวสร้างเริ่มต้นด้วย ต่อไปนี้เป็นลิงก์ไปยังบทความดีๆ เกี่ยวกับกลไกการทำให้เป็นอนุกรม:
พบกันใหม่! :)
GO TO FULL VERSION