Neden java.util.Optional Serileştirilemez, nesneyi bu tür alanlarla nasıl serileştirebilirim?


107

Enum sınıfı Serializable olduğundan, nesneyi numaralandırmalarla serileştirmede sorun yoktur. Diğer durum, sınıfın java.util.Optional sınıfının alanlarına sahip olmasıdır. Bu durumda aşağıdaki istisna atılır: java.io.NotSerializableException: java.util.Optional

Bu tür sınıflarla nasıl baş edilir, nasıl serileştirilir? Bu tür nesneleri Remote EJB'ye veya RMI aracılığıyla göndermek mümkün müdür?

Bu örnek:

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

Eğer Optionalolarak işaretlenmiş Serializable, o zaman ne olur get()seri hale getirilebilir olmayan bir şeyi döndü?
WW.

10
@WW. NotSerializableException,Elbette alırsın .
Marquis of Lorne

7
@WW. Tıpkı koleksiyonlar gibi. Koleksiyon sınıflarının çoğu serileştirilebilir, ancak bir koleksiyon örneği aslında yalnızca koleksiyondaki her nesne de serileştirilebilirse serileştirilebilir.
Stuart Marks

Buradaki kişisel görüşüm: insanlara nesnelerin serileştirilmesini bile düzgün bir şekilde test etmelerini hatırlatmak değil. Ben sadece java.io.NotSerializableException (java.util.Optional) ile karşılaştım ;-(
GhostCat

Yanıtlar:


171

Bu yanıt, başlığındaki "İsteğe Bağlı Serileştirilebilir Olmamalı mı?" Sorusuna yanıttır. Kısa cevap, Java Lambda (JSR-335) uzman grubunun bunu dikkate alması ve reddetmesidir . Bu not ve bu ve bu , birincil tasarım hedefinin Optional, bir dönüş değeri olmadığında işlevlerin dönüş değeri olarak kullanılması olduğunu gösterir. Amaç, arayanın hemen kontrol etmesi Optionalve varsa gerçek değeri çıkarmasıdır. Değer yoksa, arayan kişi varsayılan bir değeri değiştirebilir, bir istisna atabilir veya başka bir politika uygulayabilir. Bu tipik olarak, Optionaldeğerleri döndüren bir akış ardışık düzeninin (veya diğer yöntemlerin) sonundan akıcı yöntem çağrıları zincirlenerek yapılır .

Opsiyonel yöntem argümanlarıOptional gibi başka şekillerde kullanılması veya bir nesnede bir alan olarak depolanması asla amaçlanmamıştır . Ve uzantı olarak, serileştirilebilir hale getirmek, kalıcı olarak depolanmasını veya bir ağ üzerinden iletilmesini mümkün kılar ve her ikisi de orijinal tasarım hedefinin çok ötesinde kullanımları teşvik eder.Optional

Genellikle verileri Optionalbir alanda depolamaktan daha iyi organize etmenin daha iyi yolları vardır . Bir alıcı (söz konusu getValueyöntem gibi ) Optionalalandan gerçek olanı döndürürse, her arayanı boş bir değerle başa çıkmak için bir politika uygulamaya zorlar. Bu muhtemelen arayanlar arasında tutarsız davranışlara yol açacaktır. Bu alanın, ayarlandığı anda bir politika uygulayacağı kod kümelerine sahip olmak genellikle daha iyidir.

Bazen insanlar veya Optionalgibi koleksiyonlara koymak isterler . Bu da genellikle kötü bir fikirdir. Bu bu kullanımlarını yerine çoğu zaman daha iyi olan Null-Nesne değerlerinin (değil gerçek tamamen koleksiyonundan, ya da sadece omit bu girişler referanslar).List<Optional<X>>Map<Key,Optional<Value>>Optionalnull


26
Aferin Stuart. Muhakeme açısından olağanüstü bir zincir olduğunu söylemeliyim ve bir örnek üye türü olarak kullanılması amaçlanmayan bir sınıf tasarlamak için olağanüstü bir şey. Özellikle Javadoc'daki sınıf sözleşmesinde belirtilmediğinde. Belki bir sınıf yerine bir açıklama tasarlamaları gerekirdi.
Marquis of Lorne

1
Bu, Sutart'ın cevabının arkasındaki mantığa dair mükemmel bir blog yazısı: blog.joda.org/2014/11/optional-in-java-se-8.html
Wesley Hartford

2
İyi ki serileştirilebilir alanlar kullanmama gerek yok. Aksi takdirde bu bir felaket olurdu
Kurru

48
İlginç cevap, bana göre tasarım seçimi tamamen kabul edilemez ve yanlıştı. "Genellikle verileri düzenlemek için bir alanda İsteğe Bağlı depolamaktan daha iyi yollar vardır" diyorsunuz, tabii, belki, neden olmasın, ancak bu dilin değil tasarımcının seçimi olmalıdır. Java'da Scala opsiyonlarını derinden özlediğim bu durumlardan bir diğeri (Scala opsiyonları Seri hale getirilebilir ve Monad'ın yönergelerini takip ediyorlar)
Guillaume

