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

Я пытался понять, как на самом деле работает перечисление Java, и пришел к выводу, что он очень похож на обычный класс 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;
    }
}

5 ответов

Решение

Отличия:

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

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

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

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

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

new Direction(2, 2)

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

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

Как люди уже говорили, вы проигрываете 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 утверждение с этим классом; так что вы потеряете это.

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

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

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

Другие вопросы по тегам