Java 14: visualização de registros

Em breve, um novo recurso de sintaxe aparecerá nos próximos Java 14 - records. Depois de estudar a prévia , que descreve brevemente como são as gravações e com "o que eles comem", ousei adaptar o documento para Habr em russo. Quem se importa - bem-vindo ao gato.


Sumário


As entradas permitem expandir os recursos do Java. Eles fornecem sintaxe concisa para declarar classes que são portadoras simples de conjuntos de dados persistentes e imutáveis.

Razões e objetivos


As reclamações de que “o Java é muito detalhado” e que você precisa “cerimônia” com ele são bastante comuns. O motivo disso são as classes projetadas apenas para armazenar um determinado conjunto de dados. Para escrever corretamente essa classe, você precisa escrever muitos códigos formais, repetitivos e propensos a erros: construtores, getters e setters, equals (), hashCode (), toString () etc. Às vezes, os desenvolvedores trapaceiam e não substituem equals () e hashCode (), que por sua vez podem levar a comportamentos incomuns ou problemas com a depuração. Ou, quando os desenvolvedores não querem declarar mais uma classe, eles prescrevem uma alternativa, mas não é adequada, apenas porque ela tem a "forma correta".

Os ambientes de desenvolvimento ajudarão a registrar a maior parte do código da classe, mas não ajudarão o desenvolvedor a ler esse código a navegar rapidamente entre dezenas de linhas de código padrão e entender que essa classe é uma portadora de dados comum. Os conjuntos de dados padrão da modelagem de código Java devem ser simples de escrever, entender e validar.

À primeira vista, pode parecer que os registros visam reduzir o código do modelo. Colocamos o objetivo semântico neles: “modelar dados como dados” (modelar dados como dados). Se a semântica estiver correta, o código do modelo fará tudo sozinho sem a participação do desenvolvedor. Afinal, declarar conjuntos de dados persistentes deve ser fácil, claro e conciso.

Metas que não foram


Não estabelecemos o objetivo de "declarar guerra" no código padrão. Em particular, não pretendemos resolver o problema de classes mutáveis ​​usando a convenção de nomenclatura dos componentes JavaBean. Embora propriedades, metaprogramação e geração de código com base em anotações sejam frequentemente sugeridas como "soluções" para esse problema, adicionar esses recursos também não era nosso objetivo.

Descrição do produto


As entradas são um novo tipo de declaração de tipo em Java. Como enum, escrever é uma classe funcionalmente limitada. Ele anuncia sua visão e fornece uma API que se baseia nessa visão. As entradas não separam a API da apresentação e, por sua vez, são concisas.

A entrada contém um nome e uma descrição do status. A descrição do estado declara os componentes desse registro. Opcionalmente, o registro pode ter um corpo. Por exemplo:

record Point(int x, int y) { } 

Como os registros semanticamente são simples portadores de dados, eles recebem automaticamente elementos padrão:

  • Campo final privado para cada componente do estado;
  • Um método de leitura pública para cada componente de estado com o mesmo nome e tipo que o componente;
  • Um construtor público que corresponde à assinatura do registro; inicializa cada campo a partir do argumento correspondente;
  • Implementações de equals () e hashCode (), que dizem que dois registros são iguais se forem do mesmo tipo e contiverem o mesmo estado;
  • Uma implementação de toString (), que inclui uma representação de string de todos os componentes de gravação com seus nomes.

Em outras palavras, a apresentação do registro é inteiramente baseada em uma descrição do estado. Além disso, com base no estado do registro, ocorre a formação de equals (), hashCode () e toString ().

Limitações


Os registros não podem herdar nenhuma outra classe e não podem declarar campos de objetos, exceto os campos finais particulares que correspondem aos componentes de estado. Quaisquer outros campos declarados devem ser estáticos. Essas limitações garantem que a descrição do estado em si mesma defina a visualização.

As inscrições são finais e não podem ser abstratas. Essas restrições indicam que a API do registro é definida apenas por uma descrição do estado e não pode ser estendida posteriormente com outra classe ou registro.

Os componentes de gravação são finais. Essa restrição implementa o princípio de "inalterado por padrão", que é amplamente usado para conjuntos de dados.

Além das limitações mencionadas acima, os registros se comportam como classes comuns: eles podem ser declarados como de nível superior ou aninhados, podem ser genéricos, podem implementar interfaces. Os registros são criados chamando o novo operador. O corpo de gravação pode declarar métodos estáticos, campos estáticos, blocos de inicialização estáticos, construtores, métodos de instância, blocos de inicialização de instância e tipos aninhados. Um registro e componentes de estado individuais podem ser anotados. Se o registro estiver aninhado, será estático; isso elimina a situação com instâncias aninhadas que poderiam adicionar automaticamente estado ao registro.

Entradas explicitamente declaradas


Embora a implementação padrão de getters, bem como os métodos equals (), hashCode () e toString (), seja aceitável na maioria dos casos de uso, o desenvolvedor tem a opção de substituir a implementação padrão. No entanto, você deve ter um cuidado especial ao substituir os métodos equals / hashCode.

É dada atenção especial à declaração explícita do construtor canônico, cuja assinatura corresponde à descrição do estado do registro. O construtor pode ser declarado sem uma lista formal de parâmetros: nesse caso, presume-se que coincida com a descrição do estado, e quaisquer campos de registro são implicitamente inicializados pelo fechamento padrão do corpo do construtor dos parâmetros formais correspondentes (este. X = x) na saída. Isso permite que o construtor canônico verifique e ajuste apenas seus parâmetros, além de ignorar a inicialização explícita do campo. Por exemplo:

 record Range(int lo, int hi) { public Range { if (lo > hi) /* referring here to the implicit constructor parameters */ throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); } } 

Gramática


 RecordDeclaration: {ClassModifier} record TypeIdentifier [TypeParameters] (RecordComponents) [SuperInterfaces] [RecordBody] RecordComponents: {RecordComponent {, RecordComponent}} RecordComponent: {Annotation} UnannType Identifier RecordBody: { {RecordBodyDeclaration} } RecordBodyDeclaration: ClassBodyDeclaration RecordConstructorDeclaration RecordConstructorDeclaration: {Annotation} {ConstructorModifier} [TypeParameters] SimpleTypeName [Throws] ConstructorBody 

Anotações para componentes de gravação


As anotações de anotação podem ser aplicadas aos componentes de gravação se elas se aplicarem a componentes, parâmetros, campos ou métodos. As anotações de anúncio que se aplicam a qualquer um desses componentes se aplicam a declarações implícitas de qualquer elemento necessário.

As anotações de tipo que alteram os tipos de componentes de registro se estendem aos tipos nas declarações implícitas dos elementos necessários (por exemplo, parâmetros do construtor, declarações de campo e métodos). Declarações explícitas de elementos obrigatórios devem corresponder exatamente ao tipo do componente correspondente do registro, sem incluir anotações de tipo.

API de reflexão


Os seguintes métodos públicos serão adicionados ao java.lang.Class :

  • RecordComponent [] getRecordComponents ()
  • booleano isRecord ()

O método getRecordComponents () retorna uma matriz java.lang.reflect.RecordComponent , em que java.lang.reflect.RecordComponent é uma nova classe.

Os elementos dessa matriz correspondem aos componentes do registro e vão na mesma ordem em que são declarados no registro. Informações adicionais podem ser extraídas de cada RecordComponent na matriz, incluindo nome, tipo, genérico e seu valor.

O método isRecord () retornará true se esta classe for declarada como um registro. (Semelhante ao método isEnum () ).

Alternativas


Os registros podem ser definidos como a forma condicional das tuplas. Em vez de registros, podemos usar tuplas estruturais. Embora as tuplas ofereçam formas mais leves de expressar alguns conjuntos de dados, o resultado geralmente é menos informativo:

  • O principal princípio da filosofia Java é que os nomes são importantes . As classes e seus elementos possuem nomes relevantes para seu conteúdo, enquanto as tuplas e seus componentes não. Ou seja, a classe Person com as propriedades firstName e lastName é mais compreensível e confiável que a tupla anônima de String e String .
  • As classes suportam a validação de estado por meio de seus construtores, as tuplas não. Alguns conjuntos de dados, como intervalos numéricos, possuem invariantes que podem ser referenciados posteriormente se forem usados ​​pelo construtor;
  • As classes podem ter comportamento com base em seu estado; a combinação de estado e comportamento torna o próprio comportamento mais explícito e acessível. As tuplas, sendo apenas um conjunto de dados, não oferecem essa oportunidade.

Dependências


Os registros vão bem com tipos isolados (JEP 360) ; junto com tipos isolados, os registros formam uma construção, geralmente chamados de tipos de dados algébricos. Além disso, as próprias entradas permitem a correspondência de padrões . Como os registros associam suas APIs às descrições de estado, também podemos obter padrões de desconstrução para registros e usar as informações de classes isoladas em uma instrução switch .

Source: https://habr.com/ru/post/pt484700/


All Articles