JavaRush /จาวาบล็อก /Random-TH /ตัวดัดแปลงชั่วคราวซ่อนอะไรใน Java?
Анзор Кармов
ระดับ
Санкт-Петербург

ตัวดัดแปลงชั่วคราวซ่อนอะไรใน Java?

เผยแพร่ในกลุ่ม
สวัสดี! ในบทความวันนี้ เราจะดูที่ตัวแก้ไขชั่วคราวใน Java เรามาพูดถึงสาเหตุที่ต้องใช้ตัวแก้ไขนี้และวิธีใช้อย่างถูกต้อง ไป! ตัวดัดแปลงชั่วคราวซ่อนอะไรใน Java - 1

มาจำการทำให้เป็นอนุกรมกันเถอะ

ตัวดัดแปลงtransientใช้ในกระบวนการซีเรียลไลซ์และดีซีเรียลไลซ์อ็อบเจ็กต์ เรามาพูดสั้น ๆ เกี่ยวกับเรื่องนี้ก่อน ตัวดัดแปลงชั่วคราวซ่อนอะไรใน Java - 2สมมติว่าเรามีวัตถุบางอย่าง และมีเขตข้อมูล ซึ่งแต่ละเขตมีค่าอยู่บ้าง ทั้งหมดนี้เรียกว่าสถานะของวัตถุ การทำให้เป็นอนุกรมคือการแปลงสถานะของวัตถุให้เป็นลำดับไบต์ โดยปกติไบต์เหล่านี้จะถูกจัดเก็บไว้ในไฟล์บางไฟล์ การดีซีเรียลไลซ์เป็นกระบวนการย้อนกลับ ลองจินตนาการว่าเราทำให้วัตถุเป็นอนุกรมเป็นไบต์และเก็บชุดไบต์นี้ไว้ในไฟล์บางไฟล์ เมื่อทำการดีซีเรียลไลซ์ โปรแกรมจะต้องการ:
  1. อ่านชุดไบต์จากไฟล์
  2. สร้างวัตถุเริ่มต้นจากชุดไบต์นี้ และตั้งค่าแต่ละฟิลด์เป็นค่าที่วัตถุมีในขณะที่ทำให้เป็นอนุกรม
สิ่งนี้จะมีประโยชน์เมื่อใด เช่น เมื่อเราต้องการให้โปรแกรมบันทึกสถานะเมื่อปิดเครื่องและเรียกคืนในครั้งต่อไปที่เปิดเครื่อง เมื่อคุณปิด IntelliJ IDEA คุณน่าจะมีแท็บและคลาสเดียวกันเปิดขึ้นในครั้งถัดไปที่คุณเปิดใช้งาน

เรามาจำการทำให้เป็นอนุกรมในทางปฏิบัติกัน

ทีนี้เรามาดูการซีเรียลไลซ์ในทางปฏิบัติกันดีกว่า หากคุณต้องการเข้าใจหัวข้อนี้มากขึ้น เราขอแนะนำให้อ่านเนื้อหาการทำให้เป็นอนุกรมและดีซีเรียลไลซ์ใน 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ซ ตัวอย่างของฟิลด์ดังกล่าว ได้แก่ ตัวบันทึก สตรีม 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:
  1. เราจำการจัดลำดับได้ในทางทฤษฎีและการปฏิบัติ
  2. เราตระหนักดีว่าเพื่อไม่ให้ซีเรียลไลซ์บางฟิลด์ของคลาส จำเป็นต้องทำเครื่องหมายด้วยตัวtransientแก้ไข
  3. เราได้พูดคุยกันแล้วว่าควรใช้ตัวแก้ไขนี้ในสถานการณ์ใด มีสี่สถานการณ์ดังกล่าว:
    1. ฟิลด์ที่คำนวณโดยทางโปรแกรม
    2. ฟิลด์ที่มีข้อมูลลับ
    3. ฟิลด์ที่ไม่ได้ใช้อินเทอร์เฟซSerializable;
    4. เขตข้อมูลที่ไม่ได้เป็นส่วนหนึ่งของสถานะของวัตถุ
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION