Montag, 9. August 2010

Java .clone()

Man kann Objekte in Java per eineInstanz.clone() kopieren, wenn die Klasse das Interface java.lang.Cloneable() implementiert. Dieses Interface ist ein sogenanntes Marker-Interface ohne definierte Methoden. Die Methode .clone() wird hingegen von der Basisklasse Object definert undzwar als protected. Eine Klasse, welche Cloneable implementiert sollte per Konvention die Methode .clone() aus Object als public Methode überschreiben. Man beachte, das die Klasse Object selbst nicht das Cloneable Interface implementiert und man daher auf Instanzen von Object eine CloneNotSupportedException beim Aufruf von .clone() erhält.
Die überschriebene public Variante von .clone() sollte als erstes super.clone() aufrufen und dann alle Member des Klon-Objektes, welche Variablen (ohne final Schlüsselwort) auf nicht-konstante Daten (mutable) besitzt, mit Kopien solcher Daten besetzen. Denn die Konvention von .clone() sieht eine komplette Unabhängigkeit von Objekten zu ihren Klonen vor. Die Implementierung von .clone() in Object vollzieht eine nicht-rekursive Zuweisung aller Membervariablen vom Original an die entsprechenden Membervariablen vom Klon. Es findet keine Überprüfung auf Referenzwerte als Daten statt. Daher muß sich die Cloneable implementierende Klasse um diese Details kümmern.
Die Intention des Klon-Vorgangs aus Object.clone() ist es folgende Eigenschaften zu haben:
  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x)

Links:

Java Serialisierung

Serialisierung ist die Möglichkeit ein Objekt aus dem Hauptspeicher in einen persistenten Zustand zu transformieren. Entweder handelt es sich um immutable (unveränderliche) Werte aus den Java Standardtypen oder die betreffende Klasse muss das Interface Serializable implementieren. Dieses Interface besitzt keine Methoden, sondern dient als sogenanntes Marker-Interface zur Freischaltung der Serialisierbarkeit. Außerdem müssen in der aufsteigenden Vererbungshierarchie (in Richtung Object) alle Superklassen ebenfalls Serializable implementieren, außer man definiert eigene Methoden für die Serialisierung, welche dann aber nicht die Default-Serialisierung (s.u.) aufrufen dürfen. Alle Sub-Klassen einer serialisierbaren Klasse sind automatisch als serialisierbar markiert. Um als Subklasse diese Serialisierbarkeit wieder zu entfernen kann man folgenden Trick anwenden:
/**
* Modified from: "Discover the secrets of the Java Serialization API"
* by Todd Greanier July 2000
*/
private void writeObject(ObjectOutputStream out) throws IOException
{
  throw new NotSerializableException("Serialisierung wird nicht unerstützt");
}

private void readObject(ObjectInputStream in) throws IOException
{
  throw new NotSerializableException("Serialisierung wird nicht unerstützt");
}

Alle Membervariablen einer Instanz einer serialisierbaren Klasse werden rekursiv serialisert. Sollte bei dieser Rekursion ein Element auftauchen, das die Serialisation nicht unterstützt, dann wird eine Exception geworfen. Um dies zu verhindern, kann man betreffende nicht zu serialiserende Member der Instanz mit transient kennzeichen. Solche Elemente werden bei der rekursiven Serialisierung ausgelassen und müssen mit entsprechendem Programmcode beim Deserialisieren manuell wiederhergestellt werden.

Beispiel:

/**
* Modified from: "Discover the secrets of the Java Serialization API"
* by Todd Greanier July 2000
*/
import java.io.Serializable;

public class PersistentAnimation implements Serializable, Runnable {
    transient private Thread animator;
    private int animationSpeed;

    public PersistentAnimation(int animationSpeed) {
        this.animationSpeed = animationSpeed;
        startAnimation();
    }

    private void startAnimation() {
        animator = new Thread(this);
        animator.start();
    }

    public void run() {
        while (true) {
            // Hier steht Code für eigentliche Animation
        }
    }

