สวัสดี! ในบทความวันนี้ เราจะดูที่ตัวแก้ไขชั่วคราวใน Java เรามาพูดถึงสาเหตุที่ต้องใช้ตัวแก้ไขนี้และวิธีใช้อย่างถูกต้อง ไป!
สมมติว่าเรามีวัตถุบางอย่าง และมีเขตข้อมูล ซึ่งแต่ละเขตมีค่าอยู่บ้าง ทั้งหมดนี้เรียกว่าสถานะของวัตถุ การทำให้เป็นอนุกรมคือการแปลงสถานะของวัตถุให้เป็นลำดับไบต์ โดยปกติไบต์เหล่านี้จะถูกจัดเก็บไว้ในไฟล์บางไฟล์ การดีซีเรียลไลซ์เป็นกระบวนการย้อนกลับ ลองจินตนาการว่าเราทำให้วัตถุเป็นอนุกรมเป็นไบต์และเก็บชุดไบต์นี้ไว้ในไฟล์บางไฟล์ เมื่อทำการดีซีเรียลไลซ์ โปรแกรมจะต้องการ:
อะตอมแล้ว มาเขียนวิธีการความอับอายของชาวสเปน ตัวแก้ไข (สุดท้าย)
มีใครสับสนว่าเราบันทึกรหัสผ่านของผู้ใช้หรือไม่? โดยเฉพาะอย่างยิ่งรหัสผ่านดังกล่าว... ใช่แล้ว เราคิดขึ้นมาเอง แต่ก็ยัง... บางครั้งมีสถานการณ์ที่บางฟิลด์ไม่สามารถทำให้เป็นอนุกรมได้ หรือเป็นการดีกว่าที่จะไม่ทำเช่นนี้ ในตัวอย่างข้างต้น ฉันต้องการบันทึกทุกช่องยกเว้นรหัสผ่าน จะบรรลุเป้าหมายนี้ได้อย่างไร? คำตอบ: ใช้ตัว
บางครั้งคลาสบางคลาสอาจมีฟิลด์ที่คำนวณตามฟิลด์อื่นหรือข้อมูลอื่น พวกมันถูกคำนวณทันที เพื่อยกตัวอย่างสาขาดังกล่าว ลองจินตนาการถึงคำสั่งซื้อในร้านค้าออนไลน์หรือบริการจัดส่งอาหาร คำสั่งซื้อแต่ละรายการรวมถึงข้อมูลอื่นๆ ประกอบด้วยรายการสินค้าและต้นทุนทั้งหมด ในทางกลับกันจะประกอบด้วยต้นทุนรวมของแต่ละผลิตภัณฑ์ ปรากฎว่าไม่ควรกำหนดต้นทุนสุดท้าย "ด้วยมือ": ต้องคำนวณโดยทางโปรแกรมโดยสรุปต้นทุนของสินค้าทั้งหมด ฟิลด์เช่นนี้ที่ควรคำนวณโดยทางโปรแกรมไม่จำเป็นต้องทำให้เป็นอนุกรม ดังนั้นเราจึงทำเครื่องหมายด้วยตัว
นอกจากนี้ยังมีบางคลาสที่เก็บข้อมูลส่วนตัวด้วย เราดูตัวอย่างของชั้นเรียนดังกล่าวในตอนต้นของบทความ คุณไม่ควรปล่อยให้ข้อมูลดังกล่าวรั่วไหลออกนอก JVM ดังนั้น ฟิลด์ที่มีข้อมูลดังกล่าวจะต้องถูกทำเครื่องหมายด้วยตัวแก้ไข
บางครั้งคลาสจะมีฟิลด์ - อ็อบเจ็กต์ของคลาสอื่นที่ไม่ได้ใช้อินเทอร์เฟ
สิ่งสุดท้ายหนึ่ง ไม่จำเป็นต้องซีเรียลไลซ์ฟิลด์ที่ไม่ได้เป็นส่วนหนึ่งของข้อมูลสถานะของออบเจ็กต์ ตัวอย่างข้างต้นอยู่ภายใต้กฎนี้ แต่คุณยังสามารถรวมฟิลด์อื่นๆ ทั้งหมดที่เพิ่มไว้ที่นี่สำหรับการดีบักหรือเพื่อทำหน้าที่บริการบางประเภทที่ไม่มีข้อมูลเกี่ยวกับสถานะของออบเจ็กต์
มาจำการทำให้เป็นอนุกรมกันเถอะ
ตัวดัดแปลงtransient
ใช้ในกระบวนการซีเรียลไลซ์และดีซีเรียลไลซ์อ็อบเจ็กต์ เรามาพูดสั้น ๆ เกี่ยวกับเรื่องนี้ก่อน - อ่านชุดไบต์จากไฟล์
- สร้างวัตถุเริ่มต้นจากชุดไบต์นี้ และตั้งค่าแต่ละฟิลด์เป็นค่าที่วัตถุมีในขณะที่ทำให้เป็นอนุกรม
เรามาจำการทำให้เป็นอนุกรมในทางปฏิบัติกัน
ทีนี้เรามาดูการซีเรียลไลซ์ในทางปฏิบัติกันดีกว่า หากคุณต้องการเข้าใจหัวข้อนี้มากขึ้น เราขอแนะนำให้อ่านเนื้อหาการทำให้เป็นอนุกรมและดีซีเรียลไลซ์ใน Java ในบทความนี้เราจะพูดถึงด้านบนและตรงไปที่ตัวอย่าง สมมติว่าเรามีคลาสUser
ที่มีชุดของฟิลด์ getters และ setters และเมธอดtoString
:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private String email;
private LocalDate birthDate;
private String login;
private String password;
public User() {}
public User(String firstName, String lastName, String email, LocalDate birthDate, String login, String password) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.birthDate = birthDate;
this.login = login;
this.password = password;
}
/*
Геттеры, Сеттеры
*/
@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", birthDate=" + birthDate +
", login='" + login + '\'' +
", password='" + password + '\'' +
'}';
}
}
เราต้องการซีเรียลไลซ์อ็อบเจ็กต์ของคลาสนี้ในอนาคต มาเขียนวิธีการที่รับวัตถุUser
และสตริงpath
- เส้นทางไปยังไฟล์ที่เราจะบันทึกไบต์:
static void serialize(User user, String path) throws IOException {
FileOutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//create 2 threads to serialize the object and save it to a file
outputStream = new FileOutputStream(path);
objectOutputStream = new ObjectOutputStream(outputStream);
// сохраняем an object в файл
objectOutputStream.writeObject(user);
} finally {
// Закроем потоки в блоке finally
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
เราจะเขียนวิธีการดีซีเรียลไลซ์ด้วย วิธีการรับสตริงpath
(เส้นทางไปยังไฟล์ที่วัตถุจะถูก "โหลด") และส่งคืนวัตถุประเภทUser
:
static User deserialize(String path) throws IOException, ClassNotFoundException {
FileInputStream fileInputStream = null;
ObjectInputStream objectInputStream = null;
try {
//создаем 2 потока для десериализации an object из file
fileInputStream = new FileInputStream(path);
objectInputStream = new ObjectInputStream(fileInputStream);
//загружаем an object из file
return (User) objectInputStream.readObject();
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
if (objectInputStream != null) {
objectInputStream.close();
}
}
}
เครื่องมือทั้งหมดพร้อมใช้งาน ถึงเวลาแบ่งไบต์ออกเป็นmain
ที่เราสร้างคลาสอ็อบเจ็กต์User
และทำให้เป็นอนุกรม จากนั้นเราจะโหลดและเปรียบเทียบกับของเดิม:
public static void main(String[] args) throws IOException, ClassNotFoundException {
// вставьте свой путь до file
final String path = "/home/zor/user.ser";
// create our object
User user = new User();
user.setFirstName("Stefan");
user.setLastName("Smith");
user.setEmail("ssmith@email.com");
user.setBirthDate(LocalDate.of(1991, 7, 16));
user.setLogin("ssmith");
user.setPassword("gemma_arterton_4ever_in_my_heart91");
System.out.println("Initial user: " + user + "\r\n");
serialize(user, path);
User loadedUser = deserialize(path);
System.out.println("Loaded user from file: " + loadedUser + "\r\n");
}
ถ้าเรารันเมธอดนี้ เราจะเห็นผลลัพธ์ดังนี้:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
ดังที่คุณเห็นจากเอาต์พุต วัตถุต่างๆ จะเหมือนกัน แต่มีเพียงเล็กน้อย แต่... และนี่คือจุดที่transient
เข้ามามี บทบาท
ตัวแก้ไข (สุดท้าย)transient
มีใครสับสนว่าเราบันทึกรหัสผ่านของผู้ใช้หรือไม่? โดยเฉพาะอย่างยิ่งรหัสผ่านดังกล่าว... ใช่แล้ว เราคิดขึ้นมาเอง แต่ก็ยัง... บางครั้งมีสถานการณ์ที่บางฟิลด์ไม่สามารถทำให้เป็นอนุกรมได้ หรือเป็นการดีกว่าที่จะไม่ทำเช่นนี้ ในตัวอย่างข้างต้น ฉันต้องการบันทึกทุกช่องยกเว้นรหัสผ่าน จะบรรลุเป้าหมายนี้ได้อย่างไร? คำตอบ: ใช้ตัวtransient
แก้ไข transient
เป็นตัวแก้ไขที่ระบุก่อนฟิลด์คลาส (คล้ายกับตัวแก้ไขอื่น ๆ เช่นpublic
ฯลฯfinal
) เพื่อระบุว่าฟิลด์ไม่ควรถูกทำให้เป็นอนุกรม ฟิลด์ที่มีเครื่องหมายคำสำคัญtransient
จะไม่ถูกทำให้เป็นอนุกรม ตอนนี้เรามาแก้ไขตัวอย่างกับผู้ใช้ของเราเพื่อแก้ไขความสับสนเล็กน้อยและไม่บันทึกรหัสผ่านของผู้ใช้ เมื่อต้องการทำเช่นนี้ ให้ทำเครื่องหมายฟิลด์ที่เกี่ยวข้องในชั้นเรียนด้วยคำหลักtransient
:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private String email;
private LocalDate birthDate;
private String login;
private transient String password;
/*
Конструкторы, геттеры, сеттеры, toString...
*/
}
หากเรารันตามตัวอย่างด้านบนอีกครั้งmain
เราจะเห็นว่ารหัสผ่านไม่ได้รับการบันทึก:
Initial user: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='gemma_arterton_4ever_in_my_heart91'}
Loaded user from file: User{firstName='Stefan', lastName='Smith', email='ssmith@email.com', birthDate=1991-07-16, login='ssmith', password='null'}
เยี่ยมมาก เราบรรลุเป้าหมายและไม่จัดเก็บข้อมูลที่เป็นความลับ โดยเฉพาะข้อมูลประเภทนี้... (ขออภัย)
เมื่อใดจึงควรใช้ชั่วคราว?
จำเป็นต้องมีตัวอย่างกับผู้ใช้เพื่อเจาะลึกบริบทของการทำให้เป็นอนุกรม ตอนนี้เรามาพูดเจาะจงมากขึ้นเกี่ยวกับเวลาที่ควรใช้ตัวปรับtransient
แต่ง
- ฟิลด์ที่มีการคำนวณโดยทางโปรแกรม
บางครั้งคลาสบางคลาสอาจมีฟิลด์ที่คำนวณตามฟิลด์อื่นหรือข้อมูลอื่น พวกมันถูกคำนวณทันที เพื่อยกตัวอย่างสาขาดังกล่าว ลองจินตนาการถึงคำสั่งซื้อในร้านค้าออนไลน์หรือบริการจัดส่งอาหาร คำสั่งซื้อแต่ละรายการรวมถึงข้อมูลอื่นๆ ประกอบด้วยรายการสินค้าและต้นทุนทั้งหมด ในทางกลับกันจะประกอบด้วยต้นทุนรวมของแต่ละผลิตภัณฑ์ ปรากฎว่าไม่ควรกำหนดต้นทุนสุดท้าย "ด้วยมือ": ต้องคำนวณโดยทางโปรแกรมโดยสรุปต้นทุนของสินค้าทั้งหมด ฟิลด์เช่นนี้ที่ควรคำนวณโดยทางโปรแกรมไม่จำเป็นต้องทำให้เป็นอนุกรม ดังนั้นเราจึงทำเครื่องหมายด้วยตัวtransient
แก้ไข
class Order implements Serializable {
private List- items;
private transient BigDecimal totalAmount; //вычисляется на ходу
}
- ช่องที่มีข้อมูลส่วนตัว
นอกจากนี้ยังมีบางคลาสที่เก็บข้อมูลส่วนตัวด้วย เราดูตัวอย่างของชั้นเรียนดังกล่าวในตอนต้นของบทความ คุณไม่ควรปล่อยให้ข้อมูลดังกล่าวรั่วไหลออกนอก JVM ดังนั้น ฟิลด์ที่มีข้อมูลดังกล่าวจะต้องถูกทำเครื่องหมายด้วยตัวแก้ไขtransient
หากคุณต้องการทำให้คลาสดังกล่าวเป็นอนุกรม
- ฟิลด์ที่ไม่ได้ใช้อินเทอร์เฟซ
Serializable
บางครั้งคลาสจะมีฟิลด์ - อ็อบเจ็กต์ของคลาสอื่นที่ไม่ได้ใช้อินเทอร์เฟSerializable
Serializable
ซ ตัวอย่างของฟิลด์ดังกล่าว ได้แก่ ตัวบันทึก สตรีม I/O อ็อบเจ็กต์ที่เก็บการเชื่อมต่อฐานข้อมูล และคลาสยูทิลิตี้อื่นๆ หากคุณพยายามทำให้วัตถุเป็นอนุกรมซึ่งมีเขตข้อมูลที่ไม่สามารถทำให้เป็นอนุกรมได้ คุณจะได้รับข้อผิดjava.io.NotSerializableException
พลาด เพื่อหลีกเลี่ยงปัญหานี้ ฟิลด์ทั้งหมดที่ไม่ได้ใช้อินเทอร์เฟซSerializable
จะต้องมีเครื่องหมายตัวtransient
แก้ไข
public class FileReader implements Serializable {
// Первые 2 поля не реализуют Serializable
// Помечаем их How transient поля
private transient InputStream is;
private transient BufferedReader buf;
private String fileName;
// Constructors, Getters, Setters
public String readFile() throws IOException {
try {
is = new FileInputStream(fileName);
buf = new BufferedReader(new InputStreamReader(is));
String line = buf.readLine();
StringBuilder sb = new StringBuilder();
while (line != null) {
sb.append(line).append("\n");
line = buf.readLine();
}
return sb.toString();
} finally {
if (buf != null) {
buf.close();
}
if (is != null) {
is.close();
}
}
}
}
- ช่องที่มีข้อมูลเกี่ยวกับสถานะของออบเจ็กต์
สิ่งสุดท้ายหนึ่ง ไม่จำเป็นต้องซีเรียลไลซ์ฟิลด์ที่ไม่ได้เป็นส่วนหนึ่งของข้อมูลสถานะของออบเจ็กต์ ตัวอย่างข้างต้นอยู่ภายใต้กฎนี้ แต่คุณยังสามารถรวมฟิลด์อื่นๆ ทั้งหมดที่เพิ่มไว้ที่นี่สำหรับการดีบักหรือเพื่อทำหน้าที่บริการบางประเภทที่ไม่มีข้อมูลเกี่ยวกับสถานะของออบเจ็กต์
transient
และfinal
ผลลัพธ์
นั่นคือทั้งหมดที่ วันนี้เราพูดถึงตัวแก้ไขtransient
:
- เราจำการจัดลำดับได้ในทางทฤษฎีและการปฏิบัติ
- เราตระหนักดีว่าเพื่อไม่ให้ซีเรียลไลซ์บางฟิลด์ของคลาส จำเป็นต้องทำเครื่องหมายด้วยตัว
transient
แก้ไข - เราได้พูดคุยกันแล้วว่าควรใช้ตัวแก้ไขนี้ในสถานการณ์ใด มีสี่สถานการณ์ดังกล่าว:
- ฟิลด์ที่คำนวณโดยทางโปรแกรม
- ฟิลด์ที่มีข้อมูลลับ
- ฟิลด์ที่ไม่ได้ใช้อินเทอร์เฟซ
Serializable
; - เขตข้อมูลที่ไม่ได้เป็นส่วนหนึ่งของสถานะของวัตถุ
GO TO FULL VERSION