Implementando Keys
Introdução
Algumas vezes ao implementar um buscador, consulta ou até mesmo cache é necessária a utilização de classes conhecidas como Keys que contém uma ou mais propriedades de uma classe em particular. Mas o que são keys e por que elas existem? Keys são classes criadas que garante a unicidade e a imutabilidade de determinada informação e em caso de uma busca, é possível garantir que o resultado da busca será o resultado esperado. São criadas quando uma determinada classe não implementa Equals e HashCode, e esta classe precisa ser utilizada como chave por exemplo em um cache, é necessário criar uma key.
Acesso rápido
Para acessar os tópicos do artigo siga o checklist abaixo:
Criando sua Key | help! | |
Implementando o teste de sua key | help! | |
Erro comum | help! |
Criando sua Key
As classes Key geralmente permanecem no mesmo pacote que a entidade a qual ela se refere, se a classe ClienteKey foi criada, logo a mesma deve se encontrar no mesmo pacote que Cliente.
Antes de criar sua key é preciso saber quais atributos podem compor a classe. Atributos que podem
compor uma key são todos aqueles que conseguem garantir unicidade, e geralmente este atributo é o
id
, só que pode acontecer do id
não ser suficiente para que isso aconteça e aí é necessário criá-la
adicionando outros atributos. Quais atributos podem ser utilizados? Abaixo há um exemplo de possíveis
candidatos a comporem uma key:
- código (enum) + id
- id (da interface atual) + id ( de uma interface herdada)
- tipo (enum) + código (string) + nome (string)
A utilização de outros atributos na criação de uma key é análoga a utilização de dois ou três atributos
para criação de uma chave primária composta
.
Obs.: As keys não precisam ser compostas necessariamente de chaves primárias.
Em nosso exemplo vamos implementar ClienteKey
que será composta por código, mãos à obra.
public class ClienteKey {
private final String codigo;
public ClienteKey(String codigo) {
this.codigo = codigo;
}
public String getCodigo() {
return codigo;
}
@Override
public final int hashCode() {
return Objects.hashCode(codigo);
}
@Override
public final boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof ClienteKey) {
final ClienteKey that = (ClienteKey) obj;
return Objects.equal(this.codigo, that.codigo);
} else {
return false;
}
}
}
Obs.: Se o tipo de dados passado para a construção da key for um tipo primitivo, utilizar
==
ao invés de equal
, evitando assim que a jvm
faça autoboxing desnecessário.
Exemplo:
if (obj instanceof ClienteKey) {
final ClienteKey that = (ClienteKey) obj;
return Objects.equal(this.codigo, that.codigo);
}
if (obj instanceof ClienteKey) {
final ClienteKey that = (ClienteKey) obj;
return this.id == that.id;
}
Para este último exemplo suponha que o id
é um int
.
<div class="alert alert info">
Cuidado! Atente que ao gerar os métodos equals() e hashCode() é preciso defini-los como final,
para evitar que os mesmo sejam sobrescritos, o eclipse por padrão não faz isso!
Se os métodos não forem definidos como final irá aparecer esta mensagem:
</div>
FAILED: equals_test
java.lang.AssertionError: Subclass: equals is not final.
Supply an instance of a redefined subclass using withRedefinedSubclass if equals cannot be final.
Para verificar se de fato tudo está correto é preciso implementar um teste de ClienteKey que verificará se a implementação dos métodos acima está correta.
Implementando o teste de sua key
Como de costume o teste deve permanecer no mesmo pacote de testes que a classe ClienteKey, atente-se a implementação do teste.
@Test
public class TesteDeClienteKey {
public void equals_test() {
EqualsVerifier
.forClass(ClienteKey.class)
.verify();
}
}
Erro comum
Um erro comum que acontece no teste de key
é o de mutabidade, em implementações mais antigas
as variáveis não eram definidas como final no Jdbc
. Para melhor entendimento será utilizado
o Jdbc
da entidade Cliente
.
public class ClienteJdbc implements Cliente {
private Integer id;
private String codigo;
private String nome;
public ClienteJdbc() {
}
public ClienteJdbc(String codigo, String nome) {
this.codigo = codigo;
this.nome = nome;
}
public ClienteJdbc(Construtor construtor) {
this.codigo = construtor.getCodigo();
this.nome = construtor.getNome();
}
O teste de cliente
foi montado muito parecido com o TesteDeClienteKey
, com a diferença de ser passado
o Jdbc
:
@Test
public class TesteDeCliente {
public void testeDeEquals() {
EqualsVerifier
.forClass(ClienteJdbc.class)
.verify();
}
}
Ao executar o teste dará um erro de mutabilidade com esta mensagem no console:
FAILED: testeDeEquals
java.lang.AssertionError: Mutability: equals depends on mutable field codigo.
Para corrigir este erro deve-se colocar o enum suppress Warning
e o teste ficará desta forma:
@Test
public class TesteDeCliente {
public void testeDeEquals() {
EqualsVerifier
.forClass(ClienteJdbc.class)
.withRedefinedSuperclass()
.withRedefinedSuperclass().suppress(Warning.NONFINAL_FIELDS).verify();
}
}
Após este tratamento o teste funcionou. Com a utilização deste enum o teste ignora campos que não são final.
Para entender mais sobre os funcionamento do EqualsVerifier
clique aqui.