Datensätze deserialisieren keine ZyklenJava

Java-Forum
Anonymous
 Datensätze deserialisieren keine Zyklen

Post by Anonymous »

Frage
Ich bin auf etwas gestoßen, das mich neugierig gemacht hat. Anscheinend behält die Serialisierung eines Datensatzes mit einer zyklischen Referenz den Zyklus nicht bei, wenn er erneut deserialisiert wird, er wird null. Wenn Sie dieselbe Übung mit einer regulären Klasse durchführen, ist der Zyklus immer noch vorhanden.
Warum ist das so?
Jeder Hinweis oder auch relevante Ausschnitte aus dem JLS wären willkommen.

Haftungsausschluss

Mir ist bewusst, dass Javas integrierte Serialisierung vorhanden ist schlecht und sollte nicht verwendet werden, dasselbe gilt für zyklische Referenzen, die nicht als transient markiert sind. Das Setup ist gekünstelt und ich werde es nirgendwo verwenden. Ein Student ist darauf gestoßen und ich bin neugierig, warum sich Java so verhält.
Minimalbeispiel
Ich habe es auf ein Zyklus-Setup wie folgt reduziert:

Code: Select all

normal.ref.o -> normal
record.ref().o -> record
Der vollständige Code lautet:

Code: Select all

import java.io.*;
import java.util.Objects;

public class Test {
static class Ref implements Serializable {
Object o;

@Override
public String toString() {
return "Ref[o=%s]".formatted(Objects.hashCode(o));
}
}

static class Normal implements Serializable {
int x;
Ref ref;

@Override
public String toString() {
return "Normal[x=%d, ref=%s]".formatted(x, ref);
}
}

record Record(int x, Ref ref) implements Serializable {}

public static void main(String[] args) throws Exception {
// Setup
Normal normalBefore = new Normal();
normalBefore.x = 5;
normalBefore.ref = new Ref();
normalBefore.ref.o = normalBefore; // cycle
// normalBefore.ref.o == normalBefore

Record recordBefore = new Record(5, new Ref());
recordBefore.ref().o = recordBefore; // cycle
// recordBefore.ref().o == recordBefore

// Test
Normal normalAfter = serializeAndDeserialize(normalBefore);
Record recordAfter = serializeAndDeserialize(recordBefore);

System.out.println("Normal before:\t" + normalBefore);
System.out.println("Normal after:\t" + normalAfter);
System.out.println("Record before:\t" + recordBefore);
System.out.println("Record after:\t" + recordAfter);
System.out.println("recordAfter.ref().o:\t" + recordAfter.ref().o);
}

static  T serializeAndDeserialize(T obj) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();

ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
T result = (T) ois.readObject();
ois.close();

return result;
}
}
Es wird Folgendes ausgegeben:

Code: Select all

Normal before:  Normal[x=5, ref=Ref[o=1732398722]]
Normal after:   Normal[x=5, ref=Ref[o=1579572132]]

Record before:  Record[x=5, ref=Ref[o=1323468385]]
Record after:   Record[x=5, ref=Ref[o=0]]

recordAfter.ref().o:    null
Wie wir sehen können, serialisiert und deserialisiert Normal vollständig (ein vollständiger toString()-Druck würde aufgrund des Zyklus zu einem StackOverflow führen).
Der Record lässt jedoch den zyklischen Rückverweis auf sich selbst innerhalb des Ref-Wrapper-Objekts fallen, er wird null.
Code-Anleitung
Um das Lesen des Codes zu erleichtern, besteht er aus einem Wrapper für den Zyklus, Ref:

Code: Select all

static class Ref implements Serializable {
Object o;

@Override
public String toString() {
return "Ref[o=%s]".formatted(Objects.hashCode(o));
}
}
Dies ist erforderlich, da wir sonst keinen Zyklus für den Datensatz erstellen könnten, da seine Felder nicht änderbar sind (

Code: Select all

final
).
Dann die eigentlichen Datenträgerklassen:

Code: Select all

static class Normal implements Serializable {
int x;
Ref ref;

@Override
public String toString() {
return "Normal[x=%d, ref=%s]".formatted(x, ref);
}
}

record Record(int x, Ref ref) implements Serializable {}
Das int x ist technisch veraltet, aber ich habe es hinzugefügt, um zu zeigen, dass Nicht-Zyklen immer noch vollständig deserialisiert sind.
Dann haben wir eine Methode, die einfach jedes gegebene Objekt serialisiert und deserialisiert und das Zyklus-Setup in main sowie den abschließenden Test.

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post