37
"İsteğe bağlı için birincil tasarım hedefi, bir dönüş değeri olmadığında işlevlerin dönüş değeri olarak kullanılmaktır." Görünüşe göre onları uzak bir EJB'de dönüş değerleri için kullanamazsınız. Harika ...
Thilo

15

SerializationKalıcı serileştirilmiş formu, üzerinde çalıştığınız gerçek çalışma zamanı uygulamasından ayırarak birçok ilgili sorun çözülebilir.

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

Sınıf , muhtemelen bulunmayan değerlerle uğraşırken (kullanımıyla karşılaştırıldığında) iyi kod yazmaya izin veren davranışıOptional uygular . Ancak verilerinizin kalıcı bir temsiline herhangi bir fayda sağlamaz. Sadece serileştirilmiş verilerinizi büyütür ...null

Yukarıdaki taslak karmaşık görünebilir, ancak bunun nedeni, deseni yalnızca bir özellik ile göstermesidir. Sınıfınız ne kadar çok özelliğe sahipse, basitliği de o kadar fazla ortaya çıkmalıdır.

Ve unutmamak Mygerekir ki, kalıcı forma adapte etmeye gerek kalmadan tamamen uygulama değişikliği imkanı ...


2
+1, ancak daha fazla alanla, onları kopyalamak için daha fazla standart şablon olacaktır.
Marko Topolnik

1
@Marko Topolnik: mülkiyet ve yön başına en fazla tek satır. Bununla birlikte, class Mygenellikle diğer kullanımlar için de kullanışlı olduğu için yaptığınız uygun bir kurucu sağlamak , readResolvetek satırlık bir uygulama olabilir, böylece kazan plakası mülk başına tek bir satıra indirgenebilir. Her değişken özelliğin sınıfta Myzaten en az yedi satır kod içerdiği gerçeği pek verilmez .
Holger

Aynı konuyu kapsayan bir yazı yazdım. Esasen bu cevabın uzun metin versiyonu: İsteğe Bağlı Serileştirme
Nicolai

Dezavantajı şudur: opsiyonelleri kullanan herhangi bir fasulye / pojo için bunu yapmanız gerekir. Ama yine de güzel fikir.
GhostCat


4

Tuhaf bir ihmal.

Alanı olarak işaretlemeniz ve sonucun kendisini yazan transientkendi özel writeObject()yönteminizi get()ve bu sonucu akıştan okuyarak readObject()geri yükleyen bir yöntem sağlamanız gerekir Optional. Sırasıyla defaultWriteObject()ve aramayı unutmamak defaultReadObject().


Bir sınıfın koduna sahipsem, bir alanda yalnızca nesneyi depolamak daha uygundur. Böyle bir durumda isteğe bağlı sınıf, sınıfın arabirimi için kısıtlanır (get yöntemi, Optional.ofNullable (alan) döndürür). Ancak dahili temsil için, değerin isteğe bağlı olduğunu açıkça belirtmek için İsteğe Bağlı'yı kullanmak mümkün değildir.
vanarchi

Sadece göstermiştir ettik olduğunu mümkün. Herhangi bir nedenle aksini düşünüyorsanız, tam olarak ne hakkında sorunuz var?
Marquis of Lorne

Cevabınız için teşekkür ederim, benim için bir seçenek ekliyor. Yorumumda, bu konu hakkında daha fazla düşünceyi göstermek, Pro ve her bir çözümün kontra'sını göz önünde bulundurmak istedim. WriteObject / readObject yöntemlerinin kullanımında, durum temsilinde İsteğe Bağlı'nın açık niyetine sahibiz, ancak serileştirmenin uygulanması daha karmaşık hale gelir. Alan, hesaplama / akışlarda yoğun olarak kullanılıyorsa - writeObject / readObject'i kullanmak daha uygundur.
vanarchi

Yorumlar, altında göründükleri cevaplarla alakalı olmalıdır. Düşüncelerinizin alaka düzeyi içtenlikle benden kaçıyor.
Marquis of Lorne

3

Vavr.io kitaplığı (eski Javaslang) ayrıca Optionserileştirilebilir sınıfa sahiptir:

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

0

Daha tutarlı bir tür listesi tutmak ve null kullanmaktan kaçınmak istiyorsanız, garip bir alternatif var.

Sen edebilirsiniz türlerinden bir kavşak kullanarak değeri depolamak . Bir lambda ile birleştiğinde, bu aşağıdaki gibi bir şeye izin verir:

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

tempDeğişkenin ayrı olması, valueüyenin sahibini kapatmayı ve dolayısıyla çok fazla serileştirmeyi önler .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.