เหตุใด java.util.Optional จึงไม่สามารถต่ออนุกรมได้วิธีการทำให้วัตถุเป็นอนุกรมกับช่องดังกล่าว


109

คลาส Enum สามารถต่ออนุกรมได้ดังนั้นจึงไม่มีปัญหาในการทำให้วัตถุเป็นอนุกรมกับ enums อีกกรณีคือที่คลาสมีฟิลด์ของคลาส java.util.Optional ในกรณีนี้จะเกิดข้อยกเว้นต่อไปนี้: java.io.NotSerializableException: java.util.Optional

วิธีจัดการกับคลาสดังกล่าวจะทำให้เป็นอนุกรมได้อย่างไร เป็นไปได้ไหมที่จะส่งวัตถุดังกล่าวไปยัง Remote EJB หรือผ่าน RMI?

นี่คือตัวอย่าง:

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);
        }
    }
}

หากOptionalถูกทำเครื่องหมายเป็นSerializableแล้วจะเกิดอะไรขึ้นหากget()ส่งคืนสิ่งที่ไม่สามารถต่อเนื่องได้
WW.

10
@ สว. คุณจะได้รับNotSerializableException,แน่นอน
Marquis of Lorne

7
@ สว. มันเหมือนกับคอลเลกชัน คลาสคอลเลกชันส่วนใหญ่สามารถต่ออนุกรมกันได้ แต่อินสแตนซ์คอลเลคชันสามารถต่ออนุกรมได้ก็ต่อเมื่อออบเจ็กต์ทุกชิ้นที่มีอยู่ในคอลเลกชันนั้นสามารถต่ออนุกรมกันได้
Stuart Marks

