août
2007
Java 5.0 a introduit un nouveau type enum dans le langage, afin de créer des énumérations simple « à la C« tout en bénéficiant des avantages de l’OO : les enums de Java sont de vrais classes et peuvent donc comporter des champs et des méthodes…
Bien sûr l’API de réflection a été mise à jour afin de gérer ce nouveau type d’objet, notamment avec les méthodes isEnum()
et getEnumConstants()
: la première permet de savoir si une classe est une enum, alors que la seconde permet d’obtenir toutes les valeurs de ladite enum (lorsque isEnum()
retourne true).
Il faut toutefois prendre gare à cette méthode isEnum()
dont le comportement peut être troublant au prime abord…
En effet, dans la documentation de cette dernière on peut lire la description suivante :
Returns true if and only if this class was declared as an enum in the source code.
On peut donc légitimement penser au prime abord que pour toute instance d’enum le code suivant nous retourne une valeur affirmative :
myEnum.getClass().isEnum();
Or ce n’est pas forcément le cas ! En fait cela dépend de la manière dont sont déclarées les diverses constantes de l’enum.
En effet, isEnum()
ne renverra une valeur positif que pour la classe correspondant à l’enum, et en aucun cas pour ses classes filles. Oui j’ai bien dit ses classes filles ! Car s’il est impossible d’hériter directement d’une enum, cela reste possible lorsqu’on utilise une classe anonyme lors de la déclaration d’une valeur de l’enum comme dans l’exemple ci-dessous :
enum MyEnum {
/** Premier élément de l'enum, défini de manière "standard". */
VALEUR_1,
/** Second élément de l'enum, qui utilise une classe
* anonyme afin de redéfinir la méthode toString()
*/
VALEUR_2 {
@Override
public String toString() {
return "valeur_2";
}
};
}
Et bien sûr le type de la classe anonyme est un type fils de MyEnum, dont le nom se voit suffixé par $1 (et ainsi de suite). Ainsi le code suivant :
for (MyEnum myEnum : MyEnum.values()) {
Class clazz = myEnum.getClass();
System.out.println(myEnum + " (" + clazz + ") : " + clazz.isEnum());
}
Nous indiquera que la seconde valeur de l’enum n’en est pas une :
VALEUR_1 (class MyEnum) : true
valeur_2 (class MyEnum$1) : false
Le problème vient bien de cette classe MyEnum$1 qui ne respectent pas les critères requis par isEnum()…
Pour pallier le problème, il suffit d’utiliser la méthode getDeclaringClass()
héritée de la classe Enum
et ainsi obtenir le résultat escompté (en fait cette méthode retourne la classe de déclaration de l’enum et non pas la classe réelle) :
for (MyEnum myEnum : MyEnum.values()) {
Class clazz = myEnum.getDeclaringClass();
System.out.println(myEnum + " (" + clazz + ") : " + clazz.isEnum());
}
Et on obtient bien le résultat escompté :
VALEUR_1 (class MyEnum) : true
valeur_2 (class MyEnum) : true
Mais le problème c’est que l’intérêt de isEnum()
et getEnumConstants()
est moindre dans ce cas là, puisqu’on utilise déjà des enums, alors qu’avec l’API de réflection on utilise principalement des Object
s et le problème se pose alors à nouveau puisque la méthode getDeclaringClass()
ne peut pas être utilisée ! isEnum()
ne permet donc pas à lui seul de déterminer si un objet est une enum.
Il y a plusieurs solutions à ce problème. Si on travaille avec des références on peut tout simplement utiliser instanceof :
Object object = ...;
if (object instanceof Enum) {
// ...
}
En effet la classe Enum
étant une classe particulière qui ne peut pas être instanciée ou héritée de manière standard, ses instances correspondent donc obligatoirement aux valeurs des enums.
Enfin, lorsqu’on travaille avec des objets de type Class
, il y a deux solutions possibles :
La première consiste à vérifier que le type est assignable avec le type Enum
en gérant le cas particulier de la classe Enum
elle même :
public static boolean isEnumType(Class<?> clazz) {
return ( Enum.class.isAssignableFrom(clazz) && Enum.class!=clazz );
}
La seconde solution consiste à vérifier que le type ou sa classe parente soit une enum :
public static boolean isEnumType(Class<?> clazz) {
return ( clazz.isEnum() || (clazz.getSuperclass()!=null && clazz.getSuperclass().isEnum()) );
}
D’ailleur cette seconde approche permet d’obtenir le type de déclaration de l’enum de la même manière que le fait la méthode getDeclaringClass
de la classe Enum
:
@SuppressWarnings("unchecked") // cast vers (Class<? extends Enum>)
public static Class<? extends Enum> getEnumDeclaringClass(Class<?> clazz) {
if (clazz.isEnum()) {
return (Class<? extends Enum>) clazz;
}
clazz = clazz.getSuperclass();
if (clazz!=null && clazz.isEnum()) {
return (Class<? extends Enum>) clazz;
}
return null; // ce n'est pas une enum
}
Tutoriels
Discussions
- Recuperation du nom des parametres
- Définition exacte de @Override
- Possibilité d'accéder au type générique en runtime
- [ fuite ] memoire
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- Difference de performances Unix/Windows d'un programme?
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Classes, méthodes private
- jre 1.5, tomcat 6.0 et multi processeurs