    /**
     * Hook-Methode == Inverse of Control (IOC)
     * PersistentAnimation aPA = ...;
     * ObjectOutputStream aOOS = ...;
     * 
     * aOOs.writeObject(aPA);
     *      //Pseudcode der Implementierung
     *      { if(aPA besitzt Methode "private void writeObject(ObjectOutputStream)")
     *        { 
     *              //speichere aktuelles Argument in Membervariable
     *              aktiveObject = aPA;
     *              aPA.writeObject(this);
     *         } else writeUnhooked(aPA);
     *      }
     * 
     * aOOs.defaultWriteObject();
     *      //Pseudocode
     *      { 
     *          //schreibe Aufrufer der Methode
     *          writeUnhooked(aktiveObject);
     *      }
     * 
     * @param out
     *            ObjectOutputStream, welches this serialisiert:
     *            out.writeObject(this);
     * @throws IOException
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    /**
     * @param in
     *            ObjectInputStream, welches this per Deserialisierung
     *            initialisiert.
     * @throws IOException
     * @throws ClassNotFoundException
     *             Wenn Klassendatei nicht zur Verfügung steht
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        // Initialisiere this mit Zustand aus vorheriger Serialisation
        in.defaultReadObject();
        // transient Member wurde nicht serialisert, also manuell besetzen
        startAnimation();

    }
}

Man beachte, dass der Aufruf von out.defaultWriteObject() alle Felder der aktuellen Objektinstanz, unabhängig von einer manuell erfolgenden Serialisierung, serialisiert. Dabei serialisiert es alle Felder der Instanz aus den Klassen der Vererbungshierarchie beginnend mit der Basisklasse (Tiefensuche) und endend mit der Laufzeitklasse. Analog verhält es sich mit in.defaultReadObject().


Quellen:

Javas equals() und hashCode() Methoden

Die von der Basisklasse Object realisierte Implementierung von eineInstanz.equals() macht folgendes:

x.equals(y){ return this==y;}

Die Eigenschaften von .equals() sollen die Äquivalenzrelation erfüllen und pro Programmlauf konsistent sein, indem Sinne, dass bei Konstanz der für die Evaluierung der Äquivalenz herangezogenen Parameter des Objekts auch der Rückgabewert von .equals() gleich bleibt.

Die Methode .hashCode() soll bei Auswertung von .equals()==true auf den beteiligten Objekten auch den gleichen Hashwert zur Folge haben. Umgehrt bedeutet das, daß bei unterschiedlichen Hashwerten das Ergebnis von .equals() auf den beteiligten Objekten den Wert false haben muß.

Aber das heißt nicht, dass Objekte, für die .equals() auf false hinausläuft einen unterschiedlichen Hashwert haben müssen. Es wird nur zwecks Performanz von Hash-Implementierungen in Java-System-Klassen empfohlen einen unterschiedlichen Haswert zu erzeugen.

Also zusammengefasst in Pseucode:

Von .equals() ausgehend:
switch(x.equals(y)):{
case true:
  ( x.hashCode()==y.hashCode() ) == true
case false:
  ( x.hashCode()==y.hashCode() || x.hashCode()!=y.hashCode() [letzteres wird empfohlen]) == true
}

Von .hashCode() ausgehend:
switch(x.hashCode()==y.hashCode()){
case true:
  ( x.equals(y) || !(x.equals(y) ) == true
case false:
  (x.equals(y) == false) == true
} 

Also ist x.equals(y) == true hinreichend (aber nicht notwendig) für x.hashCode()==y.hashCode()==true und x.hashCode()==y.hashCode()==false hinreichend (aber nicht notwendig) für x.equals(y)==false.

Java Class Datei

Jede Klasse in Java hat ein Singelton-Objekt vom Typ Class, welches zurückgegeben werden kann durch eineInstanz.getClass() oder durch das Literal EineKlasse.class. Dieses Singleton-Objekt ist dasjenige Objekt, welches bei synchronized static Methoden der Klasse zum Einsatz kommt.

Aufgrund der Tatsache, daß das Class Objekt dem Singleton-Pattern entspricht und die .equals() Methode aus Object von der Klasse Class nicht überschrieben wird, gilt.

Class a,b;

...

a.equals(b) <==> a==b.