Tuần tự hóa Java: readObject () so với readResolve ()


127

Cuốn sách Java hiệu quả và các nguồn khác cung cấp một lời giải thích khá hay về cách sử dụng phương thức readObject () khi làm việc với các lớp Java tuần tự hóa. Phương thức readResolve (), mặt khác, vẫn còn một chút bí ẩn. Về cơ bản tất cả các tài liệu tôi tìm thấy hoặc chỉ đề cập đến một trong hai hoặc chỉ đề cập đến cả hai cá nhân.

Các câu hỏi vẫn chưa được trả lời là:

  • Sự khác biệt giữa hai phương pháp là gì?
  • Khi nào nên thực hiện phương pháp nào?
  • Nên đọc readResolve () như thế nào, đặc biệt là về mặt trả về cái gì?

Tôi hy vọng bạn có thể làm sáng tỏ vấn đề này.


Ví dụ từ JDK của Oracle:String.CaseInsensitiveComparator.readResolve()
kevinarpe

Câu trả lời:


137

readResolveđược sử dụng để thay thế đối tượng đọc từ luồng. Việc sử dụng duy nhất tôi từng thấy cho việc này là thi hành các singletons; khi một đối tượng được đọc, thay thế nó bằng thể hiện singleton. Điều này đảm bảo rằng không ai có thể tạo một thể hiện khác bằng cách tuần tự hóa và giải tuần tự hóa đơn.


3
Có một số cách để mã độc (hoặc thậm chí dữ liệu) có thể khắc phục điều đó.
Tom Hawtin - tackline

6
Josh Bloch nói về các điều kiện theo đó điều này phá vỡ trong phiên bản Java 2 hiệu quả. Mục 77. Anh ấy đề cập đến vấn đề này trong cuộc nói chuyện này, anh ấy đã đưa ra Google IO vài năm trước (đôi khi đến cuối buổi nói chuyện): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
Tôi thấy câu trả lời này hơi bất cập, vì nó không đề cập đến transientcác lĩnh vực. readResolveđược sử dụng để giải quyết đối tượng sau khi đọc. Một ví dụ sử dụng có lẽ là một đối tượng chứa một số bộ đệm có thể được tạo lại từ dữ liệu hiện có và không cần phải được tuần tự hóa; dữ liệu được lưu trong bộ nhớ cache có thể được khai báo transientreadResolve()có thể xây dựng lại sau khi khử lưu huỳnh. Những thứ như thế là những gì phương pháp này là dành cho.
Jason C

2
@JasonC nhận xét của bạn rằng "Những việc như thế [xử lý nhất thời] là những gì phương pháp này dành cho " là sai lệch. Xem tài liệu Java để Serializablebiết: "Các lớp cần chỉ định thay thế khi một phiên bản của nó được đọc từ luồng nên thực hiện readResolvephương thức đặc biệt [ ] này ...".
Mở

2
Phương thức readResolve cũng có thể được sử dụng trong trường hợp góc trong đó giả sử bạn đã tuần tự hóa rất nhiều đối tượng và lưu trữ chúng trong cơ sở dữ liệu. Nếu tại thời điểm sau, bạn muốn di chuyển dữ liệu đó sang định dạng mới, bạn có thể dễ dàng đạt được điều đó trong phương thức readResolve.
Nilesh Rajani

29

Mục 90, Java hiệu quả, bìa thứ 3 Ed readResolvewriteReplacecho các proxy nối tiếp - công dụng chính của chúng. Các ví dụ không viết ra readObjectwriteObjectcác phương thức vì chúng đang sử dụng tuần tự hóa mặc định để đọc và ghi các trường.

readResolveđược gọi sau khi readObjectđã trở lại (ngược lại writeReplaceđược gọi trước writeObjectvà có thể trên một đối tượng khác). Đối tượng mà phương thức trả về thay thế thisđối tượng được trả về cho người dùng ObjectInputStream.readObjectvà bất kỳ tham chiếu ngược nào nữa đến đối tượng trong luồng. Cả hai readResolvewriteReplacecó thể trả về các đối tượng cùng loại hoặc khác nhau. Trả lại cùng loại là hữu ích trong một số trường hợp các trường phải finalvà khả năng tương thích ngược là bắt buộc hoặc các giá trị phải được sao chép và / hoặc xác nhận.

