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.
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ı, head
her bağlantının alanını tekrar tekrar yazarak ve ardından bir null
değ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.
Aslında yazdığımızdan farklı bir sınıftaki bir nesneyi seri durumdan çıkarmak isteyebiliriz ObjectOutputStream
. Bu, java.util.List
daha 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 ArrayList
onu görüş sınıfımıza sarmak yerine doğrudan döndürebilirdik.
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.
String.CaseInsensitiveComparator.readResolve()