web-dev-qa-db-fra.com

Quelles sont les différences entre une énumération Java et une classe avec constructeur privé?

J'essayais de comprendre comment Java enum fonctionne vraiment et je suis arrivé à la conclusion qu'il est très similaire à une classe Java normale avec son constructeur déclaré) privé.

Je viens d'arriver à cette conclusion et elle n'est pas basée sur beaucoup de réflexion, mais j'aimerais savoir si je manque quelque chose.

Voici donc une implémentation d'une simple énumération Java et d'une classe équivalente 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;
    }
}

Quelle est la différence de sens entre le code ci-dessus et ci-dessous?

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;
    }
}
57
user9349193413

Différences:

  1. Les énumérations étendent Java.lang.Enum Et gagnent toutes ses fonctionnalités intéressantes :
    1. Comportement singleton automatique grâce à une sérialisation correcte
    2. Méthode automatique lisible par l'homme .toString Sur les valeurs d'énumération sans avoir à dupliquer vos noms d'énumération
    3. .name Et .ordinal Méthodes spéciales
    4. Utilisable dans les classes EnumSet et EnumMap basées sur un jeu de bits hautes performances
  2. Les énumérations sont traitées par la langue spécialement:
    1. Les énumérations utilisent une syntaxe spéciale qui simplifie la création d'instances sans écrire des dizaines de champs public static final
    2. Les énumérations peuvent être utilisées dans les instructions switch
    3. Les énumérations ne peuvent pas être instanciées en dehors de la liste d'énumération, sauf en utilisant la réflexion
    4. Les énumérations ne peuvent pas être étendues en dehors de la liste d'énumération
  3. Java compile automatiquement des éléments supplémentaires en énumérations:
    1. public static (Enum)[] values();
    2. public static (Enum) valueOf(Java.lang.String);
    3. private static final (Enum)[] $VALUES; (values() renvoie un clone de ceci)

La plupart d'entre elles peuvent être émulées avec une classe convenablement conçue, mais Enum facilite simplement la création d'une classe avec cet ensemble de propriétés particulièrement souhaitables.

58
nneonneo

Pour répondre à la question: essentiellement, il n'y a pas de différence entre les deux approches. Cependant, la construction enum vous fournit des méthodes de support supplémentaires comme values(), valueOf(), etc. que vous devriez écrire vous-même avec l'approche class-with-private-constructor .

Mais oui, j'aime comment Java sont pour la plupart comme toutes les autres classes de Java, elles peuvent avoir des champs, des comportements, etc. Mais pour moi, ce qui sépare les énumérations des classes ordinaires est l'idée que les énumérations sont des classes/types dont les instances/membres sont prédéterminés. Contrairement aux classes habituelles à partir desquelles vous pouvez créer un nombre illimité d'instances, les énumérations ne limitent la création qu'aux instances connues. Oui, comme vous l'avez illustré, vous pouvez également le faire avec des classes avec private constructeurs, mais les énumérations rendent cela plus intuitif.

10
Psycho Punch

Jetez un oeil à cette page de blog , il décrit comment Java enums sont compilés en bytecode. Vous verrez qu'il y a un petit ajout par rapport à votre deuxième exemple de code, qui est un tableau d'objets Direction appelé VALUES. Ce tableau contient toutes les valeurs possibles pour votre énumération, vous ne pourrez donc pas le faire

new Direction(2, 2)

(par exemple en utilisant la réflexion), puis utilisez-la comme une valeur Direction valide.

De plus, comme @ Eng.Fouad l'explique correctement, vous n'avez pas values(), valueOf() et ordinal().

6
mthmulders

Comme certains l'ont souligné, vous perdez values(), valueOf() et ordinal(). Vous pouvez reproduire ce comportement assez facilement en utilisant une combinaison de Map et 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;
    }
}

Comme vous pouvez le voir; le code devient très maladroit très rapidement.

Je ne sais pas s'il existe un moyen de répliquer une instruction switch avec cette classe; vous allez donc perdre cela.

5
Boris the Spider

La principale différence est que chaque classe enum étend implicitement la classe Enum<E extends Enum<E>>. Cela conduit à cela:

  1. enum les objets ont des méthodes telles que name() et ordinal()
  2. enum les objets ont des implémentations spéciales toString(), hashCode(), equals() et compareTo()
  3. Les objets enum conviennent à l'opérateur switch.

Tous les éléments mentionnés ci-dessus ne s'appliquent pas à votre version de la classe Direction. C'est la différence de "sens".

0
Andremoniy