Première partie, théorique |
Deuxième partie, pratique
Basé sur un tweet d'Evgeny Mandrikov alias
godin :
Dans ce document, il se demande quel nombre maximal de valeurs peut être spécifié dans une
enum
en Java. Après une série d'expériences et l'utilisation de la magie noire ConstantDynamic (
JEP 309 ), l'auteur de la question arrive au nombre 8191.
Dans une série de deux articles, nous recherchons les limites théoriques du nombre d'éléments dans une énumération, essayons de nous en rapprocher dans la pratique et découvrons en cours de route comment le JEP 309 peut aider.
Reconnaissance
Un chapitre de revue dans lequel nous voyons d'abord l'énumération démontée.Voyons d'abord en quoi l'énumération suivante se traduit:
public enum FizzBuzz { Fizz, Buzz, FizzBuzz; }
Après avoir compilé et démonté:
javap -c -s -p -v FizzBuzz.class Classfile /dev/null/FizzBuzz.class Last modified 32 . 2019 .; size 903 bytes MD5 checksum add0af79de3e9a70a7bbf7d57dd0cfe7 Compiled from "FizzBuzz.java" public final class FizzBuzz extends java.lang.Enum<FizzBuzz> minor version: 0 major version: 58 flags: (0x4031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM this_class: #2 // FizzBuzz super_class: #13 // java/lang/Enum interfaces: 0, fields: 4, methods: 4, attributes: 2 Constant pool: #1 = Fieldref #2.#3
En listant nous sommes rencontrés
- Un
public static final
pour chaque valeur définie dans l'énumération - Champ synthétique privé
$VALUES
, détail d'implémentation de la méthode values()
- Implémentation des méthodes
values()
et valueOf()
- Constructeur privé
- Le bloc d'initialisation statique, où se produit en fait la chose la plus intéressante. Examinons-le plus en détail.
Sous forme de code java, ce dernier ressemble à ceci:
static { Fizz = new FizzBuzz("Fizz", 0); Buzz = new FizzBuzz("Buzz", 1); FizzBuzz = new FizzBuzz("FizzBuzz", 2); $VALUES = new FizzBuzz[] { Fizz, Buzz, FizzBuzz }; }
Tout d'abord, des instances d'éléments d'énumération sont créées. Les instances créées sont immédiatement écrites dans les champs
public static final
correspondants.
Un tableau est ensuite créé et rempli de liens vers des instances de tous les éléments d'énumération. Les liens proviennent des champs de la classe que nous avons initialisés dans le paragraphe ci-dessus. Le tableau rempli est stocké dans le
private static final
$VALUES
.
Après cela, la liste est prête à être lancée.
Goulot d'étranglement
Un chapitre ennuyeux dans lequel nous recherchons des restrictions sur le nombre d'éléments d'énumération.Vous pouvez commencer votre recherche avec le chapitre
JLS §8.9.3 «Enum Members»:
JLS 8.9.3 Enum MembersLes membres d'une énumération de type E sont tous les suivants:
...
* Pour chaque constante enum c déclarée dans le corps de la déclaration de E, E a
un champ final public statique implicitement déclaré de type E qui a le même
nom comme c. Le champ a un initialiseur variable qui instancie E et passe tout
arguments de c au constructeur choisi pour E. Le champ a les mêmes annotations
comme c (le cas échéant).
Ces champs sont implicitement déclarés dans le même ordre que les champs correspondants
énumérations constantes, avant tout champ statique explicitement déclaré dans le corps du
déclaration de E.
...
* Les méthodes déclarées implicitement suivantes:
public static E[] values(); public static E valueOf(String name);
Ainsi, chaque classe d'énumération a une méthode
values()
qui retourne un tableau avec tous les éléments déclarés dans cette énumération. Il s'ensuit qu'une énumération sphérique dans le vide ne peut pas contenir plus de
Integer.MAX_VALUE + 1
éléments.
Continuons. Les énumérations en Java sont représentées comme des descendants de la classe
java.lang.Enum
et sont donc soumises à toutes les restrictions inhérentes aux classes de la JVM.
Regardons la description de haut niveau de la structure du fichier de classe donnée dans
JVMS §4.1 «La structure ClassFile»:
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
Comme nous le savons déjà par JLS §8.9.3, un champ du même nom est créé pour chaque élément d'énumération dans la classe résultante. Le nombre de champs dans la classe définit un nombre de champs non signé de 16 bits, ce qui nous limite à 65_535 champs dans un fichier de classe ou à des éléments d'énumération 65_534. Un champ est réservé au tableau
$VALUES
, dont un clone renvoie la méthode
values()
. Ce n'est pas explicitement indiqué dans la spécification, mais il est peu probable de trouver une solution plus élégante.
Les noms des champs, méthodes, classes, valeurs constantes et bien plus sont stockés dans le pool constant.
Si vous ne savez rien de la structure interne de la piscine constante, je vous recommande de lire l' ancien article de lany . Malgré le fait que depuis son écriture dans le pool de constantes beaucoup de choses nouvelles et intéressantes sont apparues, les principes de base restent inchangés.
La taille du pool de constantes de classe est également limitée par le nombre d'éléments 65_535. Le pool de constantes d'une classe correctement formée n'est jamais vide. Au minimum, il y aura un nom pour cette classe.
Par exemple, le pool de constantes de la classe d'une énumération vide compilé par javac à partir d'OpenJDK 14-ea + 29 sans informations de débogage contient 29 entrées.
Il s'ensuit que le nombre de 65_534 éléments dans une énumération est également inaccessible. Dans le meilleur des cas, nous pouvons compter sur 65_505 ou un nombre proche de celui-ci.
Le dernier accord de cette introduction prolongée:
La valeur peut être
<clinit>
dans le
static final
uniquement dans le bloc d'initialisation statique, qui est représenté au niveau du fichier de classe par une méthode appelée
<clinit>
. Le bytecode d'une méthode ne peut pas occuper plus de 65_535 octets. Un chiffre familier, n'est-ce pas?
Une
putstatic
d'écriture statique
putstatic
prend 3 octets, ce qui nous donne une estimation approximative de
65_535 / 3 = 21_845
. En fait, cette estimation est surestimée. L'instruction prend la valeur à écrire dans le champ à partir du haut de la pile, que l'une des instructions précédentes y a placée. Et cette instruction prend également de précieux octets. Mais même si vous n'en tenez pas compte, le nombre résultant est toujours nettement inférieur à 65_505.
En résumé:
- Le format de fichier de classe limite le nombre maximal d'éléments d'énumération à environ 65_505
- Le mécanisme d'initialisation du champ final statique nous limite encore plus. Théoriquement - jusqu'à 21_845 éléments maximum, en pratique ce nombre est encore moins
Dans le dernier article de la série, nous nous concentrerons sur l'optimisation malsaine et la génération de fichiers de classe.