Tại sao java.util.Optional không Serializable, làm thế nào để tuần tự hóa đối tượng với các trường như vậy


107

Lớp Enum là Serializable nên không có vấn đề gì khi serialize đối tượng với enum. Trường hợp khác là lớp có các trường thuộc lớp java.util.Optional. Trong trường hợp này, ngoại lệ sau được ném ra: java.io.NotSerializableException: java.util.Optional

Làm thế nào để đối phó với các lớp như vậy, làm thế nào để tuần tự hóa chúng? Có thể gửi các đối tượng như vậy đến EJB từ xa hoặc thông qua RMI không?

Đây là ví dụ:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Optional;

import org.junit.Test;

public class SerializationTest {

    static class My implements Serializable {

        private static final long serialVersionUID = 1L;
        Optional<Integer> value = Optional.empty();

        public void setValue(Integer i) {
            this.i = Optional.of(i);
        }

        public Optional<Integer> getValue() {
            return value;
        }
    }

    //java.io.NotSerializableException is thrown

    @Test
    public void serialize() {
        My my = new My();
        byte[] bytes = toBytes(my);
    }

    public static <T extends Serializable> byte[] toBytes(T reportInfo) {
        try (ByteArrayOutputStream bstream = new ByteArrayOutputStream()) {
            try (ObjectOutputStream ostream = new ObjectOutputStream(bstream)) {
                ostream.writeObject(reportInfo);
            }
            return bstream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Nếu Optionalđược đánh dấu là Serializable, thì điều gì sẽ xảy ra nếu get()trả về thứ gì đó không thể tuần tự hóa?
WW.

10
@WW. Bạn sẽ nhận được một NotSerializableException,khóa học.
Marquis of Lorne,

7
@WW. Nó giống như những bộ sưu tập. Hầu hết các lớp tập hợp đều có thể tuần tự hóa, nhưng một cá thể tập hợp thực sự chỉ có thể được tuần tự hóa nếu mọi đối tượng chứa trong tập hợp cũng có thể tuần tự hóa.
Stuart Marks

Ý kiến ​​cá nhân của tôi ở đây: nó không phải là để nhắc nhở mọi người kiểm tra đơn vị đúng cách ngay cả việc tuần tự hóa các đối tượng. Chỉ cần chạy vào java.io.NotSerializableException (java.util.Optional) bản thân mình ;-(
GhostCat

Câu trả lời:


171

Câu trả lời này là để trả lời cho câu hỏi trong tiêu đề, "Không nên Tùy chọn có thể được nối tiếp?" Câu trả lời ngắn gọn là nhóm chuyên gia Java Lambda (JSR-335) đã xem xét và bác bỏ nó . Lưu ý đó, và điều nàyđiều này chỉ ra rằng mục tiêu thiết kế chính Optionallà được sử dụng làm giá trị trả về của các hàm khi giá trị trả về có thể không có. Mục đích là người gọi ngay lập tức kiểm tra Optionalvà trích xuất giá trị thực nếu nó hiện diện. Nếu giá trị không có, người gọi có thể thay thế một giá trị mặc định, đưa ra một ngoại lệ hoặc áp dụng một số chính sách khác. Điều này thường được thực hiện bằng cách chuỗi các lệnh gọi phương thức thông thạo ở cuối đường ống luồng (hoặc các phương thức khác) trả về Optionalgiá trị.

Nó không bao giờ được dự định Optionalsử dụng theo những cách khác, chẳng hạn như cho các đối số phương thức tùy chọn hoặc được lưu trữ dưới dạng một trường trong một đối tượng . Và bằng cách mở rộng, việc tạo Optionaltuần tự hóa sẽ cho phép nó được lưu trữ liên tục hoặc truyền qua mạng, cả hai đều khuyến khích việc sử dụng vượt xa mục tiêu thiết kế ban đầu của nó.

Thông thường, có nhiều cách tốt hơn để tổ chức dữ liệu hơn là lưu trữ Optionaltrong một trường. Nếu một getter (chẳng hạn như getValuephương thức trong câu hỏi) trả về giá trị thực Optionaltừ trường, nó buộc mọi người gọi thực hiện một số chính sách để xử lý một giá trị trống. Điều này có thể dẫn đến hành vi thiếu cân nhắc giữa những người gọi. Thường thì tốt hơn nếu có bất kỳ bộ mã nào mà trường đó áp dụng một số chính sách tại thời điểm nó được đặt.

Đôi khi mọi người muốn đưa Optionalvào bộ sưu tập, như List<Optional<X>>hoặc Map<Key,Optional<Value>>. Đây cũng là một ý tưởng tồi. Thông thường tốt hơn là thay thế các cách sử dụng này Optionalbằng các giá trị Null-Object (không phải nulltham chiếu thực tế ) hoặc đơn giản là loại bỏ hoàn toàn các mục nhập này khỏi bộ sưu tập.


26
Làm tốt lắm Stuart. Tôi phải nói rằng đó là một chuỗi phi thường nếu suy luận, và đó là một điều phi thường để thiết kế một lớp không được sử dụng như một kiểu thành viên cá thể. Đặc biệt là khi điều đó không được nêu trong hợp đồng lớp trong Javadoc. Có lẽ họ nên thiết kế một chú thích thay vì một lớp.
Marquis of Lorne,

1
Đây là một bài viết trên blog tuyệt vời trên các lý do đằng sau câu trả lời Sutart của: blog.joda.org/2014/11/optional-in-java-se-8.html
Wesley Hartford

2
Điều tốt là tôi không cần sử dụng các trường có thể tuần tự hóa. Đây sẽ là một thảm họa khác
Kurru

48
Câu trả lời thú vị, với tôi rằng sự lựa chọn thiết kế hoàn toàn không thể chấp nhận được và sai lầm. Bạn nói "Thông thường có nhiều cách tốt hơn để tổ chức dữ liệu hơn là lưu trữ Tùy chọn trong một trường", chắc chắn, có thể, tại sao không, nhưng đó phải là lựa chọn của nhà thiết kế, không phải ngôn ngữ. Đó là một một trong những trường hợp này, nơi tôi sâu bỏ lỡ Scala optionals trong Java (optionals Scala là Serializable, và họ làm theo hướng dẫn đơn nguyên của)
Guillaume

37
"mục tiêu thiết kế chính cho Tùy chọn là được sử dụng làm giá trị trả về của các hàm khi giá trị trả về có thể không có.". Có vẻ như bạn không thể sử dụng chúng cho các giá trị trả về trong một EJB từ xa. Tuyệt vời ...
Thilo

15

Rất nhiều Serializationvấn đề liên quan có thể được giải quyết bằng cách tách biểu mẫu được tuần tự hóa liên tục khỏi việc triển khai thời gian chạy thực tế mà bạn thao tác.

/** The class you work with in your runtime */
public class My implements Serializable {
    private static final long serialVersionUID = 1L;

    Optional<Integer> value = Optional.empty();

    public void setValue(Integer i) {
        this.value = Optional.ofNullable(i);
    }

    public Optional<Integer> getValue() {
        return value;
    }
    private Object writeReplace() throws ObjectStreamException
    {
        return new MySerialized(this);
    }
}
/** The persistent representation which exists in bytestreams only */
final class MySerialized implements Serializable {
    private final Integer value;

    MySerialized(My my) {
        value=my.getValue().orElse(null);
    }
    private Object readResolve() throws ObjectStreamException {
        My my=new My();
        my.setValue(value);
        return my;
    }
}

Lớp Optionalthực hiện hành vi cho phép viết mã tốt khi xử lý các giá trị có thể vắng mặt (so với việc sử dụng null). Nhưng nó không thêm bất kỳ lợi ích nào cho việc trình bày liên tục dữ liệu của bạn. Nó sẽ chỉ làm cho dữ liệu được tuần tự hóa của bạn lớn hơn…

Bản phác thảo ở trên có thể trông phức tạp nhưng đó là vì nó thể hiện mô hình chỉ với một thuộc tính. Lớp của bạn càng có nhiều thuộc tính thì tính đơn giản của nó càng được tiết lộ.

Và đừng quên, khả năng thay đổi việc triển khai Myhoàn toàn mà không cần phải điều chỉnh hình thức liên tục…


2
+1, nhưng với nhiều trường hơn sẽ có nhiều bản soạn sẵn để sao chép chúng.
Marko Topolnik

1
@Marko Topolnik: nhiều nhất là một dòng cho mỗi thuộc tính và hướng. Tuy nhiên, bằng cách cung cấp một phương thức khởi tạo thích hợp class My, mà bạn thường làm vì nó cũng tiện dụng cho các mục đích sử dụng khác, readResolvecó thể là một triển khai một dòng, do đó giảm bảng soạn sẵn xuống một dòng cho mỗi thuộc tính. Điều này không nhiều với thực tế là mỗi thuộc tính có thể thay đổi có ít nhất bảy dòng mã trong lớp My.
Holger

Tôi đã viết một bài về chủ đề tương tự. Nó thực chất là phiên bản văn bản dài của câu trả lời này: Serialize Tùy chọn
Nicolai

Nhược điểm là: bạn cần phải làm điều đó đối với bất kỳ bean / pojo nào sử dụng tùy chọn. Nhưng vẫn còn, ý tưởng hay.
GhostCat


4

Đó là một thiếu sót đáng tò mò.

Bạn sẽ phải đánh dấu trường là transientvà cung cấp writeObject()phương thức tùy chỉnh của riêng bạn đã viết get()chính kết quả và một readObject()phương thức khôi phục Optionalbằng cách đọc kết quả đó từ luồng. Không quên gọi defaultWriteObject()defaultReadObject()tương ứng.


Nếu tôi sở hữu mã của một lớp thì việc lưu trữ đối tượng đơn thuần trong một trường sẽ thuận tiện hơn. Lớp tùy chọn trong trường hợp này sẽ bị hạn chế đối với giao diện của lớp (phương thức get sẽ trả về Optional.ofNullable (trường)). Nhưng đối với biểu diễn nội bộ, không thể sử dụng Tùy chọn để có ý định rõ ràng rằng giá trị là tùy chọn.
vanarchi

Tôi vừa mới cho thấy rằng nó tốt. Nếu bạn nghĩ khác vì một lý do nào đó, thì chính xác câu hỏi của bạn là gì?
Marquis of Lorne,

Cảm ơn câu trả lời của bạn, nó bổ sung thêm một lựa chọn cho tôi. Trong nhận xét của tôi, tôi muốn thể hiện thêm suy nghĩ về chủ đề này, hãy xem xét Pro và contra của mỗi giải pháp. Trong việc sử dụng các phương thức writeObject / readObject, chúng ta có ý định rõ ràng là Tùy chọn trong biểu diễn trạng thái, nhưng việc thực hiện tuần tự hóa trở nên phức tạp hơn. Nếu trường được sử dụng nhiều trong tính toán / luồng - sẽ thuận tiện hơn khi sử dụng writeObject / readObject.
vanarchi

Nhận xét phải có liên quan đến câu trả lời mà chúng xuất hiện bên dưới. Sự liên quan của suy nghĩ của bạn khiến tôi không hiểu.
Marquis of Lorne,

3

Thư viện Vavr.io (Javaslang cũ) cũng có Optionlớp có thể tuần tự hóa:

public interface Option<T> extends Value<T>, Serializable { ... }

0

Nếu bạn muốn duy trì một danh sách loại nhất quán hơn và tránh sử dụng null, có một lựa chọn thay thế khó hiểu.

Bạn có thể lưu trữ giá trị bằng cách sử dụng giao nhau của các loại . Được kết hợp với lambda, điều này cho phép một cái gì đó như:

private final Supplier<Optional<Integer>> suppValue;
....
List<Integer> temp = value
        .map(v -> v.map(Arrays::asList).orElseGet(ArrayList::new))
        .orElse(null);
this.suppValue = (Supplier<Optional<Integer>> & Serializable)() -> temp==null ? Optional.empty() : temp.stream().findFirst();

Việc có tempbiến riêng biệt sẽ tránh đóng lại chủ sở hữu của valuethành viên và do đó nối tiếp quá nhiều.

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.