Sử dụng readResolvekhông thi hành tài sản singleton.


9

readResolve có thể được sử dụng để thay đổi dữ liệu được tuần tự hóa thông qua phương thức readObject. Ví dụ: API xstream sử dụng tính năng này để khởi tạo một số thuộc tính không có trong XML để được giải tuần tự hóa.

http://x-stream.github.io/faq.html#Serialization


1
XML và Xstream không liên quan đến câu hỏi về Tuần tự hóa Java và câu hỏi đã được trả lời chính xác nhiều năm trước. -1
Hầu tước Lorne

5
Câu trả lời được chấp nhận nói rằng readResolve được sử dụng để thay thế một đối tượng. Câu trả lời này cung cấp thông tin bổ sung hữu ích mà nó có thể được sử dụng để sửa đổi một đối tượng trong quá trình khử lưu huỳnh. XStream được đưa ra làm ví dụ, không phải là thư viện duy nhất có thể xảy ra.
Đã đăng

5

readResolve dành cho khi bạn có thể cần trả về một đối tượng hiện có, ví dụ: vì bạn đang kiểm tra các đầu vào trùng lặp cần được hợp nhất hoặc (ví dụ: trong các hệ thống phân tán nhất quán cuối cùng) bởi vì đó là bản cập nhật có thể đến trước khi bạn biết bất kỳ phiên bản cũ hơn.


readResolve () đã rõ ràng với tôi nhưng tôi vẫn có một số câu hỏi không thể giải thích được trong đầu nhưng câu trả lời của bạn chỉ cần đọc tâm trí của tôi, cảm ơn
Rajni Gangwar

5

readObject () là một phương thức hiện có trong lớp ObjectInputStream. Trong khi đó, đối tượng đọc tại thời điểm giải nén phương thức readObject kiểm tra bên trong xem đối tượng lớp đang được giải tuần tự có phương thức readResolve hay không nếu phương thức readResolve tồn tại thì nó sẽ gọi phương thức readResolve. ví dụ

Vì vậy, ý định viết phương thức readResolve là một cách thực hành tốt để đạt được mẫu thiết kế đơn lẻ thuần túy trong đó không ai có thể lấy một thể hiện khác bằng cách tuần tự hóa / giải tuần tự hóa.



2

Khi tuần tự hóa được sử dụng để chuyển đổi một đối tượng để có thể lưu nó trong tệp, chúng ta có thể kích hoạt một phương thức, readResolve (). Phương thức này là riêng tư và được giữ trong cùng một lớp có đối tượng đang được truy xuất trong khi giải tuần tự hóa. Nó đảm bảo rằng sau quá trình khử lưu huỳnh, đối tượng nào được trả về giống như được tuần tự hóa. Đó là,instanceSer.hashCode() == instanceDeSer.hashCode()

Phương thức readResolve () không phải là phương thức tĩnh. Sau khi in.readObject()được gọi trong khi khử lưu huỳnh, nó chỉ đảm bảo rằng đối tượng được trả về giống như đối tượng được tuần tự hóa như bên dưới trong khiout.writeObject(instanceSer)

..
    ObjectOutput out = new ObjectOutputStream(new FileOutputStream("file1.ser"));
    out.writeObject(instanceSer);
    out.close();

Theo cách này, nó cũng giúp triển khai mẫu thiết kế singleton , bởi vì mỗi lần trả lại cùng một thể hiện.

public static ABCSingleton getInstance(){
    return ABCSingleton.instance; //instance is static 
}

1

Tôi biết câu hỏi này thực sự cũ và có câu trả lời được chấp nhận, nhưng khi nó xuất hiện rất cao trong tìm kiếm google, tôi nghĩ tôi đã cân nhắc vì không có câu trả lời nào được đưa ra trong ba trường hợp tôi cho là quan trọng - trong tâm trí tôi sử dụng chính cho những điều này phương pháp. Tất nhiên, tất cả đều cho rằng thực sự cần một định dạng tuần tự hóa tùy chỉnh.

