Java serileştirme: readObject () ile readResolve () karşılaştırması


127

Etkili Java kitabı ve diğer kaynaklar, serileştirilebilir Java sınıflarıyla çalışırken readObject () yönteminin nasıl ve ne zaman kullanılacağına dair oldukça iyi bir açıklama sağlar. ReadResolve () yöntemi ise biraz gizemini koruyor. Temelde bulduğum tüm belgeler ya ikisinden sadece birinden bahsediyor ya da her ikisinden de tek tek bahsediyor.

Cevapsız kalan sorular şunlardır:

  • İki yöntem arasındaki fark nedir?
  • Hangi yöntem ne zaman uygulanmalıdır?
  • ReadResolve () özellikle neyi döndürmek açısından nasıl kullanılmalıdır?

Umarım bu konuya biraz ışık tutarsınız.


Oracle'ın String.CaseInsensitiveComparator.readResolve()
JDK'sından

Yanıtlar:


138

readResolveakıştan okunan nesneyi değiştirmek için kullanılır . Bunun için şimdiye kadar gördüğüm tek kullanım tekilleri zorlamaktır; bir nesne okunduğunda, onu tekli örnekle değiştirin. Bu, singletonu serileştirerek ve seriyi kaldırarak kimsenin başka bir örnek oluşturamamasını sağlar.


3
Kötü amaçlı kodların (hatta verilerin) bunun üstesinden gelmenin birkaç yolu vardır.
Tom Hawtin - tackline

6
Josh Bloch, etkili Java 2. baskıda bunun hangi koşullar altında kırıldığından bahsediyor. Madde 77. Birkaç yıl önce Google IO'da yaptığı bu konuşmada bundan bahsetmektedir (bazı zamanlar konuşmanın sonuna doğru): youtube.com/watch?v=pi_I7oD_uGI
calvinkrishy

17
transientAlanlardan bahsetmediği için bu cevabı biraz yetersiz buluyorum . nesneyi okuduktan sonra çözmek için readResolvekullanılır . Örnek bir kullanım, belki de bir nesnenin mevcut verilerden yeniden oluşturulabilen ve serileştirilmesi gerekmeyen bazı önbellekleri tutmasıdır; önbelleğe alınan veriler bildirilebilir ve serileştirmeden sonra yeniden oluşturulabilir. Bunun gibi şeyler, bu yöntem bunun için. transientreadResolve()
Jason C

2
@JasonC "Bunun gibi şeyler [geçici işleme] bu yöntemin ne içindir " yorumunuz yanıltıcıdır. Java belgesine bakın Serializable: " Akıştan bir örneği okunduğunda bir değiştirme ataması gereken sınıflar bu [ readResolve] özel yöntemi uygulamalıdır ..." diyor.
Opher

2
readResolve yöntemi, çok sayıda nesneyi serileştirdiğinizi ve bunları veritabanında depoladığınızı varsayarak bir köşe durumunda da kullanılabilir. Daha sonra bu verileri yeni biçime geçirmek isterseniz, bunu readResolve yönteminde kolayca gerçekleştirebilirsiniz.
Nilesh Rajani

29

Öğe 90, Etkili Java, 3. Baskı kapakları readResolveve writeReplaceseri proxy'ler için - ana kullanımları. Örnekler, alanları okumak ve yazmak için varsayılan serileştirmeyi kullandıkları için yazmaz readObjectve writeObjectyöntem değildir .

readResolvesonra readObjectdöndükten sonra çağrılır (tersine writeReplacedaha önce writeObjectve muhtemelen farklı bir nesnede çağrılır ). Yöntemin döndürdüğü thisnesne, kullanıcıya döndürülen ObjectInputStream.readObjectnesnenin ve akıştaki nesneye yapılan diğer geri referansların yerini alır . Her ikisi readResolveve writeReplaceaynı veya farklı türdeki nesneleri döndürebilir. Aynı türün döndürülmesi, alanların olması gereken finalve geriye dönük uyumluluğun gerekli olduğu veya değerlerin kopyalanması ve / veya doğrulanması gereken bazı durumlarda kullanışlıdır .

Kullanımı readResolvesingleton özelliğini zorlamaz.


9

readResolve, readObject yöntemi ile serileştirilen verileri değiştirmek için kullanılabilir. Örneğin, xstream API bu özelliği, dizilişi kaldırılacak XML'de olmayan bazı öznitelikleri başlatmak için kullanır.

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


1
XML ve Xstream, Java Serileştirme ile ilgili bir soruyla ilgili değildir ve soru yıllar önce doğru bir şekilde yanıtlanmıştı. -1
Marquis of Lorne

