Mathias: Objekte, Verweise

Hallo,

ich habe folgendes Schema:

class Test1 {
   public Test2 t2;
}

class Test2 {
   public Test3 t3;
}

class Test3 {
   public Vector v = new Vector();

public void loadVector() {
      this.v.addElement("Test");
   }
}

Ich habe also innerhalb von selbstefinierten Klassen, Verweise auf weitere selbstefinierte Klassen. Wenn ich nun eine Instanz der Klasse t1 über ein Socket versende (die Klassen sind alle serialisierbar), werden dann automatisch alle Verweise auf t2 und t3 mitgesandt? Kann ich dann in der Klasse, die die Daten empfängt darauf gehen, dass ich auf das Objekt t3 zugreifen kann und dort das Element Test in diesem Vektor vorhanden ist? Wenn nicht, wie kann ich das sicherstellen?

Gruss Mathias

  1. Hi,

    Ich habe also innerhalb von selbstefinierten Klassen, Verweise auf weitere selbstefinierte Klassen. Wenn ich nun eine Instanz der Klasse t1 über ein Socket versende (die Klassen sind alle serialisierbar),

    Nein, nach dem Standardverfahren zumindest nicht (ohne weiteres) mit Deinem Beispiel-Code.
    Aus folgendem Beispiel (etwas länger, sollte lauffähig sein):

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    import java.util.Calendar;
    import java.util.Vector;

    public class TestStandardSerialization {

    static final String TARGET_DIR = "D:\temp";
        static final String SERIAL_FILE_NAME = "TestSerialisation.ser";
        static final File   SERIAL_FILE = new File(new File(TARGET_DIR), SERIAL_FILE_NAME);

    public static void main(String[] args) {
            FileOutputStream out;
            FileInputStream in;
            ObjectOutputStream os;
            ObjectInputStream is;

    // object muss java.io.Serializable implementieren
            try {
                out = new FileOutputStream(SERIAL_FILE);
                os = new ObjectOutputStream(out);
                os.writeObject(new NonSerializable());
                os.close();
            } catch (IOException e) {
                System.err.println(e.toString());
            }

    // alle nicht-statischen[*] und nicht-transienten muessen java.io.Serializable implementieren
            try {
                out = new FileOutputStream(SERIAL_FILE);
                os = new ObjectOutputStream(out);
                // transiente werden ignoriert
                os.writeObject(new SerializableWithTransient());
                // beachte: die Exception wird _erst_ durch folgendes Statement erzwungen
                os.writeObject(new SerializableNoTransient());
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            // [*] statische Member gehoeren nicht zu einer Instanz dieser Klasse

    // recursive successful write
            try {
                out = new FileOutputStream(SERIAL_FILE);
                os = new ObjectOutputStream(out);
                System.out.println("write start: " + timeStamp());
                os.writeObject(new SerializableRecursive());
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("example failed!");
            }

    // wait some time
            try {
                Thread.sleep(5 * 1000L);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
                throw new RuntimeException("example failed!");
            }

    // read again
            try {
                in = new FileInputStream(SERIAL_FILE);
                is = new ObjectInputStream(in);
                System.out.println("read start: " + timeStamp());
                SerializableRecursive toSerialize = new SerializableRecursive();
                toSerialize = (SerializableRecursive) is.readObject();
                is.close();
                // print container
                System.out.println(toSerialize.cont.vec);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("example failed!");
            }
        }

    static String timeStamp() {
            return Calendar.getInstance().getTime().toString();
        }

    // --------------------------------------------------- inner classes
        static class NonSerializable {
        }

    static class SerializableWithTransient implements Serializable {
            public transient NonSerializable sMember = new NonSerializable();
        }

    static class SerializableNoTransient implements Serializable {
            public NonSerializable nsMember = new NonSerializable();
        }

    static class SerializableRecursive implements Serializable {
            public AnyContainer cont = new AnyContainer();
        }

    static class AnyContainer implements Serializable {
            public Vector vec = new Vector();
            public AnyContainer() {
                this.vec.addElement("Object written: " +TestStandardSerialization.timeStamp());
            }
        }
    }

    geht hervor:

    • dass die finale(!) Methode writeObject(Object) nur Instanzen von Klassen, die das Marker-Interface <java.io.Serializable> implementieren, serialisiert[1]. Implizit von der Serialisierung ausgeschlossen sind statische Member (gehören zur Klasse). Explizit alle diejenigen, die mit dem modifier <transient> gekennzeichnet sind.

    • writeObject(Object) funktioniert rekursiv. Dabei wird versucht, das durch alle nicht-statischen und nicht-transienten Member gebildete Objekt-Geflecht in einen (Byte)Strom zu überführen (dabei Abbruch, wenn vorherige Bedingung nicht erüllt ist).
      Zu beachten:

    • Das rekursive Verhalten kann leicht dazu führen, dass mehr Objekte als erwartet serialisiert werden.
    • Und dass während dieses möglicherweise unerwartet lange dauernden Vorgangs die GarbageCollection dieser Objekte noch blockiert ist, weil noch intern Referenzen darauf gehalten werden (auch wenn im restlichen Porgrammfluss keinerlei Referenzen darauf mehr bestehen sollten).

    [1]
    Das finde ich unglücklich, da es aus der Signatur der Methode alleine nicht hervorgeht (und man ein Implementierungsdetail zusaetzlich kennen muss). Da in Java alle Klassen implizit von Object erben, hätte ich hier einen Parameter vom Typ Serializable erwartet.

    [entfernt..] werden dann automatisch alle Verweise auf t2 und t3 mitgesandt? Kann ich dann in der Klasse, die die Daten empfängt darauf gehen, dass ich auf das Objekt t3 zugreifen kann und dort das Element Test in diesem Vektor vorhanden ist? Wenn nicht, wie kann ich das sicherstellen?

    siehe oben.

    Viele Grüße,
    Martin Jung

    1. Hallo Martin,

      ich hab mal versucht dein Beispiel zu verstehen. Also in meinem Beispiel waren das ja keine inneren Klassen und zudem waren sie auch nicht statisch. Spielt das eine Rolle? Wenn ich dich richtig verstanden habe, dann sollten alle Objekte innerhalb von Klassen mit Verweisen auf andere Klassen "mitgesandt" werden solange sie serialisierbar und nicht statisch sind. Dh. im meinem Beispiel sollte das funktionieren und der Wert im Vektor am "Zielort" noch vorhanden sein?
      Zum versenden benutze ich den ObjectOutputStream.

      Gruss Mathias

      1. Hi Mathias,

        ich hab mal versucht dein Beispiel zu verstehen.

        Gut! ;-)

        Also in meinem Beispiel waren das ja keine inneren Klassen und zudem waren sie auch nicht statisch. Spielt das eine Rolle?

        Gute Frage - darauf ein Jein. Für die Verdeutlichung dessen, was das Beispiel zeigen sollte, spielt das zunächst einmal keine Rolle.

        Innere Klassen sind ein Mittel zur Organisation von Klassendefinitionen[1]. Auf Ebene des Bytecodes werden innere Klassen in individuelle Class-Dateien kompiliert und ihre "Inner/Outer"-Beziehungen zur Laufzeit über "ganz normale" Objektbeziehungen realisiert (der Compiler erweitert beteiligte Klassen hierzu geeignet um entsprechende Felder). Statisch (also zur Compile-Zeit) werden diese Beziehungen jedoch auch festgehalten, nämlich im Namen der inneren Klassen (<OuterClassName$InnerClassName.class>). Und da der Klassen-Name durch ObjectOutputStream.writeObject(Object) ebenso serialisiert wird, muss in der Ziel VM bei Deserialisierung mit ObjectInputStream.readObject() auch die Definition einer äußeren Klasse vorhanden sein[2]. Ist dies nicht sicherzustellen, müssen zu serialisierende Objekte richtigerweise durch Top-Level Klassen definiert werden.

        Wenn ich dich richtig verstanden habe, dann sollten alle Objekte innerhalb von Klassen mit Verweisen auf andere Klassen "mitgesandt" werden solange sie serialisierbar und nicht statisch sind.

        Nein. Wie geschrieben, dürfen zu serialisierende Member zusätzlich nicht das transient-Schlüsselwert besitzen.

        Dh. im meinem Beispiel sollte das funktionieren und der Wert im Vektor am "Zielort" noch vorhanden sein?

        Ähm - wenn Du die Klassen serialisierbar machst (und gegebenfalls in Top-Level Klassen extrahierst), dann ja. Dies zu zeigen war die Absicht meines Beispiels (Hinweis durch Re-Zitat: der Beispielcode "sollte lauffähig sein").

        Sinnvolle Korrekturen zum Beispiel:

        letzter try/catch-Block in main()

        System.out.println("read start: " + timeStamp());
                **  // SerializableRecursive toSerialize = new SerializableRecursive();
                **  SerializableRecursive toSerialize = (SerializableRecursive) is.readObject();

        Klasse AnyContainer (komplett ersetzen)

        static class AnyContainer implements Serializable {
                public Vector vec = new Vector();

        /** @see java.io.Serializable */
                private void writeObject(ObjectOutputStream os) throws IOException {
                    // individuelles Verhalten
                    this.vec.addElement("Object serialized: " +TestStandardSerialization.timeStamp());
                    // dann erst Aufruf des default write Mechanismus
                    os.defaultWriteObject();
                }
                private void readObject(java.io.ObjectInputStream is)
                        throws IOException, ClassNotFoundException {
                    // zuerst Aufruf des default read Mechanismus
                    is.defaultReadObject();
                    // individuelles Verhalten
                    this.vec.addElement("Object added during deserialization: " +TestStandardSerialization.timeStamp());
                }
            }

        Zum versenden benutze ich den ObjectOutputStream.

        Habe ich mir gedacht.

        Viele Grüße,
        Martin Jung

        [1]
        ..und ich habe innere Klassen verwendet, weil diese Teil des Beispiels sind und so einfach lauffähiger Code gepostet werden kann. Dies ist übrigens eine sinnvoller Verwendungsweck für innere Klassen.

        [2]
        Der Aufruf von ObjectInputStream.readObject() führt bei Abwesenheit der äußeren Klasse dann zu einer ClassNotFoundException.
        Dieses Standard-Verhalten kann man natürlich dadurch ändern, dass man die Deserialisierung "eben" selbst realisiert.. (spezielle ClassLoader etc.) ;-)

        1. Hallo,

          Dh. im meinem Beispiel sollte das funktionieren und der Wert im Vektor am "Zielort" noch vorhanden sein?
          Ähm - wenn Du die Klassen serialisierbar machst (und gegebenfalls in Top-Level Klassen extrahierst), dann ja. Dies zu zeigen war die Absicht meines Beispiels (Hinweis durch Re-Zitat: der Beispielcode "sollte lauffähig sein").

          Ich kriegs nicht hin, obwohl die Klassen serialisierbar sind. Ich habe folgendes Konstrukt:

          public class Channel implements Serializable {
              private int id;
           private String name;
           private String description;
           private UserList userList;

          public Channel(int id, String name) {
            this.id = id;
            this.name = name;
            this.userList = new UserList();
           }
          }

          public class UserList implements Serializable {

          public Vector userList;
           public String test = "Hallo";

          public UserList() {
            this.userList = new Vector();
           }
          }

          public class User implements Serializable {
              private int id;
              private String nickName;
              private String password;

          public User(int id, String nickName, String password) {
                  this.id = id;
                  this.nickName = nickName;
                  this.password = password;
              }
          }

          Wenn ich jetzt einen Channel über ein Socket versende, verliere ich aus irgendeinem Grund die User in der UserList. Der String test ist jedoch noch vorhanden. Kann das überhaupt sein, oder muss da an einer anderen Stelle etwas nicht stimmen? Kann es evtl. was mit den Threads zu tun haben, die ich server- und clientseitig benutze...?

          Gruss Mathias

          1. Hi,

            Wenn ich jetzt einen Channel über ein Socket versende, verliere ich aus irgendeinem Grund die User in der UserList.

            Welche User? Im geposteten Code fügst Du der User-Liste zumindest keinen User hinzu.

            Viele Grüße,
            Martin Jung

            1. Hallo,

              Nein, im angefügten Code nicht, er zeigt ja auch nur den Aufbau...

              Gruss Mathias

              1. Hi Mathias,

                habe kaum Zeit, und zum Testen komme ich gleich gar nicht.

                Aber ich vermute, Dein Problem liegt in der markierten Zeile.

                public class UserList implements Serializable {

                public Vector userList;
                 public String test = "Hallo";

                public UserList() {
                  this.userList = new Vector(); ***********
                 }
                }

                In der Ziel-VM wird der parameterlose Konstruktor für die Desarialisierung der serialisierten Instanz verwendet. Darin legst Du aber einen neue (und Leere) Vektor-Instanz an - und löschst somit die serialisierten User. Es müsste dann funktionieren, wenn Du die entsprechende Variable bereits bei der Deklaration (oder andersweitig) initialisierst (der Konstruktor wird erst danach aufgerufen).

                Viele Grüße,
                Martin Jung

                1. Hallo Martin,

                  es lag nicht daran - denke ich. Auf jeden Fall habe ich den Code so abgeändert, dass ich bereits bei der Deklaration eine Instanz anlege und es hat nichts gebracht.

                  Bevor ich aber einen einzelnen Channel mit Userlisten und Users über ein Socket versende, versende ich davor noch eine ChannelList, welche Channels enthält - und zwar dieselben Channels, einfach noch ohne die Userlists usw. Wenn ich diese Channellist im Voraus NICHT versende, dann sind in den nachher versendeten Channels die Userlisten und die User noch vorhanden...?!?!?!

                  Gruss Mathias

                  1. ...ehrlich gesagt, ich habe keine Ahnung was ich jetzt tun soll. Die Klasse ChannelList sieht etwa so aus:

                    public class ChannelList implements Serializable {
                     public Vector channelList = new Vector();

                    public void addChannel(Channel channel){
                      this.channelList.addElement(channel);
                     }

                    public void removeChannel(Channel channel){
                      this.channelList.removeElement(channel);
                     }

                    public Channel getChannel(int channelId){
                      Enumeration enum = this.channelList.elements();
                      while(enum.hasMoreElements()){
                       Channel channel = (Channel)enum.nextElement();
                       if(channel.getId() == channelId){
                        return channel;
                       }
                      }
                      return null;
                     }

                    public Enumeration elements(){
                      return this.channelList.elements();
                     }
                    }

                  2. Hi,

                    hmmm, wo is meine urpsrüngliches Antwort-Posting hin?
                    Also nochmal.

                    es lag nicht daran - denke ich. Auf jeden Fall habe ich den Code so abgeändert, dass ich bereits bei der Deklaration eine Instanz anlege und es hat nichts gebracht.

                    Wegen des Problems unten.

                    Bevor ich aber einen einzelnen Channel mit Userlisten und Users über ein Socket versende, versende ich davor noch eine ChannelList, welche Channels enthält - und zwar dieselben Channels, einfach noch ohne die Userlists usw. Wenn ich diese Channellist im Voraus NICHT versende, dann sind in den nachher versendeten Channels die Userlisten und die User noch vorhanden...?!?!?!

                    Yep. writeObject() verwendet einen internen Cache, der dies verursacht. Wirf nochmal einen Blick auf die API von ObjectOutpusStream (.reset()) oder versuche er es auch in anderen Foren http://forum.java.sun.com/thread.jsp?forum=62&thread=131752&tstart=105&trange=15

                    Gruss Mathias

                    Viele Grüße,
                    Martin Jung

                    1. Hallo Martin!

                      Also als erstes Vielen vielen Dank. Da wär ich nie draufgekommen...

                      Nun habe ich mich mit der reset() Methode beschäftigt, deren Verwendung ja offenbar zuerst eine mit mark() gesetzte Position voraussetzt. Da ich diesen "Cache" ganz abschalten möchte, habe ich das so versucht:

                      public void run() {
                        try {
                         while(true) {
                          this.in.mark(0);
                          this.handleIn((Message)in.readObject());
                          this.in.reset();
                         }
                        }catch(IOException e){
                         System.out.println(e);
                        }catch(ClassNotFoundException cnfe) {
                         System.out.println(cnfe);
                        }
                       }

                      Dabei erhalte ich immer die Exception java.io.IOException: mark/reset not supported

                      Weisst du was ich da falsch mache?

                      PS. Gehört sowas eigentlich nun in einen neuen Forums-Thread?

                      Gruss Mathias

                      1. Hi Mathias,

                        Also als erstes Vielen vielen Dank. Da wär ich nie draufgekommen...

                        Keine Ursache.

                        this.in.mark(0);
                            this.handleIn((Message)in.readObject());
                            this.in.reset();

                        Dabei erhalte ich immer die Exception java.io.IOException: mark/reset not supported
                        Weisst du was ich da falsch mache?

                        Aehm, ich meinte die Methode reset() von ObjectOUTPUTStream. Auf Empfängerseite hilft Dir das herzlich wenig, denn da ist das Kind bereits in den Brunnen gefallen ;-)
                        (werde bei Gelegenheit das in einem der vorherigen Antworten gepostete Beispiel entsprechend um diesen Aspekt erweitern...)

                        PS. Gehört sowas eigentlich nun in einen neuen Forums-Thread?

                        Nein - anderereits ist dieser Thread bald im Archiv verschwunden. Möglicherweise gehört das aber eher in ein Java-Forum. Ich kann Dir allerdings kein deutsches empfehlen, da ich keines kenne.

                        Viele Grüße,
                        Martin Jung

                        1. Hallo Martin,

                          Aehm, ich meinte die Methode reset() von ObjectOUTPUTStream. Auf Empfängerseite hilft Dir das herzlich wenig, denn da ist das Kind bereits in den Brunnen gefallen ;-)

                          Ok, es hat geklappt!

                          PS. Gehört sowas eigentlich nun in einen neuen Forums-Thread?
                          Nein - anderereits ist dieser Thread bald im Archiv verschwunden. Möglicherweise gehört das aber eher in ein Java-Forum. Ich kann Dir allerdings kein deutsches empfehlen, da ich keines kenne.

                          Dürfen hier keine Java Fragen gestellt werden? Es hat doch extra eine solche Rubrik...

                          Nochmals Danke.

                          Gruss Mathias

                          1. Hi Mathias,

                            Ok, es hat geklappt!

                            Uff!

                            Dürfen hier keine Java Fragen gestellt werden? Es hat doch extra eine solche Rubrik...

                            Die Frage hast Du ja mit dem nachfolgenden Satz bereits selbst beantwortet.

                            Viele Grüße,
                            Martin Jung