Как реализованы значения() для перечислений Java 6?

В Java вы можете создать перечисление следующим образом:

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

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

Этот вопрос касается метода "values ​​()". В частности, как это реализовано? Обычно я мог перейти к исходному для классов Java, используя F3 или CTRL + Click в Eclipse (даже для таких классов, как String, Character, Integer и даже Enum). Можно просмотреть источник других методов перечисления (например, valueOf (String)).

Создает ли "values ​​()" новый массив при каждом вызове? Если я назначу его локальной переменной и затем изменю один из элементов, что произойдет (очевидно, это не повлияет на значение, возвращаемое значениями(), что означает, что каждый раз выделяется новый массив).

Является ли код для него родным? Или JVM/компилятор рассматривает это специально, только возвращая новый экземпляр из значений(), когда он не может доказать, что он не будет изменен.

Ответ 1

В принципе, компилятор (javac) переводит ваш enum в статический массив, содержащий все ваши значения во время компиляции. Когда вы вызываете значения(), он дает вам копию .clone'd() этого массива.

Учитывая это простое перечисление:

public enum Stuff {
   COW, POTATO, MOUSE;
}

Вы действительно можете посмотреть на код, который генерирует Java:

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);
    }
}

Вы можете посмотреть, как javac "переводит" ваши классы, создавая временный каталог и запуская:

javac -d <output directory> -XD-printflat filename.java

Ответ 2

Если вы назначаете его локальной переменной, единственной вещью, которую вы можете изменить, является присвоение другой переменной этой переменной. Это не изменит само перечисление, потому что вы только меняете объект, на который ссылается ваша переменная.

Кажется, что перечисления на самом деле являются одноточечными, так что только один элемент из каждого перечисления может существовать в вашей целой программе, что делает оператор == законным для перечислений.

Таким образом, нет проблемы с производительностью, и вы не можете случайно изменить что-то в своем определении перечисления.

Ответ 3

Является ли код для него родным? Или JVM/компилятор рассматривает это специально, только возвращая новый экземпляр из значений(), когда он не может доказать, что он не будет изменен.

1) Нет. Или, по крайней мере, не в текущих реализациях. См. Ответ @lucasmo для доказательства.

2) AFAIK, no.

Гипотетически это могло бы сделать это. Однако доказательство того, что массив никогда не изменяется локально, будет сложным и относительно дорогостоящим для JIT. Если массив "ускользает" от метода, который называется values(), он становится более сложным и более дорогим.

Скорее всего, эта (гипотетическая) оптимизация не окупится... при усреднении по всему Java-коду.

Другая проблема заключается в том, что эта (гипотетическая) оптимизация может открыть дыры в безопасности.


Интересно, однако, что JLS, похоже, не указывает, что член values() возвращает копию массива. Здравый смысл 1 говорит, что он должен делать... но на самом деле он не указан.

1 - это было бы зияющее отверстие безопасности, если values() возвратил общий (изменяемый) массив значений enum.