JavaRush /Blog Java /Random-VI /Công cụ sửa đổi tạm thời ẩn giấu điều gì trong Java?
Анзор Кармов
Mức độ
Санкт-Петербург

Công cụ sửa đổi tạm thời ẩn giấu điều gì trong Java?

Xuất bản trong nhóm
Xin chào! Trong bài viết hôm nay, chúng ta sẽ xem xét công cụ sửa đổi tạm thời trong Java. Hãy nói về lý do tại sao cần có công cụ sửa đổi này và cách sử dụng nó một cách chính xác. Đi! Công cụ sửa đổi tạm thời ẩn giấu điều gì trong Java - 1

Hãy nhớ tuần tự hóa

Công cụ sửa đổi transientđược sử dụng trong quá trình tuần tự hóa và giải tuần tự hóa các đối tượng. Vì vậy, hãy nói ngắn gọn về điều này trước tiên. Công cụ sửa đổi tạm thời ẩn chứa điều gì trong Java - 2Giả sử chúng ta có một số đối tượng và nó có các trường, mỗi trường có một giá trị nào đó. Tất cả điều này được gọi là trạng thái của đối tượng. Tuần tự hóa là việc chuyển đổi trạng thái của một đối tượng thành một chuỗi byte. Những byte này thường được lưu trữ trong một số tập tin. Quá trình khử lưu huỳnh là quá trình ngược lại. Hãy tưởng tượng rằng chúng ta đã tuần tự hóa một đối tượng thành byte và lưu trữ tập hợp byte này trong một số tệp. Khi khử lưu huỳnh, chương trình cần:
  1. Đọc một tập hợp byte từ một tập tin.
  2. Xây dựng một đối tượng ban đầu từ tập hợp byte này và đặt từng trường thành giá trị mà đối tượng có tại thời điểm tuần tự hóa.
Khi nào điều này có thể hữu ích? Ví dụ: khi chúng ta muốn chương trình lưu lại trạng thái khi tắt và khôi phục lại vào lần bật tiếp theo. Khi bạn tắt IntelliJ IDEA, rất có thể bạn sẽ mở các tab và lớp tương tự vào lần bật tiếp theo

Hãy nhớ tuần tự hóa trong thực tế

Bây giờ chúng ta hãy xem xét việc tuần tự hóa trong thực tế. Nếu bạn muốn hiểu rõ hơn về chủ đề này, chúng tôi khuyên bạn nên đọc tài liệu Tuần tự hóa và giải tuần tự hóa trong Java . Chà, trong bài viết này chúng ta sẽ đi qua phần trên và đi thẳng vào các ví dụ. Giả sử chúng ta có một lớp Uservới một tập hợp một số trường, getters và setters và một phương thức 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 + '\'' +
                '}';
    }
}
Chúng tôi muốn tuần tự hóa các đối tượng của lớp này trong tương lai. Hãy viết một phương thức lấy một đối tượng Uservà một chuỗi path- đường dẫn đến tệp mà chúng ta sẽ lưu các byte:
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();
        }
    }
}
Chúng tôi cũng sẽ viết một phương pháp để khử lưu huỳnh. Phương thức này lấy một chuỗi path(đường dẫn đến tệp mà đối tượng sẽ được “tải”) và trả về một đối tượng có kiểu 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();
        }
    }
}
Tất cả các công cụ đã sẵn sàng để sử dụng. Đã đến lúc chia byte thành các nguyên tử . Hãy viết một phương thức maintrong đó chúng ta tạo một đối tượng lớp Uservà tuần tự hóa nó. Sau đó, chúng tôi sẽ tải nó và so sánh nó với những gì ban đầu:
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");
}
Nếu chúng ta chạy phương thức này, chúng ta sẽ thấy kết quả sau:
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'}
Như bạn có thể thấy từ kết quả đầu ra, các đối tượng giống hệt nhau. Nhưng có một điều nhỏ nhưng... Và đây chính xác là lúc sự xấu hổ của người Tây Ban Nha transient phát huy tác dụng .

Công cụ sửa đổi (cuối cùng)transient

