En programmation, il est courant d’avoir un type qui ne peut avoir qu’un ensemble fini de valeurs telles que les jours de la semaine, les saisons de l’année, les couleurs primaires, etc. Une énumération, ou enum en abrégé, est comme un ensemble fixe de constantes.
Utiliser une enum est bien meilleur que d’utiliser un tas de constantes car elle fournit une vérification de type sécurisée. Avec des constantes numériques ou String, vous pouvez passer une valeur invalide et ne le découvrir qu’à l’exécution. Avec les enums, il est impossible de créer une valeur enum invalide sans provoquer une erreur de compilation.
Les énumérations apparaissent chaque fois que vous avez un ensemble d’éléments dont les types sont connus au moment de la compilation. Des exemples courants incluent les directions de la boussole, les mois de l’année, les planètes du système solaire et les cartes d’un jeu (bon, peut-être pas les planètes d’un système solaire, étant donné que Pluton a vu son statut de planète révoqué).
Création d’Enums Simples
Pour créer une enum, déclarez un type avec le mot-clé enum, un nom et une liste de valeurs comme illustré sur la Figure 7.4.
public enum Saison {
HIVER, PRINTEMPS, ETE, AUTOMNE;
}
Nous faisons référence à une enum qui ne contient qu’une liste de valeurs comme une enum simple. Lorsque vous travaillez avec des enums simples, le point-virgule à la fin de la liste est facultatif. Gardez l’enum Saison à portée de main, car nous l’utiliserons tout au long de cette section.
Les valeurs enum sont considérées comme des constantes et sont généralement écrites en utilisant le style snake case. Par exemple, une enum déclarant une liste de saveurs de glace pourrait inclure des valeurs comme VANILLE, ROCKY_ROAD, MENTHE_CHOCOLAT, etc.
Utiliser une enum est super facile.
var s = Saison.ETE;
System.out.println(Saison.ETE); // ETE
System.out.println(s == Saison.ETE); // true
Comme vous pouvez le voir, les enums affichent le nom de l’enum lorsque toString() est appelé. Elles peuvent être comparées en utilisant == car elles sont comme des constantes static final. En d’autres termes, vous pouvez utiliser equals() ou == pour comparer des enums, puisque chaque valeur enum n’est initialisée qu’une seule fois dans la Machine Virtuelle Java (JVM).
Une chose que vous ne pouvez pas faire est d’étendre une enum.
public enum SaisonEtendue extends Saison {} // NE COMPILE PAS
Les valeurs dans une enum sont fixes. Vous ne pouvez pas en ajouter d’autres en étendant l’enum.
Appel des méthodes values(), name() et ordinal()
Une enum fournit une méthode values() pour obtenir un tableau de toutes les valeurs. Vous pouvez l’utiliser comme n’importe quel tableau normal, y compris dans une boucle foreach :
for(var saison: Saison.values()) {
System.out.println(saison.name() + " " + saison.ordinal());
}
La sortie montre que chaque valeur enum a une valeur int correspondante, et les valeurs sont listées dans l’ordre dans lequel elles sont déclarées :
HIVER 0
PRINTEMPS 1
ETE 2
AUTOMNE 3
La valeur int restera la même pendant votre programme, mais le programme est plus facile à lire si vous vous en tenez à la valeur enum lisible par l’homme.
Vous ne pouvez pas comparer directement un int et une valeur enum puisqu’une enum est un type, comme une classe Java, et non un int primitif.
if (Saison.ETE == 2) {} // NE COMPILE PAS
Appel de la méthode valueOf()
Une autre fonctionnalité utile est la récupération d’une valeur enum à partir d’une String en utilisant la méthode valueOf(). Cela est utile lorsque vous travaillez avec du code plus ancien ou que vous analysez les entrées utilisateur. La String passée doit correspondre exactement à la valeur enum.
Saison s = Saison.valueOf("ETE"); // ETE
Saison t = Saison.valueOf("ete"); // IllegalArgumentException
La première instruction fonctionne et attribue la valeur enum appropriée à s. Notez que cette ligne ne crée pas une valeur enum, du moins pas directement. Chaque valeur enum est créée une fois lorsque l’enum est chargée pour la première fois. Une fois que l’enum a été chargée, elle récupère la valeur enum unique avec le nom correspondant.
La deuxième instruction rencontre un problème. Il n’y a pas de valeur enum avec le nom en minuscules “ete”. Java lève les bras en signe de défaite et lance une IllegalArgumentException.
Exception in thread "main" java.lang.IllegalArgumentException:
No enum constant enums.Saison.ete
Utilisation des Enums dans les Instructions switch
Les enums peuvent être utilisées dans les instructions et expressions switch. Faites attention aux valeurs de case dans ce code :
Saison ete = Saison.ETE;
switch(ete) {
case HIVER:
System.out.print("Sortez la luge !");
break;
case ETE:
System.out.print("À la piscine !");
break;
default:
System.out.print("Est-ce que c'est l'été ?");
}
Le code affiche “À la piscine !” puisqu’il correspond à ETE. Dans chaque instruction case, nous avons simplement tapé la valeur de l’enum plutôt que d’écrire Saison.HIVER. Après tout, le compilateur sait déjà que les seules correspondances possibles peuvent être des valeurs enum. Java traite le type enum comme implicite. En fait, si vous tapiez case Saison.HIVER, cela ne compilerait pas. Vous ne nous croyez pas ? Jetez un coup d’œil à cet exemple équivalent utilisant une expression switch :
Saison ete = Saison.ETE;
var message = switch(ete) {
case Saison.HIVER -> "Sortez la luge !"; // NE COMPILE PAS
case 0 -> "À la piscine !"; // NE COMPILE PAS
default -> "Est-ce que c'est l'été ?";
};
System.out.print(message);
La première instruction case ne compile pas car Saison est utilisé dans la valeur case. Si nous changions Saison.AUTOMNE en simplement AUTOMNE, alors la ligne compilerait. Qu’en est-il de la deuxième instruction case ? Comme nous l’avons dit plus tôt, vous ne pouvez pas comparer les enums avec des valeurs int, vous ne pouvez pas les utiliser dans une instruction switch avec des valeurs int.
Ajout de Constructeurs, de Champs et de Méthodes
Alors qu’une enum simple est composée uniquement d’une liste de valeurs, nous pouvons définir une enum complexe avec des éléments supplémentaires. Imaginons que notre zoo veuille suivre les tendances de trafic pour déterminer quelles saisons attirent le plus de visiteurs.
public enum Saison {
HIVER("Faible"), PRINTEMPS("Moyen"), ETE("Élevé"), AUTOMNE("Moyen");
private final String visiteursAttendus;
private Saison(String visiteursAttendus) {
this.visiteursAttendus = visiteursAttendus;
}
public void afficherVisiteursAttendus() {
System.out.println(visiteursAttendus);
}
}
Il y a quelques points à noter ici. À la ligne 2, la liste des valeurs enum se termine par un point-virgule (;). Bien que cela soit facultatif lorsque notre enum est composée uniquement d’une liste de valeurs, c’est obligatoire s’il y a autre chose dans l’enum que les valeurs.
Les lignes 3 à 9 sont du code Java régulier. Nous avons une variable d’instance, un constructeur et une méthode. Nous marquons la variable d’instance comme private et final à la ligne 3 afin que nos propriétés enum ne puissent pas être modifiées.
Bien qu’il soit possible de créer une enum avec des variables d’instance qui peuvent être modifiées, c’est une très mauvaise pratique de le faire car elles sont partagées au sein de la JVM. Lors de la conception d’une enum, les valeurs devraient être immuables.
Tous les constructeurs enum sont implicitement private, le modificateur étant facultatif. C’est raisonnable puisque vous ne pouvez pas étendre une enum et les constructeurs ne peuvent être appelés qu’au sein de l’enum elle-même. En fait, un constructeur enum ne compilera pas s’il contient un modificateur public ou protected.
Et les parenthèses à la ligne 2 ? Ce sont des appels de constructeur, mais sans le mot-clé new normalement utilisé pour les objets. La première fois que nous demandons n’importe laquelle des valeurs enum, Java construit toutes les valeurs enum. Après cela, Java renvoie simplement les valeurs enum déjà construites. Compte tenu de cette explication, vous pouvez voir pourquoi cela n’appelle le constructeur qu’une seule fois :
public enum UneSeuleFois {
UNE_FOIS(true);
private UneSeuleFois(boolean b) {
System.out.print("construction,");
}
}
public class ImprimerUne {
public static void main(String[] args) {
System.out.print("début,");
UneSeuleFois premierAppel = UneSeuleFois.UNE_FOIS; // Affiche construction,
UneSeuleFois secondAppel = UneSeuleFois.UNE_FOIS; // N'affiche rien
System.out.print("fin");
}
}
Cette classe affiche ce qui suit :
début,construction,fin
Si l’enum UneSeuleFois avait été utilisée plus tôt dans le programme, et donc initialisée plus tôt, alors la ligne qui déclare la variable premierAppel n’imprimerait rien.
Comment appelons-nous une méthode enum ? C’est facile aussi : nous utilisons simplement la valeur enum suivie de l’appel de méthode.
Saison.ETE.afficherVisiteursAttendus();
Parfois, vous voulez définir différentes méthodes pour chaque enum. Par exemple, notre zoo a des horaires saisonniers différents. Il fait froid et la nuit tombe tôt en hiver. Nous pouvons garder une trace des heures grâce à des variables d’instance, ou nous pouvons laisser chaque valeur enum gérer elle-même ses heures.
public enum Saison {
HIVER {
public String getHeures() { return "10h-15h"; }
},
PRINTEMPS {
public String getHeures() { return "9h-17h"; }
},
ETE {
public String getHeures() { return "9h-19h"; }
},
AUTOMNE {
public String getHeures() { return "9h-17h"; }
};
public abstract String getHeures();
}
Que se passe-t-il ici ? On dirait que nous avons créé une classe abstraite et un tas de petites sous-classes. D’une certaine façon, c’est le cas. L’enum elle-même a une méthode abstraite. Cela signifie que chaque valeur enum est obligée d’implémenter cette méthode. Si nous oublions d’implémenter la méthode pour l’une des valeurs, nous obtenons une erreur de compilation :
La constante enum HIVER doit implémenter la méthode abstraite getHeures()
Mais que faire si nous ne voulons pas que chaque valeur enum ait une méthode ? Pas de problème. Nous pouvons créer une implémentation pour toutes les valeurs et la surcharger uniquement pour les cas spéciaux.
public enum Saison {
HIVER {
public String getHeures() { return "10h-15h"; }
},
ETE {
public String getHeures() { return "9h-19h"; }
},
PRINTEMPS, AUTOMNE;
public String getHeures() { return "9h-17h"; }
}
C’est mieux. Nous ne codons que les cas spéciaux et laissons les autres utiliser l’implémentation fournie par l’enum.
Une enum peut même implémenter une interface, car cela ne nécessite que de surcharger les méthodes abstraites :
public interface Meteo { int getTemperatureMoyenne(); }
public enum Saison implements Meteo {
HIVER, PRINTEMPS, ETE, AUTOMNE;
public int getTemperatureMoyenne() { return 30; }
}
Ce n’est pas parce qu’une enum peut avoir beaucoup de méthodes qu’elle le devrait. Essayez de garder vos enums simples. Si votre enum fait plus d’une page ou deux, elle est probablement trop longue. Lorsque les enums deviennent trop longues ou trop complexes, elles sont difficiles à lire.
Vous avez peut-être remarqué que dans chacun de ces exemples d’enum, la liste des valeurs est venue en premier. Ce n’était pas un accident. Que l’enum soit simple ou complexe, la liste des valeurs vient toujours en premier.