Lấy ví dụ các lớp sưu tập. Việc xê-ri hóa mặc định danh sách được liên kết hoặc BST sẽ dẫn đến việc mất dung lượng rất lớn với hiệu suất tăng rất ít so với việc chỉ tuần tự hóa các phần tử theo thứ tự. Điều này thậm chí còn đúng hơn nếu bộ sưu tập là hình chiếu hoặc chế độ xem - giữ tham chiếu đến cấu trúc lớn hơn so với API công khai của nó.

  1. Nếu đối tượng được tuần tự hóa có các trường không thay đổi cần tuần tự hóa tùy chỉnh, giải pháp ban đầu writeObject/readObjectlà không đủ, vì đối tượng được giải tuần tự được tạo trước khi đọc phần của luồng được viết writeObject. Hãy thực hiện tối thiểu danh sách liên kết này:

    public class List<E> extends Serializable {
        public final E head;
        public final List<E> tail;
    
        public List(E head, List<E> tail) {
            if (head==null)
                throw new IllegalArgumentException("null as a list element");
            this.head = head;
            this.tail = tail;
        }
    
        //methods follow...
    }

Cấu trúc này có thể được tuần tự hóa bằng cách viết đệ quy headtrường của mỗi liên kết, theo sau là một nullgiá trị. Tuy nhiên, việc khử định dạng như vậy trở nên không thể: readObjectkhông thể thay đổi giá trị của các trường thành viên (hiện đã được sửa thành null). Đây là writeReplace/ readResolvecặp:

private Object writeReplace() {
    return new Serializable() {
        private transient List<E> contents = List.this;

        private void writeObject(ObjectOutputStream oos) {
            List<E> list = contents;
            while (list!=null) {
                oos.writeObject(list.head);
                list = list.tail;
            }
            oos.writeObject(null);
        }

        private void readObject(ObjectInputStream ois) {
            List<E> tail = null;
            E head = ois.readObject();
            if (head!=null) {
                readObject(ois); //read the tail and assign it to this.contents
                this.contents = new List<>(head, this.contents)
            }                     
        }


        private Object readResolve() {
            return this.contents;
        }
    }
}

Tôi xin lỗi nếu ví dụ trên không biên dịch (hoặc làm việc), nhưng hy vọng nó đủ để minh họa quan điểm của tôi. Nếu bạn nghĩ rằng đây là một ví dụ rất xa, hãy nhớ rằng nhiều ngôn ngữ chức năng chạy trên JVM và cách tiếp cận này trở nên thiết yếu trong trường hợp của chúng.

  1. Chúng ta có thể muốn thực sự giải tuần tự hóa một đối tượng của một lớp khác với những gì chúng ta đã viết cho ObjectOutputStream. Đây sẽ là trường hợp với các khung nhìn như java.util.Listtriển khai danh sách hiển thị một lát cắt dài hơn ArrayList. Rõ ràng, tuần tự hóa toàn bộ danh sách sao lưu là một ý tưởng tồi và chúng ta chỉ nên viết các yếu tố từ lát cắt đã xem. Tại sao lại dừng lại ở đó và có một mức độ vô dụng sau khi giải trừ? Chúng ta có thể chỉ cần đọc các phần tử từ luồng vào một ArrayListvà trả lại trực tiếp thay vì gói nó trong lớp xem của chúng ta.

  2. Ngoài ra, có một lớp đại biểu tương tự dành riêng cho tuần tự hóa có thể là một lựa chọn thiết kế. Một ví dụ tốt sẽ được sử dụng lại mã serialization của chúng tôi. Ví dụ: nếu chúng ta có một lớp trình xây dựng (tương tự StringBuilder cho String), chúng ta có thể viết một ủy nhiệm tuần tự hóa tuần tự hóa bất kỳ bộ sưu tập nào bằng cách viết một trình xây dựng trống vào luồng, theo sau là kích thước bộ sưu tập và các phần tử được trả về bởi trình lặp của colection. Deserialization sẽ liên quan đến việc đọc trình xây dựng, nối thêm tất cả các phần tử đã đọc sau đó và trả về kết quả cuối cùng build()từ các đại biểu readResolve. Trong trường hợp đó, chúng ta sẽ chỉ cần triển khai tuần tự hóa trong lớp gốc của hệ thống phân cấp bộ sưu tập và không cần thêm mã từ các triển khai hiện tại hoặc tương lai, miễn là chúng triển khai trừu tượng iterator()builder()phương thức (phương thức sau để tạo lại bộ sưu tập cùng loại - bản thân nó sẽ là một tính năng rất hữu ích). Một ví dụ khác là có một hệ thống phân cấp lớp mà mã chúng tôi không kiểm soát hoàn toàn - (các) lớp cơ sở của chúng tôi từ thư viện bên thứ ba có thể có bất kỳ số trường riêng nào chúng tôi không biết và có thể thay đổi từ phiên bản này sang phiên bản khác, phá vỡ các đối tượng nối tiếp của chúng tôi. Trong trường hợp đó, sẽ an toàn hơn khi ghi dữ liệu và xây dựng lại đối tượng theo cách thủ công về khử lưu huỳnh.