5
Kabul edilen yanıt, readResolve'un bir nesneyi değiştirmek için kullanıldığını belirtir. Bu cevap, seriyi kaldırma sırasında bir nesneyi değiştirmek için kullanılabilecek yararlı ek bilgileri sağlar. XStream, bunun gerçekleştiği tek olası kitaplık olarak değil, örnek olarak verilmiştir.
2014

5

readResolve, var olan bir nesneyi iade etmeniz gerektiğinde, örneğin birleştirilmesi gereken yinelenen girdileri kontrol ettiğiniz için veya (örneğin sonunda tutarlı dağıtılmış sistemlerde) farkına varmadan ulaşabilecek bir güncelleme olduğu için eski sürümler.


ReadResolve () benim için açıktı ama yine de aklımda açıklanamayan bazı sorular var ama cevabınız sadece aklımı oku, teşekkürler
Rajni Gangwar

5

readObject (), ObjectInputStream sınıfında mevcut bir yöntemdir . seri halden çıkarma sırasında nesneyi okurken readObject yöntemi, readResolve yöntemine sahip serileştirilmiş sınıf nesnesinin olup olmadığını dahili olarak kontrol eder, readResolve yöntemi mevcutsa, readResolve yöntemini çağırır ve aynısını döndürür örneği.

Bu nedenle readResolve yöntemini yazmanın amacı, hiç kimsenin serileştirerek / seriyi kaldırarak başka bir örnek alamayacağı saf tekli tasarım modeli elde etmek için iyi bir uygulamadır.



2

Bir nesneyi dosyaya kaydedilebilecek şekilde dönüştürmek için serileştirme kullanıldığında, readResolve () adlı bir yöntemi tetikleyebiliriz. Yöntem özeldir ve nesnesi seriyi kaldırma sırasında alınan aynı sınıfta tutulur. Serileştirmeden sonra, hangi nesnenin döndürüldüğünün serileştirilmiş ile aynı olmasını sağlar. Yani,instanceSer.hashCode() == instanceDeSer.hashCode()

readResolve () yöntemi statik bir yöntem değildir. in.readObject()Seri durumdan çıkarılırken sonra çağrılır, yalnızca döndürülen nesnenin aşağıdaki gibi serileştirilenle aynı olduğundan emin olur.out.writeObject(instanceSer)

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

Bu şekilde, her seferinde aynı örnek döndürüldüğü için tekli tasarım deseni uygulamasına da yardımcı olur .

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

1

Bu sorunun gerçekten eski olduğunu ve kabul edilmiş bir cevabı olduğunu biliyorum, ancak google aramada çok yukarılarda belirdiğinden, değerlendireceğimi düşündüm çünkü sağlanan hiçbir cevap, önemli olduğunu düşündüğüm üç durumu kapsamaz - aklımda bunlar için birincil kullanım yöntemleri. Elbette, hepsi aslında özel serileştirme formatına ihtiyaç olduğunu varsayar.

Örneğin toplama derslerini ele alalım. Bağlantılı bir listenin veya bir BST'nin varsayılan serileştirilmesi, öğeleri sırayla serileştirmeye kıyasla çok az performans kazancı ile büyük bir alan kaybına neden olur. Bu, bir koleksiyon bir projeksiyon veya bir görünümse daha da doğrudur - genel API'sinde ortaya çıkardığından daha büyük bir yapıya referans tutar.

  1. Serileştirilmiş nesne, özel serileştirme gerektiren değişmez alanlara sahipse, serileştirilmiş nesne, yazılan akışın bir bölümünü okumadan öncewriteObject/readObject oluşturulduğundan orijinal çözümü yetersizdir . Bağlantılı bir listenin bu minimum uygulamasını ele alın:writeObject

    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...
    }

Bu yapı, headher bağlantının alanını tekrar tekrar yazarak ve ardından bir nulldeğerle serileştirilebilir . Ancak böyle bir formatın serisini kaldırmak imkansız hale gelir: readObjectüye alanlarının değerleri değiştirilemez (şimdi olarak sabitlenmiştir null). İşte writeReplace/ readResolveçifti geliyor:

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

