Каковы различия между перечислением Java и классом с помощью частного конструктора?

Я пытался понять, как Java enum действительно работает, и я пришел к выводу, что он очень похож на обычный Java-класс, у которого его конструктор объявлен частным.

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

Итак, ниже приведена реализация простого перечисления Java и эквивалентного класса Java.

public enum Direction {
    ENUM_UP(0, -1),
    ENUM_DOWN(0, 1),
    ENUM_RIGHT(1, 0),
    ENUM_LEFT(-1, 0);


    private int x;
    private int y;

    private Direction(int x, int y){
        this.x = x;
        this.y = y;
    }
    public int getEnumX(){
        return x;
    }
    public int getEnumY(){
        return y;
    }
}

В чем разница между значением кода выше и ниже?

public class Direction{
    public static final Direction UP = new Direction(0, -1) ;
    public static final Direction DOWN = new Direction(0, 1) ;
    public static final Direction LEFT = new Direction(-1, 0) ;
    public static final Direction RIGHT = new Direction(1, 0) ;


    private int x ;
    private int y ;

    private Direction(int x, int y){
        this.x = x ;
        this.y = y ;
    }
    public int getX(){
        return x;
    }
    public int getY(){
        return y;
    }
}

Ответ 1

Отличия:

  • Enums расширяют java.lang.Enum и получают все приятные функции:
    • Автоматическое поведение однопользовательского режима с помощью правильной сериализации
    • Автоматический читаемый человеком метод .toString для значений перечисления без необходимости дублирования имен перечислений
    • .name и .ordinal специальные методы
    • Используется в высокопроизводительных классах EnumSet и EnumMap на основе биттетов
  • Перечисления рассматриваются языком специально:
    • В Enums используется специальный синтаксис, который упрощает создание экземпляра без написания десятков полей public static final
    • Перечисления могут использоваться в операторах switch
    • Перечисления не могут быть созданы вне списка перечислений, кроме как с помощью отражения
    • Перечисления не могут быть расширены за пределами списка перечислений
  • Java автоматически компилирует лишние вещи в перечисления:
    • public static (Enum)[] values();
    • public static (Enum) valueOf(java.lang.String);
    • private static final (Enum)[] $VALUES; (values() возвращает клон этого)

Большинство из них можно эмулировать с соответствующим классом, но Enum просто упрощает создание класса с этим набором особенно желательных свойств.

Ответ 2

Взгляните на эту страницу в блоге, он описывает, как Java enum скомпилированы в байт-код. Вы увидите, что есть небольшое дополнение по сравнению с вашим вторым образцом кода, который представляет собой массив объектов Direction, называемых VALUES. Этот массив содержит все возможные значения для вашего перечисления, поэтому вы не сможете сделать

new Direction(2, 2)

(например, используя отражение), а затем использовать это как допустимое значение Direction.

Плюс, как правильно объясняет @Eng.Fouad, у вас нет values(), valueOf() и ordinal().

Ответ 3

Чтобы ответить на вопрос: по существу, нет разницы между этими двумя подходами. Тем не менее, конструкция enum предоставляет вам дополнительные вспомогательные методы, такие как values(), valueOf() и т.д., Которые вам придется писать самостоятельно с помощью подхода class-with-private-constructor.

Но мне нравится, как перечисления Java в основном похожи на любые другие классы в Java, у них могут быть поля, поведение и т.д. Но для меня то, что разделяет перечисления из простых классов, - это идея, что перечисления представляют собой классы/типы, экземпляры/члены заданы. В отличие от обычных классов, в которых вы можете создавать любое количество экземпляров, перечисления ограничивают создание только известных экземпляров. Да, как вы показали, вы также можете сделать это с помощью классов с частными конструкторами, но перечисления просто делают это более интуитивно понятным.

Ответ 4

Как указали люди, вы теряете values(), valueOf() и ordinal(). Вы можете довольно легко воспроизвести это поведение, используя комбинацию Map и List.

public class Direction {

    public static final Direction UP = build("UP", 0, -1);
    public static final Direction DOWN = build("DOWN", 0, 1);
    public static final Direction LEFT = build("LEFT", -1, 0);
    public static final Direction RIGHT = build("RIGHT", 1, 0);
    private static final Map<String, Direction> VALUES_MAP = new LinkedHashMap<>();
    private static final List<Direction> VALUES_LIST = new ArrayList<>();
    private final int x;
    private final int y;
    private final String name;

    public Direction(int x, int y, String name) {
        this.x = x;
        this.y = y;
        this.name = name;
    }

    private static Direction build(final String name, final int x, final int y) {
        final Direction direction = new Direction(x, y, name);
        VALUES_MAP.put(name, direction);
        VALUES_LIST.add(direction);
        return direction;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public static Direction[] values() {
        return VALUES_LIST.toArray(new Direction[VALUES_LIST.size()]);
    }

    public static Direction valueOf(final String direction) {
        if (direction == null) {
            throw new NullPointerException();
        }
        final Direction dir = VALUES_MAP.get(direction);
        if (dir == null) {
            throw new IllegalArgumentException();
        }
        return dir;
    }

    public int ordinal() {
        return VALUES_LIST.indexOf(this);
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 29 * hash + name.hashCode();
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Direction other = (Direction) obj;
        return name.equals(other.name);
    }

    @Override
    public String toString() {
        return name;
    }
}

Как вы можете видеть; код становится очень неуклюжим очень быстро.

Я не уверен, есть ли способ для репликации инструкции switch с этим классом; поэтому вы потеряете это.

Ответ 5

Основное отличие состоит в том, что каждый класс enum неявно расширяет класс Enum<E extends Enum<E>>. Это приводит к тому, что:

  • enum объекты имеют такие методы, как name() и ordinal()
  • enum объекты имеют специальные реализации toString(), hashCode(), equals() и compareTo()
  • enum объекты подходят для оператора switch.

Все перечисленные выше не применимы для вашей версии класса Direction. Это различие "смысл".