0

Phương thức readResolve

Đối với các lớp Nối tiếp và Bên ngoài, phương thức readResolve cho phép một lớp thay thế / giải quyết đối tượng đọc từ luồng trước khi nó được trả về cho người gọi. Bằng cách thực hiện phương thức readResolve, một lớp có thể trực tiếp kiểm soát các kiểu và thể hiện của các thể hiện của chính nó được giải tuần tự hóa. Phương pháp được định nghĩa như sau:

MỌI TIẾP CẬN-SỬA ĐỔI Đối tượng readResolve () ném ObjectStreamException;

Các readResolve phương pháp được gọi khi ObjectInputStream đã đọc một đối tượng từ các dòng suối và đang chuẩn bị để trả lại cho người gọi. ObjectInputStream kiểm tra xem lớp của đối tượng có xác định phương thức readResolve không. Nếu phương thức được định nghĩa, phương thức readResolve được gọi để cho phép đối tượng trong luồng chỉ định đối tượng được trả về. Đối tượng trả về phải là loại tương thích với tất cả các mục đích sử dụng. Nếu nó không tương thích, ClassCastException sẽ bị ném khi loại không khớp được phát hiện.

Ví dụ, một lớp Biểu tượng có thể được tạo mà chỉ có một phiên bản duy nhất của mỗi ràng buộc biểu tượng tồn tại trong một máy ảo. Các readResolve phương pháp sẽ được thực hiện để xác định xem biểu tượng đã được đã được xác định và thay thế các tồn tại trước đó đối tượng Symbol tương đương để duy trì các hạn chế danh tính. Theo cách này, tính duy nhất của các đối tượng Biểu tượng có thể được duy trì trong suốt quá trình tuần tự hóa.


0

Như đã trả lời, readResolvelà một phương thức riêng được sử dụng trong ObjectInputStream trong khi giải tuần tự hóa một đối tượng. Điều này được gọi ngay trước khi trả lại thực tế. Trong trường hợp của Singleton, ở đây chúng ta có thể buộc trả về tham chiếu cá thể singleton đã có thay vì tham chiếu thể hiện khử lưu. Similary chúng tôi có writeReplacecho ObjectOutputStream.

Ví dụ như readResolve :

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SingletonWithSerializable implements Serializable {
private static final long serialVersionUID = 1L;

public static final SingletonWithSerializable INSTANCE = new SingletonWithSerializable();

private SingletonWithSerializable() {
    if (INSTANCE != null)
        throw new RuntimeException("Singleton instance already exists!");
}

private Object readResolve() {
    return INSTANCE;
}

public void leaveTheBuilding() {
    System.out.println("SingletonWithPublicFinalField.leaveTheBuilding() called...");
}

public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    SingletonWithSerializable instance = SingletonWithSerializable.INSTANCE;

    System.out.println("Before serialization: " + instance);

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file1.ser"))) {
        out.writeObject(instance);
    }

    try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("file1.ser"))) {
        SingletonWithSerializable readObject = (SingletonWithSerializable) in.readObject();
        System.out.println("After deserialization: " + readObject);
    }

}

}

Đầu ra:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.