Equals o 4° mandamento ... [resolvido]

Bom me surgiu uma duvida, sobre o quarto mandamento e sua correta implementação

  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

    tranduzindo isso, se estou correto

  • diz que a igualdade entre objetos é consistente, e que para qualquer referencia não nulas de x e y, mulplas invocações de x.equals(y) terá sempre que retornar true ou sempre que retornar false, portanto as informações usadas na comparação entre objetos não podem sofrer modificações.

    Pensando em sistemas com beans persistentes, como fazer isso funcionar ?? objetos sendo carregados, utilizados, criados e posteriormente persistindos e só nessa hora ganhando seu ID (principal atributo usado em igualdades) … como então garantir essa parte da igualdade ??

    abro aqui essa discussão, espero que o ponto que levantei seja entendido… desde já agradeço… abraços

  • O equals tem restrições muito fortes e é difícil implementá-lo de forma correta para alguns tipos de classes, como por exemplo, beans de entidade (ou alguma coisa equivalente). Na minha opinião, você nunca deveria sobreescrever equals em beans de entidade.

    O que faria sentido seria, caso exista um objeto específico para a PK, sobreescrever o equals da PK. Caso a PK seja int, Integer, String, Date ou algo assim, é fácil e nada precisa ser feito. Caso a PK seja PlacaDeCarro, você sobrescreve o equals (e o hashCode também) da classe PlacaDeCarro.

    Essas classes que representam a PK são Value Objects (VOs) de acordo com a definição do Eric Evans e do Martin Fowler, que são objetos imutáveis que carregam algum valor (não confundir com o antipadrão do modelo anêmico com o mesmo nome, ou seja, um monte de atributos e getters e setters burros do modelo anêmico).

    Para exemplificar, vamos definir o seguinte:

    [code]public final class PlacaDeCarro {
    private final String placa;

    public PlacaDeCarro(String letras, int numero) {
        if (numero < 0 || numero > 9999) throw new IllegalArgumentException();
        if (letras == null || letras.size() != 3) throw new IllegalArgumentException();
        char[] let = letras.toCharArray();
        if (let[0] < 'A' || let[0] > 'Z' || let[1] < 'A' || let[1] > 'Z' || let[2] < 'A' || let[2] > 'Z') {
            throw new IllegalArgumentException();
        }
        this.placa = letras + new DecimalFormat("0000").format(numero);
    }
    
    @Override
    public String toString() {
        return placa;
    }
    
    @Override
    public boolean equals(Object obj) {
        return obj != null && obj instanceof PlacaDeCarro && ((PlacaDeCarro) obj).placa.equals(this.placa);
    }
    
    @Override
    public int hashCode() {
        return placa.hashCode();
    }
    

    }

    public class Carro {
    private PlacaDoCarro placa;

    private int numeroPortas;
    
    private String modelo;
    
    private int ano;
    
    private Color cor;
    
    // Construtor padrão. Possivelmente aquele gerado pelo compilador, ou não (tanto faz).
    public Carro() {}
    
    // Mais atributos, getters, setters e possivelmente outros métodos.
    

    }[/code]Vamos supor que você sobrescreva equals e hashCode na classe Carro. Se você fizer isso, vai ter problemas:[code]
    Map<Carro, Pessoa> carrosDonos = new HashMap<Carro, Pessoa>();

    Carro c1 = new Carro();
    c1.setPlaca(new PlacaDoCarro(“ABC”, 1234));
    c1.setModelo(“Vectra”);
    Pessoa p = new Pessoa();
    p.setNome(“João”);
    carrosDonos.put(c1, p);
    System.out.println(carrosDonos.get(c1).getNome()); // Vai imprimir João.
    c1.setModelo(“Corsa”);
    System.out.println(carrosDonos.get(c1).getNome()); // Se o hashCode tiver mudado, problemas a vista![/code]Ou seja, se você sobrescrever o equals e o hashCode, provavelmente vai ter problemas!
    Caso você só considere a placa no equals e no hashCode, mesmo assim você tem problemas:Carro c1 = new Carro(); c1.setPlaca(new PlacaDoCarro("ABC", 1234)); c1.setModelo("Vectra"); Carro c2 = new Carro(); c2.setPlaca(new PlacaDoCarro("ABC", 1234)); c2.setModelo("Palio"); System.out.println(c1.equals(c2)); // Vai dar true, um Palio é igual a um Vectra!Ou seja, mesmo assim você vai ter problemas.
    Conclusão: Não sobrescreva o equals em entidades. Uma solução seria criar um outro método (vou chamá-lo de sameId(Object)) e usá-lo no lugar do equals. Esse método, não diria se dois objetos são iguais, apenas se têm o mesmo id.

    não é bem esse o ponto,

    mas sobre o que vc colocu, é inviável, isso traz 1 problema de dimensões catastroficas, quase todos os frameworks utilizam collections, e collection não funcionam sem a correta implementação de equals e hashCode…

    implementar corretamente um equals para uma entidade é muito facil, desde que vc ignore o 4° mandamento, em que fala que todos as informações para o teste de equals são imutaveis…

    quase todo bean inicia em branco, e tem seus dados carregados, ou iniciam com os principais dados preenchidos e é persistido, e tem seu ID preenchido…

    segundo a regra do equals, isso torania impossivel implementar o equals, porem é facil de entender, que 2 obejtos entidades são iguais, sempre que seus IDs são iguais…

    Esse é o unico ponto, e acho totalmente inviável não implementar equals, e tb hashCode (para otimizar a manipulação de collections)…

    A duvida é essa, será que essa 4° regra, onde diz que o estado de igualdade do objeto é imutavel ?? esse reciocinio é facil de compreender e de implementar para VOs (Values Objects) porem, para outros objetos é algo dificil…

    é realmente um erro ? ignorar essa regra ??

    normalmente eu gosto da seguinte abordagem,

    [code]public class Pessoa {
    private Integer id;
    private String nome;
    private String cpf;

    public boolean equals(Object o) {
    if (!(o instanceof Pessoa))
    return false;
    if (id != null)
    return id.equals(other.id);
    if (cpf != null)
    return cpf.equals(other.cpf);
    return false;
    }

    public int hashCode() {
    int hash = 5;
    hash = hash*57 + id != null ? id.hashCode() : hashCodeIdNull();
    }

    public int hashCodeIdNull() {
    return cpf != null ? cpf.hashCode() : 0;
    }
    }
    [/code]

    em resumo do equals…

    • caso haja ID, o teste seria por ID…
    • caso não haja ID e haja CPF, o teste seria por CPF…
    • caso contrario os objetos não podem ser iguais, pois nem há como testar.

    e se vc olhar, essa é a forma mais logica de testar 2 pessoas, com esses atributos… e o hashCode tb é conforme, se houver ID hashCode por id, se não houver hashCode por CPF…

    porem se eu persistir o objeto, ele praticamente se transforma em outro, ganha um ID… agora como se manter conforme com a 4° lei do equals ??
    se manter na lei de constancia do hashCode é facil, so vai perder um pouco de performance no caso de não haver id…

    Esse quarto mandamento tem uma condição escondida:

    O equals deve ser consistente somente enquanto o objeto continuar consistente. Ou seja, se mudou um atributo que o identifica, ele é um objeto diferente.

    [quote=Bruno Laturner]Esse quarto mandamento tem uma condição escondida:

    O equals deve ser consistente somente enquanto o objeto continuar consistente. Ou seja, se mudou um atributo que o identifica, ele é um objeto diferente.[/quote]

    pois é, é a unica forma de reciocinar com essa regra, e implementar o equals corretamente, conforme comentario abaixo…

    [quote=Lavieri]pois é, é a unica forma de reciocinar com essa regra, e implementar o equals corretamente, conforme comentario abaixo…

    Você tem que pensar no seguinte:

    Não é Sun, a IBM, nem a JCP nem o Gosling que vão te dizer se um dos teus objetos é igual ao outro, é você quem dirá isso.

    O que quero dizer com isso é que é você quem sabe se vai considerar se esse objeto com ID diferente é o mesmo de um outro. Se disser que “Sim, eles são diferentes” então o equals retornará false. Não importa o que outros achem disso.

    Pessoalmente eu não acho que o ID fornecido pela persistência deve ser uma das chaves dessa comparação. IDs não fazem parte do teu negócio, do teu domínio.

    Acredito que o ID faz parte do seu modelo sim, afinal de contas vc o usa, e é ele que identifica o objeto, e é geralmente através dele que vc faz a referencia…

    vc pode ter outros coisas que identifique a igualdade entre objetos…

    acredito que se os IDs são iguais, mesmo tendo CPF diferentes, por exemplo, um com CPF preenchido e outro sem CPF… é possivel sim, afirmar que os 2 objetos são iguais (ou seja, fazem referencia a mesma pessoa dentro do teu dominio)…

    por exemplo… imagine que o ID 12 identifica o joão, e que ele ainda não tem seu CPF definido…

    [code]Pessoa joao1 = Repositories.findById(Pessoa.class,12);
    Pessoa joao2 = Repositories.findById(Pessoa.class,12);

    joao2.setCpf(“111.222.333-44”);[/code]

    mesmo tendo joão2 com o CPF setado enquanto joao1 não o tem… acredito que

    joao1.equals(joao2); deva continuar sendo verdade, pois os objetos são os mesmo

    imagine vc tendo um Set list… contendo varias pessoas

    Set<Pessoa> pessoas = new HashSet<Pessoa(); pessoas.add(joao1); pessoas.add(joao2);

    ali eu vou ter na verdade 2 objetos iguais dentro do Set, o que não deveria ocorrer, caso não concidere o ID como diferenciador do objeto.

    Acho dificil no seu dominio, vc falar que algo é o Identificador da sua entidade, e dizer que ele não pertence ao seu modelo… o.O>

    São identificadores no banco de dados (relacional ainda por cima), que requer eles, eles só facilitam a procura pelo registro. Se são identificadores na tua aplicação, não sei.

    O ponto é que com um mecanismo de persistência totalmente transparente, que faz todas as ligações por você, nem saberia que os IDs existem. Pergunte se eu sei em que endereço de memória está um objeto que acabei de alocar. Quem sabe é a JVM; a única coisa que sei são dos dados do objeto.

    Não vejo assim, e ID não é so uma chave primaria , auto-incrase, colocada na tabela, ID é o modo de diferenciar um registro de outro na sua tabela… as vezes nada na sua tabela o identifica, e então vc cria um ID para fazer isso…

    Como citou o exemplo o victorwss, o ID de um carro é sua placa … o ID de uma pessoa, em uma sistema que obriga ter CPF pode ser o seu CPF …

    e por ai vai… e mesmo sendo apenas o ID criado para facilitar, acredito q seja importante sim, e principalmente para o equals, é a forma de determinar a igualdade entre 2 objetos… principalmente pq é a Identificação do seu objeto…

    o grande problema de vocês é um pequeno erro de tradução …
    não esta escrito que os elementos utilizados no equals são imutáveis …
    esta escrito que o equals tem que retornar sempre true ou sempre false, desde que nenhuma informação utilizada no equals seja alterada …

    Exemplo:

    public class Teste {
       private String name;
       public Teste(String name) {
          if(name==null) throw new IllegalArgumentException();
          this.name = name;
       }
       @Override
       public String toString(){
          return name;
       }
       public void change(String nn){
          this.name = nn;
       }
       @Override
       public  int hashCode(){
          return name.hashCode();
       }
       @Override
       public boolean equals(Object other){
          return other==this || (other!=null && other instanceof Teste && name.equals(other.name));
       }
    }
    
    Teste t1 = new Teste("Rodrigo");
    Teste t2 = new Teste("Rodrigo");
    //Tem que retornar sempre true por que os dois objetos são iguais neste momento
    for(int i=0;i<200;i++){
       System.our.println(t1.equals(t2));
       System.our.println(t2.equals(t1));
    }
    t2.change("Urubatan");
    //Tem que retornar sempre false por que os dois objetos são diferentes neste momento
    for(int i=0;i<200;i++){
       System.our.println(t1.equals(t2));
       System.our.println(t2.equals(t1));
    }

    E isto não invalida a regra 4.
    Não esta escrito que "The information used in equals comparisons on the objects cannot be modified" :smiley:

    tnks! … sabia que não podia ser o que li huauhaa… meu inglês não é o melhor, brigadao ai Urubatan

    Os IDs que estava falando eram somente as PKs, sequences, auto-generated, colocadas nas tabelas.

    Para qualquer outro tipo de ID, daqueles que o usuário importa em manter seguro, estes concordo plenamente que possam participar do equals.

    Bem, então, só esteja atento quanto a objetos que o equals pode atuar diferente no futuro ou que o hashCode possa mudar. Esses objetos podem ser perigosos de usar como chaves em HashMaps, HashSets, ou quaisquer outras coisas que confiem demais em equals e hashCode para funcionar adequadamente. E se forem usados para tal, tenha certeza que não mudem enquanto estão sendo usados desta forma.

    Concordo plenamento com o Bruno, um ID nao deve fazer parte do equals. E tambem um ID nao deve ser uma informacao que tenha significado no modelo, ele deve servir somente para identificar um registro no banco de dados relacional. (Tanto que se estiver usando um banco oo vc nao precisa de ID).

    Deve fazer parte do equals algum atributo- ou atributos - do objeto que seja unico para o negocio. No exemplo do cliente seria o cpf (e é uma ideia terrivel que pode custar muito caro usar cpf como chave primaria de uma tabela).

    Por quê?

    é so que imagina c teu banco aceitar CPF nulo, nem sempre é exegido que toda pessoa tenha CPF cadastrado, como vc vai diferenciar 1 objeto de outro ?

    Não sei, é facil de endner que não haveria sentido de usar apenas um atributo de indice, mais na pratica nem sempre da pra não usar… alem de ser a forma mais rapida … se vc for pensar em desempenho

    Caso permita cpf nulo o cpf nao pode ser utilizado no equals. Normalmente sera utilizado um codigo que identifica o cliente para o usuario (veja que nao eh o mesmo do Id), tambem pode ser gerado automaticamente pelo sistema, mas nesse caso ele so se torna uma entidade valida para o negocio depois que lhe é atribuido um codigo. Antes so pode ser testado por instancia. No caso especifico do cliente nao imagino outra forma.

    [quote=Lavieri]é so que imagina c teu banco aceitar CPF nulo, nem sempre é exegido que toda pessoa tenha CPF cadastrado, como vc vai diferenciar 1 objeto de outro ?

    Não sei, é facil de endner que não haveria sentido de usar apenas um atributo de indice, mais na pratica nem sempre da pra não usar… alem de ser a forma mais rapida … se vc for pensar em desempenho[/quote]

    Editado porque nao consegui entender direito o que voce disse, mas o que quero dizer é algo como:

    no modelo relacional:

    create table pedido(
    pedido_id integer, – pk pode ser string (guid, sequencial, qqr coisa)
    numero_pedido integer,
    ano_pedido integer,
    numero_filial integer,


    )

    onde numero_pedido, ano_pedido e numero_filial formam uma chave unica.

    e no equals vc usa os tres atributos. Enquanto os tres nao forem atribuidos o pedido nao esta em estado consistente, portanto o equals so faz sentido na comparacao por instancia.