Wie wird values ​​() für Java 6-Enums implementiert?

72

In Java können Sie eine Aufzählung wie folgt erstellen:

public enum Letter {
    A, B, C, D, E, F, G;

    static {
       for(Letter letter : values()) {
          // do something with letter
       }
    }
}

Diese Frage betrifft die Methode "values ​​()". Wie wird es konkret umgesetzt? Normalerweise könnte ich mit F3 oder STRG + Klicken in Eclipse zur Quelle für Java-Klassen springen (auch für Klassen wie String, Character, Integer und sogar Enum). Es ist möglich, die Quelle der anderen Aufzählungsmethoden anzuzeigen (z. B. valueOf (String)).

Erstellt "values ​​()" bei jedem Aufruf ein neues Array? Wenn ich es einer lokalen Variablen zuweise und dann eines der Elemente ändere, was passiert (dies hat natürlich keinen Einfluss auf den von values ​​() zurückgegebenen Wert, was bedeutet, dass jedes Mal ein neues Array zugewiesen wird).

Ist der Code dafür nativ? Oder behandelt die JVM / der Compiler sie speziell und gibt nur dann eine neue Instanz von values ​​() zurück, wenn sie nicht nachweisen kann, dass sie nicht geändert wird.

les2
quelle

Antworten:

112

Grundsätzlich übersetzt der Compiler (javac) Ihre Aufzählung in ein statisches Array, das alle Ihre Werte zur Kompilierungszeit enthält. Wenn Sie values ​​() aufrufen, erhalten Sie eine .clone'd () - Kopie dieses Arrays.

Angesichts dieser einfachen Aufzählung:

public enum Stuff {
   COW, POTATO, MOUSE;
}

Sie können sich den Code ansehen, den Java generiert:

public enum Stuff extends Enum<Stuff> {
    /*public static final*/ COW /* = new Stuff("COW", 0) */,
    /*public static final*/ POTATO /* = new Stuff("POTATO", 1) */,
    /*public static final*/ MOUSE /* = new Stuff("MOUSE", 2) */;
    /*synthetic*/ private static final Stuff[] $VALUES = new Stuff[]{Stuff.COW, Stuff.POTATO, Stuff.MOUSE};

    public static Stuff[] values() {
        return (Stuff[])$VALUES.clone();
    }

    public static Stuff valueOf(String name) {
        return (Stuff)Enum.valueOf(Stuff.class, name);
    }

    private Stuff(/*synthetic*/ String $enum$name, /*synthetic*/ int $enum$ordinal) {
        super($enum$name, $enum$ordinal);
    }
}

Sie können sehen, wie javac Ihre Klassen "übersetzt", indem Sie ein temporäres Verzeichnis erstellen und Folgendes ausführen:

javac -d <output directory> -XD-printflat filename.java
lucasmo
quelle
1
Wäre eine System.arraycopy nicht schneller?
Gandalf
+1; zusätzliches Lob, wenn Sie die Bedeutung von erklären synthetic.
wchargin
3
Warum values()methodiert clone()das Array $VALUES? Warum nicht einfach direkt zurücksenden?
RestInPeace
4
@RestInPeace, da das Array veränderbar ist. Siehe stackoverflow.com/a/16125639/488861 für ein Beispiel
Rytek
2
Ich hatte nichts davon gewusst -XD-printflat. Sehr nützlich, um die internen Abläufe solcher Dinge zu verstehen. Schade, dass dieses Ding nicht besser dokumentiert ist. Danke, dass du mich darauf aufmerksam gemacht hast!
MvG
2

Wenn Sie es einer lokalen Variablen zuweisen, können Sie dieser Variablen nur eine andere Aufzählung zuweisen. Dadurch wird die Aufzählung selbst nicht geändert, da Sie nur das Objekt ändern, auf das Ihre Variablen verweisen.

Es scheint, dass die Aufzählungen tatsächlich Singletons sind, so dass nur ein Element aus jeder Aufzählung in Ihrem gesamten Programm vorhanden sein kann. Dies macht den Operator == für Aufzählungen legal.

Es gibt also kein Leistungsproblem und Sie können nicht versehentlich etwas in Ihrer Aufzählungsdefinition ändern.

Janusz
quelle
2
Ich denke, das OP bedeutet, das von values ​​() zurückgegebene Array zu ändern. Wenn dies dasselbe Array-Objekt ist, das intern (anstelle einer Kopie) aufbewahrt wird, würde eine Änderung (z. B. Zuweisen eines Elements zu einem anderen, Zuweisen von Null zu einem Element usw.) es nicht nur für die Enum-Klasse durcheinander bringen. aber für zukünftige Aufrufe von values ​​()
newacct
1
Ja, du hast recht. Wenn es keinen Klon gäbe, könnten Sie eine Aufzählung aus dem Array entfernen, und spätere Aufrufe von Werten würden diesen Wert verfehlen.
Janusz
Und doch ist es nur eine flache Kopie, da die Enum-Instanzen eindeutig sind.
Jose Tepedino
1

Ist der Code dafür nativ? Oder behandelt die JVM / der Compiler sie speziell und gibt nur dann eine neue Instanz von values ​​() zurück, wenn sie nicht nachweisen kann, dass sie nicht geändert wird.

1) Nein. Oder zumindest nicht in aktuellen Implementierungen. Siehe @ lucasmos Antwort für die Beweise.

2) AFAIK, Nr.

Hypothetisch könnte es dies tun. Der Nachweis, dass ein Array niemals lokal modifiziert wird, wäre jedoch kompliziert und für die JIT relativ teuer. Wenn das Array der aufgerufenen Methode "entgeht" values(), wird es komplexer und teurer.

Die Chancen stehen gut, dass sich diese (hypothetische) Optimierung nicht auszahlt ... wenn sie über den gesamten Java-Code gemittelt wird.

Das andere Problem ist, dass diese (hypothetische) Optimierung Sicherheitslücken öffnen kann.


Das Interessante ist jedoch, dass das JLS nicht anzugeben scheint, dass das values()Mitglied eine Array-Kopie zurückgibt. Der gesunde Menschenverstand 1 sagt, dass es tun muss ... aber es ist nicht wirklich spezifiziert.

1 - Es wäre eine klaffende Sicherheitslücke, wenn values()ein gemeinsames (veränderliches) Array von enumWerten zurückgegeben würde.

Stephen C.
quelle