[JAVA] Punkt 89: Für die Steuerung bevorzugen Sie Aufzählungstypen gegenüber readResolve

89. Wählen Sie zum Beispiel die Steuerung den Aufzählungstyp aus readResolve aus

Punkt 3 führte das Singleton-Muster mit dem folgenden Beispiel ein.

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    ...
    }

    public void leaveTheBuilding() {
    ...
    }
}

Diese Klasse ist bei der Implementierung von Serializable kein Singleton mehr. Es ist kein Singleton mehr, unabhängig davon, ob es sich um ein standardmäßiges serialisiertes Formular oder ein benutzerdefiniertes serialisiertes Formular handelt und ob es eine readObject-Methode (Item87) bereitstellt. Die readObject-Methode, ob standardmäßig oder nicht, gibt eine neu erstellte Instanz zurück, die sich von der unterscheidet, die beim Initialisieren der Klasse erstellt wurde.

readResolve Mit der readResolve-Methode kann eine Instanz zurückgegeben werden, die sich von dem von readObject erstellten Objekt unterscheidet.

Wenn die deserialisierte Klasse eine readResolve-Methode ordnungsgemäß definiert, wird diese Methode nach der Deserialisierung für das neu erstellte Objekt aufgerufen. Die von dieser Methode zurückgegebene Referenz ersetzt das neu erstellte Objekt. Mit dieser Funktion gibt es in den meisten Fällen keine Verweise auf neu erstellte Objekte, und neu erstellte Objekte werden bald von der Garbage Collection verarbeitet.

Wenn die obige Elvis-Klasse Serializable implementiert, kann durch Hinzufügen der readResolve-Methode wie unten gezeigt garantiert werden, dass sie Singleton ist.

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    ...
    }

    public void leaveTheBuilding() {
    ...
    }
    private Object readResolve() {
        return INSTANCE;
    }
}

Diese readResolve-Methode ignoriert das deserialisierte Objekt und gibt die Instanz zurück, die während der Klasseninitialisierung erstellt wurde. Daher dürfen serialisierte Elvis-Instanzen keine Daten enthalten. Das heißt, alle Felder müssen als vorübergehend deklariert werden.

** Wenn Sie eine Instanz mit readResolve steuern möchten, müssen Sie allen Feldern mit Referenzen ** Transienten hinzufügen. Andernfalls könnte ein Angriff wie der in Item88 gezeigte ausgeführt werden, bevor readResolve ausgeführt wird.

Der Angriff ist etwas kompliziert, aber die Idee ist einfach.

Schreiben Sie zuerst die Stealer-Klasse. Die Stealer-Klasse verfügt über eine readResolve-Methode und ein vom Stealer ausgeblendetes Singleton-Feld. Ersetzen Sie ein nicht transientes Singleton-Feld im Serialisierungsdatenstrom durch eine Instanz von Stealer. Zu diesem Zeitpunkt enthält die Singleton-Instanz einen Stealer mit einer Singleton-Referenz.

Singleton enthält Stealer. Wenn Singleton deserialisiert wird, wird zuerst die readResolve-Methode des Stealers abgespielt. Wenn die readResolve-Methode des Stealers fließt, verweist das Instanzfeld daher auf einen teilweise deserialisierten Singleton.

Die readResolve-Methode des Stealers kopiert die Referenz aus dem Instanzfeld in das statische Feld, und auf die Referenz kann auch nach dem Flow der readResolve-Methode zugegriffen werden. Die readResolve-Methode gibt dann den richtigen Typ des versteckten Singleton-Felds zurück. Andernfalls löst die VM eine CastException aus, wenn das Serialisierungssystem die Stealer-Referenz in einem Singleton-Feld speichert.

Ein konkretes Beispiel wird gezeigt. Betrachten Sie die folgende kaputte Singleton-Klasse.

package tryAny.effectiveJava;

import java.io.Serializable;
import java.util.Arrays;

//Unvollständige Singleton-Implementierung mit nicht vorübergehenden Eigenschaften
public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    //Nicht vorübergehende Eigenschaften
    private String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" };

    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

Die Stealer-Klasse ist wie folgt.

package tryAny.effectiveJava;

import java.io.Serializable;

//Klasse greift Elvis-Klasse an
public class ElvisStealer implements Serializable {
    //Statisches Feld für Nicht-Singleton-Instanzen
    static Elvis impersonator;

    //Eine weitere Elvis-Instanz
    private Elvis payload;

    private Object readResolve() {
        //Halten Sie die Elvis-Instanz in der Nutzlast im Imitator
        impersonator = payload;

        //Gibt ein String-Array zurück, da diese Eigenschaft als String-Array getarnt wird
        return new String[] { "A Fool Such as I" };
    }

    private static final long serialVersionUID = 0;
}

Unten finden Sie eine Klasse, die einen hausgemachten Stream deserialisiert, der zwei Instanzen eines fehlerhaften Singletons erstellt. (Funktioniert es nicht mit Java 10?)