Yukarıdaki örnek derlenmezse (veya çalışmazsa) özür dilerim, ama umarım bu benim açımdan bahsetmek için yeterlidir. Bunun çok zor bir örnek olduğunu düşünüyorsanız, lütfen birçok işlevsel dilin JVM'de çalıştığını ve bu yaklaşımın bu durumda önemli hale geldiğini unutmayın.

  1. Aslında yazdığımızdan farklı bir sınıftaki bir nesneyi seri durumdan çıkarmak isteyebiliriz ObjectOutputStream. Bu, java.util.Listdaha uzun bir dilimden bir dilimi ortaya çıkaran bir liste uygulaması gibi görünümlerde geçerli olacaktır ArrayList. Açıkçası, tüm destek listesinin serileştirilmesi kötü bir fikirdir ve yalnızca görüntülenen dilimdeki öğeleri yazmalıyız. Yine de neden durup seriyi kaldırma işleminden sonra gereksiz bir dolaylılık seviyesine sahip olasınız? Akıştaki öğeleri basitçe okuyabilir ve ArrayListonu görüş sınıfımıza sarmak yerine doğrudan döndürebilirdik.

  2. Alternatif olarak, serileştirmeye ayrılmış benzer bir delege sınıfına sahip olmak bir tasarım seçimi olabilir. İyi bir örnek, serileştirme kodumuzu yeniden kullanmak olabilir. Örneğin, bir oluşturucu sınıfımız varsa (StringBuilder for String'e benzer), akışa boş bir oluşturucu yazarak herhangi bir koleksiyonu serileştiren bir serileştirme temsilcisi yazabiliriz, ardından koleksiyon boyutu ve koleksiyonun yineleyicisi tarafından döndürülen öğeler gelir. Seriyi kaldırma, oluşturucuyu okumayı, daha sonra okunan tüm öğeleri eklemeyi build()ve delegelerden final sonucunu döndürmeyi içerir readResolve. Bu durumda, serileştirmeyi yalnızca koleksiyon hiyerarşisinin kök sınıfında uygulamamız gerekir iterator()ve özet ve soyut uygulamalarını gerçekleştirmeleri koşuluyla, mevcut veya gelecekteki uygulamalardan hiçbir ek koda ihtiyaç duyulmaz.builder()yöntem (ikincisi aynı türden koleksiyonu yeniden oluşturmak için - ki bu kendi başına çok kullanışlı bir özellik olacaktır). Başka bir örnek, kodu tam olarak kontrol etmediğimiz bir sınıf hiyerarşisine sahip olmak olabilir - üçüncü taraf bir kütüphanedeki temel sınıflarımız, hakkında hiçbir şey bilmediğimiz herhangi bir sayıda özel alana sahip olabilir ve bunlar bir sürümden diğerine değişebilir. serileştirilmiş nesnelerimiz. Bu durumda, verileri yazmak ve seriyi kaldırma üzerine nesneyi manuel olarak yeniden oluşturmak daha güvenli olacaktır.


0

ReadResolve Yöntemi

Serializable ve Externalizable sınıflar için readResolve yöntemi, bir sınıfın akıştan okunan nesneyi çağırıcıya döndürülmeden önce değiştirmesine / çözümlemesine izin verir. ReadResolve yöntemini uygulayarak, bir sınıf, serileştirilmesi kaldırılan kendi örneklerinin türlerini ve örneklerini doğrudan denetleyebilir. Yöntem şu şekilde tanımlanır:

ANY-ACCESS-MODIFIER Object readResolve (), ObjectStreamException atar;

ReadResolve zaman yöntemi olarak adlandırılır ObjectInputStream akışından bir nesne okuma ve arayan geri döndürmek için hazırlanmaktadır. ObjectInputStream , nesne sınıfının readResolve yöntemini tanımlayıp tanımlamadığını kontrol eder. Yöntem tanımlanırsa, akıştaki nesnenin döndürülecek nesneyi belirlemesine izin vermek için readResolve yöntemi çağrılır. Döndürülen nesne, tüm kullanımlarla uyumlu bir türde olmalıdır. Uyumlu değilse , tür uyuşmazlığı keşfedildiğinde bir ClassCastException atılır.

Örneğin, bir sanal makine içinde her sembol bağlamanın yalnızca tek bir örneğinin var olduğu bir Symbol sınıfı oluşturulabilir. ReadResolve yöntem sembol önceden tanımlanmış olup olmadığını belirlemek ve kimlik kısıtlaması korumak için önceden mevcut olan eşdeğer Sembol nesnesi yerine uygulanacaktır. Bu şekilde, Symbol nesnelerinin benzersizliği serileştirme boyunca korunabilir.


0

Zaten yanıtlandığı gibi, readResolvebir nesnenin serisini kaldırırken ObjectInputStream'de kullanılan özel bir yöntemdir. Bu, gerçek örnek döndürülmeden hemen önce çağrılır. Singleton durumunda, burada serileştirilmemiş örnek referansı yerine zaten var olan tekli örnek referansını döndürmeye zorlayabiliriz. Benzer şekilde ObjectOutputStream için de sahibiz writeReplace.

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

}

}

Çıktı:

Before serialization: com.ej.item3.SingletonWithSerializable@7852e922
After deserialization: com.ej.item3.SingletonWithSerializable@7852e922
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.