ส่วนตัวของฉันใช้ที่นี่: ไม่ได้มีไว้เพื่อเตือนให้ผู้คนทดสอบหน่วยอย่างถูกต้องแม้กระทั่งการทำให้เป็นอนุกรมของวัตถุ เพิ่งเจอjava.io.NotSerializableException (java.util.Optional)เอง ;-(
GhostCat

คำตอบ:


172

คำตอบนี้เป็นคำตอบสำหรับคำถามในหัวข้อ "ไม่จำเป็นต้องเป็นตัวเลือกต่อเนื่องหรือไม่" คำตอบสั้น ๆ ว่า (JSR-335) กลุ่ม Java แลมบ์ดาผู้เชี่ยวชาญพิจารณาและปฏิเสธมัน โน้ตนั้นและอันนี้และอันนี้ระบุว่าเป้าหมายหลักในการออกแบบOptionalเพื่อใช้เป็นค่าส่งคืนของฟังก์ชันเมื่อค่าที่ส่งคืนอาจไม่มี จุดประสงค์คือผู้โทรตรวจสอบOptionalและดึงค่าจริงทันทีหากมีอยู่ หากไม่มีค่าผู้โทรสามารถแทนที่ค่าเริ่มต้นโยนข้อยกเว้นหรือใช้นโยบายอื่น ๆ โดยทั่วไปจะทำได้โดยการผูกมัดวิธีการที่คล่องแคล่วเรียกการสิ้นสุดของไปป์ไลน์สตรีม (หรือวิธีการอื่น ๆ ) ที่ส่งคืนOptionalค่า

มันก็ไม่ได้มีไว้สำหรับการOptionalที่จะใช้วิธีการอื่น ๆ เช่นสำหรับข้อโต้แย้งวิธีการที่ไม่จำเป็นหรือที่จะเก็บไว้เป็นข้อมูลในวัตถุ และโดยการขยายการทำให้เป็นOptionalอนุกรมจะช่วยให้สามารถจัดเก็บได้อย่างต่อเนื่องหรือส่งผ่านเครือข่ายซึ่งทั้งสองอย่างนี้สนับสนุนให้ใช้เกินเป้าหมายการออกแบบเดิม

โดยปกติแล้วจะมีวิธีที่ดีกว่าในการจัดระเบียบข้อมูลมากกว่าการจัดเก็บOptionalในเขตข้อมูล หาก getter (เช่นgetValueวิธีการในคำถาม) ส่งคืนค่าจริงOptionalจากฟิลด์จะบังคับให้ผู้เรียกใช้นโยบายบางอย่างเพื่อจัดการกับค่าว่าง ซึ่งอาจนำไปสู่พฤติกรรมที่ไม่สอดคล้องกันระหว่างผู้โทร มักจะดีกว่าที่จะมีชุดรหัสใด ๆ ที่ฟิลด์นั้นใช้นโยบายบางอย่างในเวลาที่กำหนด

บางคนต้องการที่จะนำOptionalเข้ามาในคอลเลกชันเหมือนหรือList<Optional<X>> Map<Key,Optional<Value>>ซึ่งมักจะเป็นความคิดที่ไม่ดี มักจะดีกว่าที่จะแทนที่การใช้งานเหล่านี้Optionalด้วยค่า Null-Object (ไม่ใช่nullการอ้างอิงจริง) หรือเพียงแค่ละเว้นรายการเหล่านี้จากคอลเล็กชันทั้งหมด


26
Stuart ทำได้ดีมาก ฉันต้องบอกว่ามันเป็นห่วงโซ่ที่ไม่ธรรมดาถ้าให้เหตุผลและมันเป็นสิ่งที่พิเศษในการออกแบบชั้นเรียนที่ไม่ได้ตั้งใจให้ใช้เป็นประเภทสมาชิกอินสแตนซ์ โดยเฉพาะอย่างยิ่งเมื่อไม่ได้ระบุไว้ในสัญญาคลาสใน Javadoc บางทีพวกเขาควรออกแบบคำอธิบายประกอบแทนชั้นเรียน
Marquis of Lorne

1
นี่คือบล็อกโพสต์ที่ยอดเยี่ยมเกี่ยวกับเหตุผลเบื้องหลังคำตอบของSutart
Wesley Hartford

2
สิ่งที่ดีฉันไม่จำเป็นต้องใช้ฟิลด์ที่ต่อเนื่องกันได้ มิฉะนั้นจะเป็นหายนะ
Kurru

49
คำตอบที่น่าสนใจสำหรับฉันแล้วว่าตัวเลือกการออกแบบนั้นไม่สามารถยอมรับได้อย่างสมบูรณ์และเข้าใจผิด คุณพูดว่า "โดยปกติแล้วจะมีวิธีที่ดีกว่าในการจัดระเบียบข้อมูลมากกว่าการจัดเก็บทางเลือกในฟิลด์" อาจจะเป็นเพราะเหตุใด แต่นั่นควรเป็นทางเลือกของนักออกแบบไม่ใช่ภาษา เป็นอีกกรณีหนึ่งที่ฉันพลาดตัวเลือก Scala ใน Java อย่างมาก (ตัวเลือก Scala สามารถต่ออนุกรมได้และเป็นไปตามแนวทางของ Monad)
Guillaume

38
"เป้าหมายหลักในการออกแบบสำหรับตัวเลือกคือใช้เป็นค่าส่งคืนของฟังก์ชันเมื่ออาจไม่มีค่าส่งคืน" ดูเหมือนว่าคุณไม่สามารถใช้เพื่อส่งคืนค่าใน EJB ระยะไกลได้ เยี่ยมมาก ...
Thilo

15

Serializationปัญหาที่เกี่ยวข้องจำนวนมากสามารถแก้ไขได้โดยการแยกรูปแบบซีเรียลไลซ์ถาวรจากการใช้งานรันไทม์จริงที่คุณดำเนินการ

/** 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;
    }
}

คลาสOptionalใช้พฤติกรรมที่อนุญาตให้เขียนโค้ดที่ดีเมื่อจัดการกับค่าที่อาจขาดหายไป (เมื่อเทียบกับการใช้null) แต่ไม่ได้เพิ่มประโยชน์ใด ๆ ให้กับการนำเสนอข้อมูลของคุณอย่างต่อเนื่อง มันจะทำให้ข้อมูลซีเรียลของคุณใหญ่ขึ้น ...

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

และอย่าลืมความเป็นไปได้ที่จะเปลี่ยนแปลงการนำไปใช้Myโดยสิ้นเชิงโดยไม่จำเป็นต้องปรับเปลี่ยนรูปแบบที่คงอยู่ ...


2
+1 แต่เมื่อมีฟิลด์มากขึ้นจะมีต้นแบบมากขึ้นในการคัดลอก
Marko Topolnik

1
@Marko Topolnik: มากที่สุดบรรทัดเดียวต่อคุณสมบัติและทิศทาง อย่างไรก็ตามด้วยการจัดหาตัวสร้างที่เหมาะสมclass Myซึ่งคุณมักจะทำตามที่สะดวกสำหรับการใช้งานอื่น ๆ เช่นกันreadResolveอาจเป็นการใช้งานแบบบรรทัดเดียวซึ่งจะช่วยลดมาตรฐานสำเร็จรูปให้เหลือเพียงบรรทัดเดียวต่อคุณสมบัติ ซึ่งไม่มากนักจากข้อเท็จจริงที่ว่าคุณสมบัติที่เปลี่ยนแปลงได้แต่ละรายการจะมีรหัสอย่างน้อยเจ็ดบรรทัดในชั้นเรียนMyอยู่ดี
Holger

ฉันเขียนโพสต์ครอบคลุมหัวข้อเดียวกัน โดยพื้นฐานแล้วคำตอบนี้เป็นเวอร์ชันข้อความยาว: Serialize Optional
Nicolai

ข้อเสียคือคุณต้องทำเช่นนั้นสำหรับ bean / pojo ใด ๆ ที่ใช้ optionals แต่ยังคงเป็นความคิดที่ดี
GhostCat


4

มันเป็นการละเลยที่น่าสงสัย

คุณจะต้องทำเครื่องหมายฟิลด์เป็นtransientและระบุwriteObject()วิธีการที่กำหนดเองของคุณเองซึ่งเขียนget()ผลลัพธ์เองและreadObject()วิธีการที่คืนค่าOptionalโดยการอ่านผลลัพธ์นั้นจากสตรีม ไม่ลืมที่จะโทรdefaultWriteObject()และdefaultReadObject()ตามลำดับ


ถ้าฉันเป็นเจ้าของรหัสของคลาสจะสะดวกกว่าในการจัดเก็บออบเจ็กต์ในฟิลด์ คลาสทางเลือกในกรณีดังกล่าวจะถูก จำกัด สำหรับส่วนต่อประสานของคลาส (เมธอด get จะส่งคืน Optional.ofNullable (ฟิลด์)) แต่สำหรับการแสดงภายในไม่สามารถใช้ตัวเลือกเพื่อระบุเจตนาอย่างชัดเจนว่าค่าเป็นทางเลือก
vanarchi

ฉันเพิ่งแสดงให้เห็นว่ามันเป็นไปได้ หากคุณคิดอย่างอื่นด้วยเหตุผลบางประการคำถามของคุณเกี่ยวกับอะไรกันแน่?
Marquis of Lorne

ขอบคุณสำหรับคำตอบมันเพิ่มตัวเลือกให้ฉัน ในความคิดเห็นของฉันฉันต้องการแสดงความคิดเห็นเพิ่มเติมเกี่ยวกับหัวข้อนี้ให้พิจารณา Pro และตรงกันข้ามกับแต่ละโซลูชัน ในการใช้งานเมธอด writeObject / readObject เรามีเจตนาที่ชัดเจนว่าเป็นทางเลือกในการเป็นตัวแทนของรัฐ แต่การนำไปใช้ในการทำให้เป็นอนุกรมมีความซับซ้อนมากขึ้น หากฟิลด์ถูกใช้อย่างเข้มข้นในการคำนวณ / สตรีม - สะดวกกว่าในการใช้ writeObject / readObject
vanarchi

ความคิดเห็นควรเกี่ยวข้องกับคำตอบที่ปรากฏภายใต้ ความเกี่ยวข้องของเพลงของคุณทำให้ฉันเข้าใจอย่างตรงไปตรงมา
Marquis of Lorne

3

ไลบรารี Vavr.io (เดิมคือ Javaslang) ยังมีOptionคลาสที่สามารถต่ออนุกรมได้:

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

0

หากคุณต้องการรักษารายการประเภทที่สอดคล้องกันมากขึ้นและหลีกเลี่ยงการใช้ null มีทางเลือกหนึ่งที่น่าสนใจ

คุณสามารถเก็บค่าใช้แยกประเภท เมื่อใช้ร่วมกับแลมบ์ดาสิ่งนี้จะช่วยให้:

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();

การtempแยกตัวแปรออกจากกันหลีกเลี่ยงการปิดทับเจ้าของvalueสมาชิกและทำให้เป็นอนุกรมมากเกินไป

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.