package tryAny.effectiveJava;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ElvisImpersonator {
    //Geänderter Elvis-Stream
    private static final byte[] serializedForm = new byte[] { (byte) 0xac, (byte) 0xed, 0x00, 0x05, 0x73, 0x72, 0x00,
            0x05, 0x45, 0x6c, 0x76, 0x69, 0x73, (byte) 0x84, (byte) 0xe6, (byte) 0x93, 0x33, (byte) 0xc3, (byte) 0xf4,
            (byte) 0x8b, 0x32, 0x02, 0x00, 0x01, 0x4c, 0x00, 0x0d, 0x66, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x53,
            0x6f, 0x6e, 0x67, 0x73, 0x74, 0x00, 0x12, 0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e, 0x67, 0x2f,
            0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x3b, 0x78, 0x70, 0x73, 0x72, 0x00, 0x0c, 0x45, 0x6c, 0x76, 0x69, 0x73,
            0x53, 0x74, 0x65, 0x61, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
            0x4c, 0x00, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x74, 0x00, 0x07, 0x4c, 0x45, 0x6c, 0x76, 0x69,
            0x73, 0x3b, 0x78, 0x70, 0x71, 0x00, 0x7e, 0x00, 0x02 };

    public static void main(String[] args) {
        Elvis elvis = (Elvis) deserialize(serializedForm);
        Elvis impersonator = ElvisStealer.impersonator;
        elvis.printFavorites();
        impersonator.printFavorites();
    }

    static Object deserialize(byte[] sf) {
        try {
            return new ObjectInputStream(new ByteArrayInputStream(sf)).readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }

    }
}

Wenn Sie dieses Programm ausführen

[Hound Dog,Heartbreak Hotel]
[A Fool Such as I]

Es scheint, dass es als angezeigt wird. (Als ich es auf Java 10 ausgeführt habe, hat es nicht funktioniert. Es sieht wie folgt aus)

Exception in thread "main" java.lang.IllegalArgumentException: java.lang.ClassNotFoundException: Elvis
	at tryAny.effectiveJava.ElvisImpersonator.deserialize(ElvisImpersonator.java:29)
	at tryAny.effectiveJava.ElvisImpersonator.main(ElvisImpersonator.java:19)
Caused by: java.lang.ClassNotFoundException: Elvis
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:190)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:374)
	at java.base/java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:685)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1877)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1763)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2051)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1585)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at tryAny.effectiveJava.ElvisImpersonator.deserialize(ElvisImpersonator.java:27)
	... 1 more

Wenn das Ergebnis wie erwartet ist, ist es ein Problem, da es zwei verschiedene Instanzen gibt, obwohl es sich um eine einzelne Tonne handelt.

Dieses Problem kann gelöst werden, indem das Feld "ikingSongs "vorübergehend gemacht wird.

single-element enum Es ist jedoch sinnvoll, die Elvis-Klasse als Aufzählung mit einem Element (Item3) zu erstellen.

Wenn Sie eine Klasse schreiben, die serialisierbar ist und eine Instanzsteuerung in enum erfordert, garantiert Java, dass es keine andere Instanz als die deklarierte geben kann. Im Beispiel von Elvis ist es wie folgt.

public enum Elvis {
    INSTANCE;
    private String[] favoriteSongs =
            {"Hound Dog", "Heartbreak Hotel"};

    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Enum macht das readResolve-Instanzsteuerelement nicht überflüssig. Enum kann keine Klassen verarbeiten, die serialisierbar sind und eine Instanzsteuerung erfordern, die zur Kompilierungszeit nicht festgelegt sind.

readResolve-Barrierefreiheit

Wenn Sie readResolve in die letzte Klasse einfügen, sollte es privat sein.

Wenn Sie es in eine Klasse einordnen, die nicht endgültig ist, müssen Sie es berücksichtigen. Wenn es privat ist, kann nicht über Unterklassen darauf zugegriffen werden. Wenn paketprivat, kann nur auf Unterklassen desselben Pakets zugegriffen werden. Wenn geschützt oder öffentlich, kann es in allen Unterklassen verwendet werden.

Im Fall von protected oder public gibt die Unterklasse beim Deserialisieren der Unterklasse eine Instanz der Oberklasse zurück, wenn readResolve nicht überschrieben wird, was wahrscheinlich eine ClassCastException verursacht.

Recommended Posts

Punkt 89: Für die Steuerung bevorzugen Sie Aufzählungstypen gegenüber readResolve
Punkt 58: Bevorzugen Sie für jede Schleife herkömmliche Schleifen
Punkt 61: Bevorzugen Sie primitive Typen gegenüber primitiven Boxen
Punkt 28: Listen Arrays vorziehen
Punkt 65: Schnittstellen der Reflexion vorziehen
Punkt 43: Bevorzugen Sie Methodenverweise auf Lambdas
Punkt 42: Bevorzugen Sie Lambdas gegenüber anonymen Klassen
Punkt 39: Bevorzugen Sie Anmerkungen gegenüber Namensmustern
Punkt 85: Bevorzugen Sie Alternativen zur Java-Serialisierung
Punkt 41: Verwenden Sie Markierungsschnittstellen, um Typen zu definieren
Punkt 23: Bevorzugen Sie Klassenhierarchien gegenüber markierten Klassen
Punkt 81: Bevorzugen Sie Parallelitätsdienstprogramme, um zu warten und zu benachrichtigen
Punkt 80: Ziehen Sie Executoren, Aufgaben und Streams Threads vor