Có ai nhầm lẫn rằng chúng tôi đã lưu mật khẩu của người dùng không? Đặc biệt là mật khẩu như vậy... Vâng, vâng, chúng tôi đã tự nghĩ ra nó, nhưng vẫn... Đôi khi có những tình huống khi một số trường không thể được tuần tự hóa, hoặc tốt hơn là không nên làm điều này. Trong ví dụ trên, tôi muốn lưu tất cả các trường ngoại trừ mật khẩu. Làm thế nào để đạt được điều này? Trả lời: sử dụng công cụ sửa đổi transient. transientlà một công cụ sửa đổi được chỉ định trước một trường lớp (tương tự như các công cụ sửa đổi khác, chẳng hạn như public, finalv.v.) để chỉ ra rằng trường này không được tuần tự hóa. Các trường được đánh dấu bằng từ khóa transientkhông được tuần tự hóa. Bây giờ, hãy chỉnh sửa ví dụ với người dùng của chúng tôi để sửa một lỗi nhỏ và không lưu mật khẩu của người dùng. Để thực hiện việc này, hãy đánh dấu trường tương ứng trong lớp bằng từ khóa 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...
     */
}
Nếu chúng ta chạy lại phương thức từ ví dụ trên main, chúng ta sẽ thấy mật khẩu chưa được lưu:
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'}
Tuyệt vời, chúng tôi đã đạt được mục tiêu của mình và không lưu trữ thông tin bí mật. Đặc biệt là loại thông tin này... (xin lỗi)

Khi nào nên sử dụng thoáng qua?

Cần có một ví dụ về người dùng để đi sâu vào bối cảnh tuần tự hóa. Bây giờ hãy nói cụ thể hơn về thời điểm sử dụng công cụ sửa đổi transient.

  • Các trường được tính toán theo chương trình

Một số lớp đôi khi có các trường được tính toán dựa trên các trường khác hoặc thông tin khác. Có thể nói, chúng được tính toán một cách nhanh chóng. Để đưa ra ví dụ về trường như vậy, hãy tưởng tượng một đơn đặt hàng trong một cửa hàng trực tuyến hoặc một số dịch vụ giao đồ ăn. Mỗi đơn hàng, cùng với các thông tin khác, bao gồm danh sách hàng hóa và tổng chi phí. Ngược lại, nó bao gồm tổng chi phí của mỗi sản phẩm. Hóa ra, chi phí cuối cùng không nên được đặt “bằng tay”: nó phải được tính toán theo chương trình, tổng hợp giá thành của tất cả hàng hóa. Các trường như thế này cần được tính toán theo chương trình thì không cần phải tuần tự hóa. Do đó, chúng tôi đánh dấu chúng bằng một công cụ sửa đổi transient.
class Order implements Serializable {

    private List items;
    private transient BigDecimal totalAmount; //вычисляется на ходу

}

  • Các trường có thông tin cá nhân

Ngoài ra còn có một số lớp lưu trữ thông tin cá nhân. Chúng tôi đã xem xét một ví dụ về một lớp như vậy ở đầu bài viết. Bạn không nên cho phép thông tin đó rò rỉ ra ngoài JVM. Do đó, các trường có dữ liệu như vậy phải được đánh dấu bằng công cụ sửa đổi transientnếu bạn định sắp xếp thứ tự một lớp như vậy.

  • Các trường không triển khai giao diệnSerializable

Đôi khi một lớp chứa các trường - đối tượng của các lớp khác không triển khai giao diện Serializable. Ví dụ về các trường như vậy là trình ghi nhật ký, luồng I/O, đối tượng lưu trữ kết nối cơ sở dữ liệu và các lớp tiện ích khác. Nếu bạn cố gắng tuần tự hóa một đối tượng chứa các trường không thể tuần tự hóa, bạn sẽ gặp lỗi java.io.NotSerializableException. Để tránh điều này, tất cả các trường không triển khai giao diện Serializablephải được đánh dấu bằng công cụ sửa đổi 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();
            }
        }
    }
}

  • Các trường có thông tin về trạng thái đối tượng

Vâng, một điều cuối cùng. Không cần phải tuần tự hóa các trường không phải là một phần thông tin trạng thái của đối tượng. Các ví dụ trên thuộc quy tắc này. Nhưng bạn cũng có thể đưa vào đây tất cả các trường khác được thêm vào để gỡ lỗi hoặc để thực hiện một số loại chức năng dịch vụ không mang thông tin về trạng thái của đối tượng.

transientfinal

Kết quả

Đó là tất cả. Hôm nay chúng ta đã nói về công cụ sửa đổi transient:
  1. Chúng tôi nhớ việc tuần tự hóa trong lý thuyết và thực hành.
  2. Chúng tôi nhận ra rằng để không tuần tự hóa một số trường của lớp, chúng cần được đánh dấu bằng một công cụ sửa đổi transient.
  3. Chúng tôi đã thảo luận về những tình huống nào nên sử dụng công cụ sửa đổi này. Có bốn tình huống như vậy:
    1. các trường được tính toán theo chương trình;
    2. các trường chứa thông tin bí mật;
    3. các trường không triển khai giao diện Serializable;
    4. các trường không thuộc trạng thái của đối tượng.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION