Primeira parte, teórica |
Parte Dois, Prática
Baseado em um tweet de Evgeny Mandrikov aka
godin :
Nele, ele se pergunta que número máximo de valores pode ser especificado em uma
enum
em Java. Após uma série de experimentos e o uso da magia negra ConstantDynamic (
JEP 309 ), o autor da pergunta chega ao número 8191.
Em uma série de dois artigos, procuramos os limites teóricos do número de elementos em uma enumeração, tentamos nos aproximar deles na prática e descobrimos ao longo do caminho como o JEP 309 pode ajudar.
Reconhecimento
Um capítulo de revisão no qual vemos pela primeira vez a enumeração desmontada.Primeiro, vamos ver o que a seguinte enumeração se traduz:
public enum FizzBuzz { Fizz, Buzz, FizzBuzz; }
Depois de compilar e desmontar:
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
Na listagem somos atendidos
- Um campo
public static final
para cada valor definido na enumeração - Campo sintético privado
$VALUES
, detalhes de implementação do método values()
- Implementação dos métodos
values()
e valueOf()
- Construtor privado
- O bloco de inicialização estática, onde realmente acontece a coisa mais interessante. Vamos considerar com mais detalhes.
Na forma de código java, o último se parece com isso:
static { Fizz = new FizzBuzz("Fizz", 0); Buzz = new FizzBuzz("Buzz", 1); FizzBuzz = new FizzBuzz("FizzBuzz", 2); $VALUES = new FizzBuzz[] { Fizz, Buzz, FizzBuzz }; }
Primeiro, são criadas instâncias de elementos de enumeração. Instâncias criadas são gravadas imediatamente nos campos
public static final
correspondentes.
Em seguida, uma matriz é criada e preenchida com links para instâncias de todos os elementos de enumeração. Os links são obtidos dos campos da classe que inicializamos no parágrafo acima. A matriz preenchida é armazenada no campo
private static final
$VALUES
.
Depois disso, a listagem está pronta para ser usada.
Gargalo
Um capítulo chato no qual procuramos restrições no número de elementos de enumeração.Você pode iniciar sua pesquisa no capítulo
8.9.3 da
JLS, “Enum Members”:
Membros do Enum do JLS 8.9.3Os membros de uma enumeração tipo E são todos os seguintes:
...
* Para cada constante enum c declarada no corpo da declaração de E, E tem
um campo final estático público declarado implicitamente do tipo E que possui o mesmo
nome como c. O campo possui um inicializador variável que instancia E e passa qualquer
argumentos de c para o construtor escolhido para E. O campo tem as mesmas anotações
como c (se houver).
Esses campos são declarados implicitamente na mesma ordem que o correspondente
constantes enum, antes de qualquer campo estático declarado explicitamente no corpo do
declaração de E.
...
* Os seguintes métodos declarados implicitamente:
public static E[] values(); public static E valueOf(String name);
Portanto, cada classe de enumeração possui um método
values()
que retorna uma matriz com todos os elementos declarados nessa enumeração. Daqui resulta que uma enumeração esférica no vácuo não pode conter mais do que elementos
Integer.MAX_VALUE + 1
.
Seguindo em frente. As enumerações em Java são representadas como descendentes da classe
java.lang.Enum
e, portanto, estão sujeitas a todas as restrições inerentes às classes na JVM.
Vejamos a descrição de alto nível da estrutura do arquivo de classe fornecida na
JVMS §4.1 “A Estrutura do 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]; }
Como já sabemos no JLS §8.9.3, um campo com o mesmo nome é criado para cada elemento de enumeração na classe resultante. O número de campos na classe define um
fields_count
não assinado de 16 bits, o que nos limita a 65_535 campos em um arquivo de classe ou 65_534 elementos de enumeração. Um campo é reservado para a matriz
$VALUES
, cujo clone retorna o método
values()
. Isso não é explicitamente declarado na especificação, mas é improvável que surja uma solução mais elegante.
Os nomes dos campos, métodos, classes, valores constantes e muito mais são armazenados no pool constante.
Se você não sabe nada sobre a estrutura interna do pool constante, recomendo a leitura do artigo antigo da lany . Apesar do fato de que, desde a sua escrita no conjunto de constantes, muitas coisas novas e interessantes apareceram, os princípios básicos permanecem inalterados.
O tamanho do pool de constantes de classe também é limitado pelo número de 65_535 elementos. O conjunto de constantes de uma classe formada corretamente nunca está vazio. No mínimo, haverá um nome para esta classe.
Por exemplo, o conjunto de constantes da classe de enumeração vazia compilada pelo javac do OpenJDK 14-ea + 29 sem informações de depuração contém 29 ocorrências.
Daqui resulta que o número de 65_534 elementos em uma enumeração também é inatingível. Na melhor das hipóteses, podemos contar com 65_505 ou um número próximo a isso.
O último acorde nesta introdução prolongada:
O valor pode ser
<clinit>
no campo
static final
apenas no bloco de inicialização estático, representado no nível do arquivo de classe por um método chamado
<clinit>
. O bytecode de qualquer método não pode ocupar mais de 65_535 bytes. Um número familiar, não é?
Uma instrução de gravação estática
putstatic
leva 3 bytes, o que nos dá uma estimativa aproximada de
65_535 / 3 = 21_845
. De fato, essa estimativa é exagerada. A instrução assume o valor para gravar no campo a partir do topo da pilha, que uma das instruções anteriores colocou lá. E esta instrução também ocupa bytes preciosos. Mas mesmo se você não levar isso em consideração, o número resultante ainda será significativamente menor que 65_505.
Em resumo:
- O formato do arquivo de classe limita o número máximo de elementos de enumeração para aproximadamente 65_505
- O mecanismo estático de inicialização do campo final nos limita ainda mais. Teoricamente - até 21_845 elementos no máximo, na prática esse número é ainda menos
No artigo final da série, focaremos na otimização não saudável e na geração de arquivos de classe.