Por que preciso substituir os métodos equals e hashCode em Java?


383

Recentemente, li este documento do Developer Works .

O documento é tudo sobre a definição hashCode()e equals()de forma eficaz e correcta, no entanto eu não sou capaz de descobrir por que precisamos substituir esses dois métodos.

Como posso tomar a decisão de implementar esses métodos com eficiência?


4
Existem dois ótimos artigos em programming.guide explicando exatamente isso: Quando devo substituir iguais? e Por que você sempre deve substituir o hashCode ao substituir igual . (Aviso, a resposta aceita está realmente errada.) #
Aioobe

A substituição de maiúsculas e minúsculas é igual a apenas: dois objetos iguais terão código de hash diferente = os mesmos objetos entram em um intervalo diferente (duplicação). Substituição de maiúsculas e minúsculas apenas código de hash: dois mesmos objetos terão o mesmo código de hash = o mesmo objeto no mesmo intervalo (duplicação).
VdeX 12/09

Respostas:


524

Joshua Bloch diz sobre Java eficaz

Você deve substituir hashCode () em todas as classes que substituem igual a (). Não fazer isso resultará em uma violação do contrato geral de Object.hashCode (), que impedirá que sua classe funcione corretamente em conjunto com todas as coleções baseadas em hash, incluindo HashMap, HashSet e Hashtable.

Vamos tentar entender isso com um exemplo do que aconteceria se substituíssemos equals()sem substituir hashCode()e tentássemos usar a Map.

Digamos que temos uma classe como esta e que dois objetos de MyClasssão iguais se os seus importantFieldforem iguais (com hashCode()e equals()gerados pelo eclipse)

public class MyClass {

    private final String importantField;
    private final String anotherField;

    public MyClass(final String equalField, final String anotherField) {
        this.importantField = equalField;
        this.anotherField = anotherField;
    }

    public String getEqualField() {
        return importantField;
    }

    public String getAnotherField() {
        return anotherField;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((importantField == null) ? 0 : importantField.hashCode());
        return result;
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final MyClass other = (MyClass) obj;
        if (importantField == null) {
            if (other.importantField != null)
                return false;
        } else if (!importantField.equals(other.importantField))
            return false;
        return true;
    }

}

Substituir apenas equals

Se apenas equalsfor anulado, quando você chamar myMap.put(first,someValue)primeiro o hash de um balde e quando você o chamar myMap.put(second,someOtherValue), o hash de outro balde (como eles têm um diferente hashCode). Portanto, embora sejam iguais, pois não fazem o hash no mesmo intervalo, o mapa não consegue perceber e os dois permanecem no mapa.


Embora não seja necessário substituir, equals()se substituirmos hashCode(), vamos ver o que aconteceria nesse caso específico, onde sabemos que dois objetos de MyClasssão iguais se os importantFieldmesmos forem iguais, mas não substituímos equals().

Substituir apenas hashCode

Imagine que você tem isso

MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");

Se você substituir apenas hashCode, quando ligar myMap.put(first,someValue)primeiro, calcula hashCodee armazena em um determinado intervalo. Então, quando você ligar myMap.put(second,someOtherValue), substitua primeiro pelo segundo, conforme a Documentação do Mapa, pois eles são iguais (de acordo com os requisitos de negócios).

Mas o problema é que iguais não foram redefinidos; portanto, quando o mapa secondpassa e percorre o balde, procurando se existe algum objeto kque second.equals(k)seja verdadeiro, ele não encontrará o que second.equals(first)será false.

Espero que esteja claro


5
você pode elaborar um pouco mais, no segundo caso, por que o segundo objeto deve ir em outro balde?
Hussain Akhtar Wahid 'Ghouri'

57
Não gosto desta resposta porque sugere que você não pode substituir hashCode () sem substituir igual a (), o que simplesmente não é verdadeiro. Você diz que seu código de exemplo (a parte "substituir somente hashCode") não funcionará porque você define seus dois objetos como iguais, mas - desculpe - essa definição está apenas na sua cabeça. No seu primeiro exemplo, você tem dois objetos diferentes com o mesmo hashCode, e isso é perfeitamente legal. Portanto, o motivo pelo qual você precisa substituir equals () não é porque você já substituiu o hashCode (), mas porque deseja mover sua definição "igual" da cabeça para o código.
user2543253

11
if you think you need to override one, then you need to override both of themestá errado. Você precisa substituir hashCodese a sua classe substitui, equalsmas a reversão não é verdadeira.
akhil_mittal

4
Acho totalmente aceitável substituir somente hashCode () sem substituir igual a () também. Também está escrito em Java eficaz : books.google.fr/…
Johnny

2
@PhantomReference, observe que apenas a substituição equalsviolaria o contrato descrito no javadoc de Object: "Se dois objetos forem iguais de acordo com o equals(Object)método, a chamada do hashCodemétodo em cada um dos dois objetos deverá produzir o mesmo resultado inteiro". Claro, nem todas as partes de todos os contratos são exercidas em todo o código, mas ainda assim, formalmente, é uma violação e eu consideraria um bug esperando para acontecer.
aioobe

264

Coleções como HashMape HashSetusam um valor de código de hash de um objeto para determinar como ele deve ser armazenado dentro de uma coleção, e o código de hash é usado novamente para localizar o objeto em sua coleção.

A recuperação de hash é um processo de duas etapas:

  1. Encontre o balde certo (usando hashCode())
  2. Pesquise no depósito o elemento certo (usando equals())

Aqui está um pequeno exemplo de por que devemos substituir equals()e hashcode().

Considere uma Employeeclasse que possui dois campos: idade e nome.

public class Employee {

    String name;
    int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof Employee))
            return false;
        Employee employee = (Employee) obj;
        return employee.getAge() == this.getAge()
                && employee.getName() == this.getName();
    }

    // commented    
    /*  @Override
        public int hashCode() {
            int result=17;
            result=31*result+age;
            result=31*result+(name!=null ? name.hashCode():0);
            return result;
        }
     */
}

Agora crie uma classe, insira o Employeeobjeto em um HashSete teste se esse objeto está presente ou não.

public class ClientTest {
    public static void main(String[] args) {
        Employee employee = new Employee("rajeev", 24);
        Employee employee1 = new Employee("rajeev", 25);
        Employee employee2 = new Employee("rajeev", 24);

        HashSet<Employee> employees = new HashSet<Employee>();
        employees.add(employee);
        System.out.println(employees.contains(employee2));
        System.out.println("employee.hashCode():  " + employee.hashCode()
        + "  employee2.hashCode():" + employee2.hashCode());
    }
}

Ele imprimirá o seguinte:

false
employee.hashCode():  321755204  employee2.hashCode():375890482

Agora, remova o comentário do hashcode()método, execute o mesmo e a saída seria:

true
employee.hashCode():  -938387308  employee2.hashCode():-938387308

Agora você pode ver por que, se dois objetos são considerados iguais, seus códigos de hash também devem ser iguais? Caso contrário, você nunca seria capaz de encontrar o objeto, já que o método padrão de código de hash na classe Object praticamente sempre apresenta um número exclusivo para cada objeto, mesmo que o equals()método seja substituído de tal maneira que dois ou mais objetos sejam considerados iguais . Não importa quão iguais sejam os objetos, se seus códigos de hash não refletirem isso. Então, mais uma vez: se dois objetos são iguais, seus hashcode s também devem ser iguais.


4
Exemplo perfeito. Demonstrou claramente a diferença!
Coderpc

3
agradável explicou @rajeev
VdeX

2
@VikasVerma igual a objeto terá código hash igual não significa que objeto desigual terá código hash desigual. E se os objetos forem realmente diferentes, mas o código hash for o mesmo?
Ravi

11
explicou muito bem :)
Rahul

4
resposta muito melhor do que a resposta aceita! Graças
troncos

50

Você deve substituir hashCode () em todas as classes que substituem igual a (). Não fazer isso resultará em uma violação do contrato geral de Object.hashCode (), o que impedirá que sua classe funcione corretamente em conjunto com todas as coleções baseadas em hash, incluindo HashMap, HashSet e Hashtable.


   de Effective Java , por Joshua Bloch

Ao definir equals()e hashCode()consistentemente, você pode melhorar a usabilidade de suas classes como chaves em coleções baseadas em hash. Como o documento da API do hashCode explica: "Este método é suportado para o benefício de tabelas de hash, como as fornecidas por java.util.Hashtable".

A melhor resposta para sua pergunta sobre como implementar esses métodos com eficiência está sugerindo que você leia o Capítulo 3 de Java Efetivo .


4
Esta é a resposta correta. Como corolário, é claro, que se você nunca usar a classe em uma coleção baseada em hash, não importa se você não implementou hashCode().
magro

11
Em casos mais complexos, você nunca sabe se as coleções que você usa está usando hashes, portanto, fique longe "não importa que você não tenha implementado hashCode ()"
Victor Sergienko

11
Posso substituir hashCode () sem substituir igual a ()?
Johnny

@StasS, sim, ao contrário do que diz a resposta aceita. Veja a explicação na segunda parte deste artigo: Por que você sempre deve substituir o hashCode ao substituir igual
aioobe 28/11/16

22

Simplificando, o método equals em Object verifica a igualdade de referência, onde duas instâncias da sua classe ainda podem ser semanticamente iguais quando as propriedades são iguais. Isso é importante, por exemplo, ao colocar seus objetos em um contêiner que utilize iguais e hashcode, como HashMap e Set . Digamos que temos uma classe como:

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }
}

Criamos duas instâncias com o mesmo ID :

Foo a = new Foo("id", "something");
Foo b = new Foo("id", "something else");

Sem substituir os iguais, estamos obtendo:

  • a.equals (b) é falso porque são duas instâncias diferentes
  • a.equals (a) é verdadeiro, pois é a mesma instância
  • b.equals (b) é verdadeiro, pois é a mesma instância

Corrigir? Bem, talvez, se é isso que você quer. Mas digamos que queremos que objetos com o mesmo ID sejam o mesmo objeto, independentemente de serem duas instâncias diferentes. Substituímos os iguais (e código de hash):

public class Foo {
    String id;
    String whatevs;

    Foo(String id, String whatevs) {
        this.id = id;
        this.whatevs = whatevs;
    }

    @Override
    public boolean equals(Object other) {
        if (other instanceof Foo) {
            return ((Foo)other).id.equals(this.id);   
        }
    }

    @Override
    public int hashCode() {
        return this.id.hashCode();
    }
}

Quanto à implementação de equals e hashcode, posso recomendar o uso dos métodos auxiliares do Guava


20

Identidade não é igualdade.

  • é igual à ==identidade do teste do operador .
  • equals(Object obj) O método compara o teste de igualdade (ou seja, precisamos dizer à igualdade substituindo o método)

Por que preciso substituir os métodos equals e hashCode em Java?

Primeiro, precisamos entender o uso do método equals.

Para identificar diferenças entre dois objetos, precisamos substituir o método equals.

Por exemplo:

Customer customer1=new Customer("peter");
Customer customer2=customer1;
customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
------------------------------
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.

------------------------------
Now I have overriden Customer class equals method as follows:
 @Override
    public boolean equals(Object obj) {
        if (this == obj)   // it checks references
            return true;
        if (obj == null) // checks null
            return false;
        if (getClass() != obj.getClass()) // both object are instances of same class or not
            return false;
        Customer other = (Customer) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference 
            return false;
        return true; 
    }
Customer customer1=new Customer("peter");
Customer customer2=new Customer("peter");
Insteady identify the Object equality by JVM, we can do it by overring equals method.
customer1.equals(customer2);  // returns true by our own logic

Agora, o método hashCode pode entender facilmente.

hashCode produz um número inteiro para armazenar objetos em estruturas de dados como HashMap , HashSet .

Suponha que substituímos o método igual a Customeracima,

customer1.equals(customer2);  // returns true by our own logic

Enquanto trabalhamos com a estrutura de dados quando armazenamos objetos em buckets (bucket é um nome sofisticado para pasta). Se usarmos a técnica de hash integrada, para mais de dois clientes, ele gera dois hashcode diferentes. Então, estamos armazenando o mesmo objeto idêntico em dois lugares diferentes. Para evitar esse tipo de problema, devemos substituir o método hashCode também com base nos seguintes princípios.

  • instâncias não iguais podem ter o mesmo código hash.
  • instâncias iguais devem retornar o mesmo código hash.

3
Era isso que eu procurava desde a última hora. Awesome mate (y)
Adnan

13

Ok, deixe-me explicar o conceito em palavras muito simples.

Primeiramente, de uma perspectiva mais ampla, temos coleções, e o hashmap é uma das estruturas de dados nas coleções.

Para entender por que precisamos substituir o método equals e hashcode, se for necessário primeiro entender o que é o hashmap e o que ele faz.

Um hashmap é uma estrutura de dados que armazena pares de dados-chave de valor de maneira array. Vamos dizer a [], onde cada elemento em 'a' é um par de valores-chave.

Além disso, cada índice na matriz acima pode ser vinculado à lista, tendo assim mais de um valor em um índice.

Agora, por que um hashmap é usado? Se tivermos que pesquisar em uma matriz grande, pesquisando em cada uma delas, se elas não forem eficientes, o que a técnica de hash nos diz que permite pré-processar a matriz com alguma lógica e agrupar os elementos com base nessa lógica, por exemplo, Hashing

por exemplo: temos matriz 1,2,3,4,5,6,7,8,9,10,11 e aplicamos uma função hash mod 10 para que 1,11 sejam agrupados. Portanto, se tivéssemos que procurar 11 na matriz anterior, teríamos que iterar a matriz completa, mas quando a agrupamos, limitamos nosso escopo de iteração, melhorando assim a velocidade. Essa estrutura de dados usada para armazenar todas as informações acima pode ser vista como uma matriz 2D para simplificar

Agora, além do hashmap acima, também informa que ele não adicionará duplicados. E esta é a principal razão pela qual temos que substituir os iguais e o código de hash

Então, quando se diz que explica o trabalho interno do hashmap, precisamos descobrir quais métodos o hashmap possui e como ele segue as regras acima, que expliquei acima

portanto, o hashmap tem o método chamado como put (K, V) e, de acordo com o hashmap, ele deve seguir as regras acima para distribuir eficientemente a matriz e não adicionar duplicatas

Então, o que faz é que ele primeiro gere o código hash da chave especificada para decidir em qual índice o valor deve entrar. se nada estiver presente nesse índice, o novo valor será adicionado por lá, se algo já estiver lá o novo valor deve ser adicionado após o final da lista vinculada nesse índice. mas lembre-se de que nenhuma duplicata deve ser adicionada conforme o comportamento desejado do hashmap. então digamos que você tenha dois objetos Inteiros aa = 11, bb = 11. Como todo objeto derivado da classe de objeto, a implementação padrão para comparar dois objetos é que ele compara a referência e não os valores dentro do objeto. Portanto, no caso acima, ambos semânticos iguais serão reprovados no teste de igualdade e a possibilidade de existirem dois objetos com o mesmo código de hash e os mesmos valores, criando duplicatas. Se substituirmos, poderemos evitar adicionar duplicatas. Você também pode consultarDetalhe de trabalho

import java.util.HashMap;


public class Employee {

String name;
String mobile;
public Employee(String name,String mobile) {
    this.name=name;
    this.mobile=mobile;
}

@Override
public int hashCode() {
    System.out.println("calling hascode method of Employee");
    String str=this.name;
    Integer sum=0;
    for(int i=0;i<str.length();i++){
        sum=sum+str.charAt(i);
    }
    return sum;

}
@Override
public boolean equals(Object obj) {
    // TODO Auto-generated method stub
    System.out.println("calling equals method of Employee");
    Employee emp=(Employee)obj;
    if(this.mobile.equalsIgnoreCase(emp.mobile)){

        System.out.println("returning true");
        return true;
    }else{
        System.out.println("returning false");
        return false;
    }


}

public static void main(String[] args) {
    // TODO Auto-generated method stub

    Employee emp=new Employee("abc", "hhh");
    Employee emp2=new Employee("abc", "hhh");
    HashMap<Employee, Employee> h=new HashMap<>();
    //for (int i=0;i<5;i++){
        h.put(emp, emp);
        h.put(emp2, emp2);

    //}

    System.out.println("----------------");
    System.out.println("size of hashmap: "+h.size());


}

}

Eu tenho uma confusão: por que precisamos substituir o método equals quando substituímos o método hashCode no caso do HashMap? De qualquer forma, o hashmap substitui o valor se o código de hash do objeto for igual.
precisa

O hashmap do @VikasVerma não substitui nenhum tipo de valor se o código de hash dos objetos for igual, ele decide apenas o índice onde o objeto recém-adicionado ao hashmap deve ser colocado. Agora, pode haver objetos no índice; portanto, para evitar duplicados, substituímos o método equals e escrevemos a lógica para definir quando os dois objetos em comparação devem ser tratados como iguais. Se não for substituído, em seguida, embora objectos com mesmos valores serão armazenados porque a referência de ambos os objetos serão diferentes
Chetan

11

hashCode() :

Se você substituir apenas o método de código hash, nada acontecerá. Porque ele sempre retorna novo hashCodepara cada objeto como uma classe Object.

equals() :

Se você substituir apenas o método igual, a.equals(b)é verdade, isso significa que o hashCodede aeb deve ser o mesmo, mas não acontecer. Porque você não substituiu o hashCodemétodo.

Nota: o hashCode()método da classe Object sempre retorna novo hashCodepara cada objeto.

Portanto, quando você precisar usar seu objeto na coleção baseada em hash, deverá substituir ambos equals()e hashCode().


Esse é um ponto interessante, sobre substituir apenas hashCode () . Está totalmente bom, certo? Ou também pode haver casos problemáticos?
Johnny

11
Esta é uma resposta enganosa e errada. A substituição (= somente =) hashCode () garante que todo objeto que está sendo instanciado da respectiva classe com propriedades semelhantes possua o mesmo código de hash. Mas não será útil, pois nenhum deles será igual um ao outro.
mfaisalhyder

8

Java coloca uma regra que

"Se dois objetos são iguais usando o método Object class equals, o método hashcode deve fornecer o mesmo valor para esses dois objetos."

Portanto, se em nossa classe substituirmos equals(), devemos substituir hashcode()também o método para seguir esta regra. Ambos os métodos, equals()e hashcode(), são usados Hashtable, por exemplo, para armazenar valores como pares de valores-chave. Se substituirmos um e não o outro, existe a possibilidade de que ele Hashtablenão funcione como queremos, se usarmos esse objeto como chave.


6

Porque se você não os substituir, você usará a implentação padrão em Object.

Dado que os valores de igualdade e hascode da instância geralmente requerem conhecimento do que compõe um objeto, eles geralmente precisam ser redefinidos em sua classe para ter algum significado tangível.


6

Para usar nossos próprios objetos de classe como chaves em coleções como HashMap, Hashtable etc., devemos substituir os dois métodos (hashCode () e equals ()), tendo em mente o trabalho interno da coleção. Caso contrário, leva a resultados errados que não são esperados.


6

Adicionando à resposta de @Lombo

Quando você precisará substituir igual a ()?

A implementação padrão de equals () do objeto é

public boolean equals(Object obj) {
        return (this == obj);
}

o que significa que dois objetos serão considerados iguais apenas se tiverem o mesmo endereço de memória, o que será verdadeiro somente se você estiver comparando um objeto com ele.

Mas você pode considerar dois objetos iguais, se eles tiverem o mesmo valor para uma ou mais de suas propriedades (consulte o exemplo dado na resposta de @Lombo).

Então você substituirá equals()essas situações e daria suas próprias condições para a igualdade.

Eu implementei com sucesso equals () e está funcionando muito bem. Então, por que eles estão pedindo para substituir o hashCode () também?

Bem. Desde que você não use Coleções baseadas em "Hash" em sua classe definida pelo usuário, tudo bem. Mas em algum momento no futuro você poderá usar HashMapou, HashSetse não o fizer overridee "implementar corretamente" o hashCode () , essa coleção baseada em Hash não funcionará conforme o esperado.

Substituir apenas iguais (adição à resposta de @Lombo)

myMap.put(first,someValue)
myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?

Primeiro, o HashMap verifica se o hashCode de secondé o mesmo que first. Somente se os valores forem iguais, continuará a verificação da igualdade no mesmo bucket.

Mas aqui o hashCode é diferente para esses 2 objetos (porque eles têm diferentes endereços de memória - da implementação padrão). Por isso, nem se importará em verificar a igualdade.

Se você tiver um ponto de interrupção dentro do método equals () substituído, ele não entrará em ação se eles tiverem hashCodes diferentes. contains()verificações hashCode()e somente se forem iguais, chamaria seu equals()método.

Por que não podemos fazer com que o HashMap verifique a igualdade em todos os buckets? Portanto, não há necessidade de substituir hashCode () !!

Está faltando o ponto das coleções baseadas em Hash. Considere o seguinte :

Your hashCode() implementation : intObject%9.

A seguir estão as chaves armazenadas na forma de baldes.

Bucket 1 : 1,10,19,... (in thousands)
Bucket 2 : 2,20,29...
Bucket 3 : 3,21,30,...
...

Digamos que você queira saber se o mapa contém a chave 10. Deseja pesquisar todos os baldes? ou Deseja pesquisar apenas um balde?

Com base no hashCode, você identificaria que, se 10 estiver presente, ele deverá estar presente no balde 1. Portanto, somente o balde 1 será pesquisado !!


5
class A {
    int i;
    // Hashing Algorithm
    if even number return 0 else return 1
    // Equals Algorithm,
    if i = this.i return true else false
}
  • put ('key', 'value') calculará o valor do hash hashCode()para determinar o bucket e usa o equals()método para descobrir se o valor já está presente no bucket . Caso contrário, será adicionado, caso contrário, será substituído pelo valor atual
  • get ('key') usará hashCode()para encontrar a entrada (bloco) primeiro e equals()para encontrar o valor em Entry

se Ambos são substituídos,

Mapa < Um >

Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...

se igual não for substituído

Mapa < Um >

Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..

Se hashCode não for substituído

Mapa < Um >

Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...

Contrato igual de HashCode

  1. Duas chaves iguais de acordo com o método equal devem gerar o mesmo hashCode
  2. Duas chaves gerando o mesmo código hash não precisam ser iguais (no exemplo acima, todos os números pares geram o mesmo código hash)

4

Considere a coleção de bolas em um balde, todas na cor preta. Seu trabalho é colorir essas bolas da seguinte maneira e usá-las no jogo apropriado,

Para Tênis - Amarelo, Vermelho. Para Críquete - Branco

Agora o balde tem bolas em três cores amarelo, vermelho e branco. E que agora você fez a coloração. Só você sabe qual cor é para qual jogo.

Colorir as bolas - Hashing. Escolhendo a bola para o jogo - Igual a.

Se você fez a coloração e alguém escolhe a bola para críquete ou tênis, eles não se importam com a cor !!!


4

Eu estava olhando para a explicação "Se você substituir apenas o hashCode, quando o chamar myMap.put(first,someValue)primeiro, calcula o hashCode e o armazena em um determinado intervalo. Então, quando você o chama myMap.put(first,someOtherValue), deve substituir primeiro pelo segundo, conforme a Documentação do Mapa, porque são iguais (de acordo com nossa definição). " :

Acho segunda vez quando estamos adicionando, myMapentão deve ser o 'segundo' objeto comomyMap.put(second,someOtherValue)


4

1) O erro comum é mostrado no exemplo abaixo.

public class Car {

    private String color;

    public Car(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if(obj==null) return false;
        if (!(obj instanceof Car))
            return false;   
        if (obj == this)
            return true;
        return this.color.equals(((Car) obj).color);
    }

    public static void main(String[] args) {
        Car a1 = new Car("green");
        Car a2 = new Car("red");

        //hashMap stores Car type and its quantity
        HashMap<Car, Integer> m = new HashMap<Car, Integer>();
        m.put(a1, 10);
        m.put(a2, 20);
        System.out.println(m.get(new Car("green")));
    }
}

o carro verde não foi encontrado

2. Problema causado por hashCode ()

O problema é causado pelo método não substituído hashCode(). O contrato entre equals()e hashCode()é:

  1. Se dois objetos forem iguais, eles deverão ter o mesmo código de hash.
  2. Se dois objetos tiverem o mesmo código de hash, eles podem ou não ser iguais.

    public int hashCode(){  
      return this.color.hashCode(); 
    }

4

É útil ao usar objetos de valor . A seguir, um trecho do Repositório de Padrões de Portland :

Exemplos de objetos de valor são coisas como números, datas, dinheiro e seqüências de caracteres. Geralmente, eles são pequenos objetos que são usados ​​amplamente. Sua identidade é baseada em seu estado, e não na identidade do objeto. Dessa forma, você pode ter várias cópias do mesmo objeto de valor conceitual.

Para que eu possa ter várias cópias de um objeto que representa a data de 16 de janeiro de 1998. Qualquer uma dessas cópias será igual uma à outra. Para um objeto pequeno como esse, geralmente é mais fácil criar novos e movê-los ao invés de confiar em um único objeto para representar a data.

Um objeto de valor sempre deve substituir .equals () em Java (ou = em Smalltalk). (Lembre-se de substituir também .hashCode ().)


3

Suponha que você tenha a classe (A) que agrega duas outras (B) (C) e que você precisa armazenar instâncias de (A) dentro da hashtable. A implementação padrão apenas permite distinguir instâncias, mas não por (B) e (C). Portanto, duas instâncias de A podem ser iguais, mas o padrão não permite compará-las da maneira correta.


3

Os métodos equals e hashcode são definidos na classe de objeto. Por padrão, se o método equals retornar true, o sistema irá além e verificará o valor do código hash. Se o código de hash dos 2 objetos também for o mesmo, os objetos serão considerados iguais. Portanto, se você substituir apenas o método igual a, então mesmo que o método igual substituído indique que 2 objetos são iguais, o código de hash definido pelo sistema pode não indicar que os 2 objetos são iguais. Portanto, precisamos substituir o código de hash também.


Se o método equals retornar true, não há necessidade de verificar o código hash. Se dois objetos têm códigos de hash diferentes, no entanto, é possível considerá-los diferentes sem precisar chamar iguais. Além disso, o conhecimento de que nenhuma das coisas da lista tem um código de hash específico implica que nenhuma das coisas da lista pode corresponder a nenhum objeto com esse código de hash. Como um exemplo simples, se houver uma lista de objetos cujos códigos de hash são números pares e uma lista de objetos em que são números ímpares, nenhum objeto cujo código de hash seja um número par estará na segunda lista.
Supercat 28/07

Se alguém tivesse dois objetos X e Y cujos métodos "iguais" indicassem que correspondiam, mas o código hash de X fosse um número par e o código hash de Y fosse um número ímpar, uma coleção conforme descrita acima indicava que o código hash do objeto Y era ímpar e armazenado na segunda lista, não seria possível encontrar uma correspondência para o objeto X. Observaria que o código hash de X era par e, como a segunda lista não possui objetos com códigos hash de número par, isso não incomodaria. procurar ali algo que corresponda a X, mesmo que Y corresponda a X. O que você deve dizer ...
supercat

... seria que muitas coleções evitam comparar coisas cujos códigos de hash implicam que eles não podem ser iguais. Dado dois objetos cujos códigos de hash são desconhecidos, geralmente é mais rápido compará-los diretamente do que computar seus códigos de hash, portanto, não há garantia de que coisas que relatam códigos de hash desiguais, mas retornem trueparaequals não ser considerado como correspondente. Por outro lado, se as coleções acontecerem, observe que as coisas não podem ter o mesmo código de hash, é provável que elas não notem que são iguais.
Supercat 28/07

3

Métodos iguais e Hashcode em Java

Eles são métodos da classe java.lang.Object, que é a superclasse de todas as classes (classes personalizadas também e outras definidas na API java).

Implementação:

públicos booleanos iguais (Object obj)

public int hashCode ()

insira a descrição da imagem aqui

públicos booleanos iguais (Object obj)

Este método simplesmente verifica se dois objetos referenciam x e y se referem ao mesmo objeto. ou seja, verifica se x == y.

É reflexivo: para qualquer valor de referência x, x.equals (x) deve retornar true.

É simétrico: para qualquer valor de referência xey, x.equals (y) deve retornar true se e somente se y.equals (x) retornar true.

É transitivo: para quaisquer valores de referência x, ye z, se x.equals (y) retornar true e y.equals (z) retornar true, x.equals (z) deverá retornar true.

É consistente: para quaisquer valores de referência xey, várias invocações de x.equals (y) retornam consistentemente true ou retornam false consistentemente, desde que nenhuma informação usada em comparações iguais no objeto seja modificada.

Para qualquer valor de referência não nulo x, x.equals (nulo) deve retornar false.

public int hashCode ()

Este método retorna o valor do código de hash para o objeto no qual esse método é chamado. Este método retorna o valor do código de hash como um número inteiro e é suportado para o benefício de classes de coleção baseadas em hash, como Hashtable, HashMap, HashSet etc. Este método deve ser substituído em todas as classes que substituem o método equals.

O contrato geral do hashCode é:

Sempre que é invocado no mesmo objeto mais de uma vez durante a execução de um aplicativo Java, o método hashCode deve retornar consistentemente o mesmo número inteiro, desde que nenhuma informação usada em comparações iguais no objeto seja modificada.

Esse número inteiro não precisa permanecer consistente de uma execução de um aplicativo para outra execução do mesmo aplicativo.

Se dois objetos forem iguais de acordo com o método equals (Object), a chamada do método hashCode em cada um dos dois objetos deverá produzir o mesmo resultado inteiro.

Não é necessário que, se dois objetos forem desiguais, de acordo com o método equals (java.lang.Object), a chamada do método hashCode em cada um dos dois objetos produza resultados inteiros distintos. No entanto, o programador deve estar ciente de que a produção de resultados inteiros distintos para objetos desiguais pode melhorar o desempenho das hashtables.

Objetos iguais devem produzir o mesmo código de hash, desde que iguais, porém objetos desiguais não precisam produzir códigos de hash distintos.

Recursos:

JavaRanch

Cenário


A imagem (link do vídeo) está no modo privado. Torne público para assistir.
UdayKiran Pulipati

2

No exemplo abaixo, se você comentar a substituição de iguais ou código de hash na classe Person, esse código falhará ao procurar a ordem de Tom. O uso da implementação padrão do hashcode pode causar falhas nas pesquisas de hashtable.

O que tenho abaixo é um código simplificado que puxa a ordem das pessoas por Pessoa. A pessoa está sendo usada como uma chave na hashtable.

public class Person {
    String name;
    int age;
    String socialSecurityNumber;

    public Person(String name, int age, String socialSecurityNumber) {
        this.name = name;
        this.age = age;
        this.socialSecurityNumber = socialSecurityNumber;
    }

    @Override
    public boolean equals(Object p) {
        //Person is same if social security number is same

        if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
            return true;
        } else {
            return false;
        }

    }

    @Override
    public int hashCode() {        //I am using a hashing function in String.java instead of writing my own.
        return socialSecurityNumber.hashCode();
    }
}


public class Order {
    String[]  items;

    public void insertOrder(String[]  items)
    {
        this.items=items;
    }

}



import java.util.Hashtable;

public class Main {

    public static void main(String[] args) {

       Person p1=new Person("Tom",32,"548-56-4412");
        Person p2=new Person("Jerry",60,"456-74-4125");
        Person p3=new Person("Sherry",38,"418-55-1235");

        Order order1=new Order();
        order1.insertOrder(new String[]{"mouse","car charger"});

        Order order2=new Order();
        order2.insertOrder(new String[]{"Multi vitamin"});

        Order order3=new Order();
        order3.insertOrder(new String[]{"handbag", "iPod"});

        Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
        hashtable.put(p1,order1);
        hashtable.put(p2,order2);
        hashtable.put(p3,order3);

       //The line below will fail if Person class does not override hashCode()
       Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
        for(String item:tomOrder.items)
        {
            System.out.println(item);
        }
    }
}

2

A classe String e as classes wrapper têm implementação equals()e hashCode()métodos diferentes da classe Object. O método equals () da classe Object compara as referências dos objetos, não o conteúdo. O método hashCode () da classe Object retorna um código de hash distinto para cada objeto, independentemente de o conteúdo ser o mesmo.

Isso gera problemas ao usar a coleção de mapas e a chave é do tipo Persistente, StringBuffer / tipo de construtor. Como eles não substituem equals () e hashCode (), diferentemente da classe String, equals () retornará false quando você comparar dois objetos diferentes, embora ambos tenham o mesmo conteúdo. Isso fará com que o hashMap armazene as mesmas chaves de conteúdo. Armazenar as mesmas chaves de conteúdo significa que ele está violando a regra do Mapa, porque o Mapa não permite chaves duplicadas. Portanto, você substitui os métodos equals () e hashCode () em sua classe e fornece a implementação (o IDE pode gerar esses métodos) para que funcionem da mesma forma que os equals () e hashCode () da String e evitam as mesmas chaves de conteúdo.

Você deve substituir o método hashCode () junto com equals () porque equals () funciona de acordo com o código hash.

Além disso, a substituição do método hashCode () junto com equals () ajuda a intactar o contrato equals () - hashCode (): "Se dois objetos são iguais, eles devem ter o mesmo código de hash."

Quando você precisa escrever uma implementação personalizada para hashCode ()?

Como sabemos, o trabalho interno do HashMap é baseado no princípio do Hashing. Existem certos depósitos nos quais os conjuntos de entradas são armazenados. Você personaliza a implementação hashCode () de acordo com seus requisitos para que os mesmos objetos de categoria possam ser armazenados no mesmo índice. Quando você armazena os valores na coleção de mapas usando o put(k,v)método, a implementação interna de put () é:

put(k, v){
hash(k);
index=hash & (n-1);
}

Significa que ele gera índice e o índice é gerado com base no código de hash de um objeto-chave específico. Portanto, faça com que esse método gere código de hash de acordo com seus requisitos, porque os mesmos conjuntos de entradas de código de hash serão armazenados no mesmo intervalo ou índice.

É isso aí!


1

hashCode()O método é usado para obter um número inteiro exclusivo para um determinado objeto. Esse número inteiro é usado para determinar a localização do depósito, quando esse objeto precisa ser armazenado em alguns HashTable, HashMapcomo na estrutura de dados. Por padrão, o hashCode()método do objeto retorna e representação inteira do endereço da memória em que o objeto está armazenado.

O hashCode()método dos objetos é usado quando os inserimos em um HashTable, HashMapou HashSet. Mais sobre HashTablesa Wikipedia.org para referência.

Para inserir qualquer entrada na estrutura de dados do mapa, precisamos de chave e valor. Se a chave e os valores forem tipos de dados definidos pelo usuário, hashCode()a chave determinará onde armazenar o objeto internamente. Quando também é necessário procurar o objeto no mapa, o código de hash da chave determinará onde procurar o objeto.

O código de hash aponta apenas para uma determinada "área" (ou lista, bloco, etc) internamente. Como objetos de chave diferentes podem potencialmente ter o mesmo código de hash, o próprio código de hash não garante que a chave correta seja encontrada. Em HashTableseguida, itera essa área (todas as chaves com o mesmo código de hash) e usa o equals()método da chave para encontrar a chave correta. Depois que a chave correta é encontrada, o objeto armazenado para essa chave é retornado.

Portanto, como podemos ver, uma combinação dos métodos hashCode()e equals()é usada ao armazenar e procurar objetos em um HashTable.

NOTAS:

  1. Sempre use os mesmos atributos de um objeto para gerar hashCode()e equals()ambos. Como no nosso caso, usamos a identificação do funcionário.

  2. equals() deve ser consistente (se os objetos não forem modificados, ele deverá continuar retornando o mesmo valor).

  3. Sempre a.equals(b), então a.hashCode()deve ser o mesmo que b.hashCode().

  4. Se você substituir um, deverá substituir o outro.

http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html


hashCode()não é usado para retornar um número inteiro exclusivo para cada objeto. Isso é impossível. Você mesmo contradiz isso na segunda frase do quarto parágrafo.
Marquês de Lorne

@EJP, na maioria das vezes o hascode () retornará o interger exclusivo para dois objetos diferentes. Mas haverá chances de colidir hascode para dois objetos diferentes, esse conceito é chamado de Hashcode Collision . Por favor, consulte: tech.queryhome.com/96931/…
Paramesh Korrakuti

1

IMHO, é como diz a regra - Se dois objetos são iguais, então eles devem ter o mesmo hash, ou seja, objetos iguais devem produzir valores de hash iguais.

Dado acima, o padrão equals () em Object é ==, que faz comparação no endereço, hashCode () retorna o endereço em número inteiro (hash no endereço real), que é novamente distinto para Objeto distinto.

Se você precisar usar os objetos personalizados nas coleções baseadas em Hash, precisará substituir equals () e hashCode (), por exemplo, se eu quiser manter o HashSet dos objetos Employee, se eu não usar hashCode mais forte e igual a Posso acabar substituindo os dois objetos de funcionário diferentes, isso acontece quando uso a idade como o hashCode (), mas devo usar o valor exclusivo que pode ser o ID do funcionário.


1

Para ajudá-lo a verificar objetos duplicados, precisamos de um igual personalizado e hashCode.

Como o código hash sempre retorna um número, é sempre rápido recuperar um objeto usando um número em vez de uma chave alfabética. Como vai fazer? Suponha que criamos um novo objeto passando algum valor que já está disponível em outro objeto. Agora, o novo objeto retornará o mesmo valor de hash de outro objeto, porque o valor passado é o mesmo. Depois que o mesmo valor de hash é retornado, a JVM sempre acessa o mesmo endereço de memória e, se houver mais de um objeto presente para o mesmo valor de hash, ele usará o método equals () para identificar o objeto correto.


1

Quando você deseja armazenar e recuperar seu objeto personalizado como uma chave no Mapa, sempre substitua igual e hashCode no seu Objeto personalizado. Por exemplo:

Person p1 = new Person("A",23);
Person p2 = new Person("A",23);
HashMap map = new HashMap();
map.put(p1,"value 1");
map.put(p2,"value 2");

Aqui, p1 e p2 considerarão apenas um objeto e o maptamanho será apenas 1, porque são iguais.


1
public class Employee {

    private int empId;
    private String empName;

    public Employee(int empId, String empName) {
        super();
        this.empId = empId;
        this.empName = empName;
    }

    public int getEmpId() {
        return empId;
    }

    public void setEmpId(int empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    @Override
    public String toString() {
        return "Employee [empId=" + empId + ", empName=" + empName + "]";
    }

    @Override
    public int hashCode() {
        return empId + empName.hashCode();
    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj) {
            return true;
        }
        if (!(this instanceof Employee)) {
            return false;
        }
        Employee emp = (Employee) obj;
        return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
    }

}

Classe de teste

public class Test {

    public static void main(String[] args) {
        Employee emp1 = new Employee(101,"Manash");
        Employee emp2 = new Employee(101,"Manash");
        Employee emp3 = new Employee(103,"Ranjan");
        System.out.println(emp1.hashCode());
        System.out.println(emp2.hashCode());
        System.out.println(emp1.equals(emp2));
        System.out.println(emp1.equals(emp3));
    }

}

Em Object Class igual (Object obj) é usado para comparar a comparação de endereços; por isso, quando na classe Test, se você comparar dois objetos, então é igual a método false, mas quando substituímos o código hash (), ele pode comparar o conteúdo e fornecer o resultado adequado.


e Classe de teste que adicionei no programa abaixo.
Manash Ranjan Dakua

Em Object Class igual (Object obj) é usado para comparar a comparação de endereços; por isso, quando na classe Test, se você comparar dois objetos, então é igual a método false, mas quando substituímos o código hash (), ele pode comparar o conteúdo e fornecer o resultado adequado.
Manash Ranjan Dakua

11
você pode usar o link editar logo abaixo esta resposta para adicionar à sua resposta .. Por favor, não adicionar uma resposta como dois mais incompletos
Suraj Rao

1

Se você substituir equals()e não hashcode(), não encontrará nenhum problema, a menos que você ou outra pessoa use esse tipo de classe em uma coleção de hash como HashSet. As pessoas antes de mim explicaram claramente a teoria documentada várias vezes; estou aqui apenas para fornecer um exemplo muito simples.

Considere uma classe cuja equals()necessidade de significar algo personalizado:

    public class Rishav {

        private String rshv;

        public Rishav(String rshv) {
            this.rshv = rshv;
        }

        /**
        * @return the rshv
        */
        public String getRshv() {
            return rshv;
        }

        /**
        * @param rshv the rshv to set
        */
        public void setRshv(String rshv) {
            this.rshv = rshv;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Rishav) {
                obj = (Rishav) obj;
                if (this.rshv.equals(((Rishav) obj).getRshv())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return rshv.hashCode();
        }

    }

Agora considere esta classe principal: -

    import java.util.HashSet;
    import java.util.Set;

    public class TestRishav {

        public static void main(String[] args) {
            Rishav rA = new Rishav("rishav");
            Rishav rB = new Rishav("rishav");
            System.out.println(rA.equals(rB));
            System.out.println("-----------------------------------");

            Set<Rishav> hashed = new HashSet<>();
            hashed.add(rA);
            System.out.println(hashed.contains(rB));
            System.out.println("-----------------------------------");

            hashed.add(rB);
            System.out.println(hashed.size());
        }

    }

Isso produzirá a seguinte saída: -

    true
    -----------------------------------
    true
    -----------------------------------
    1

Eu estou feliz com os resultados. Mas se eu não tiver substituído hashCode(), ele causará pesadelo, pois objetos Rishavcom o mesmo conteúdo de membro não serão mais tratados como únicos, pois hashCodeserão diferentes, conforme gerado pelo comportamento padrão, eis a saída:

    true
    -----------------------------------
    false
    -----------------------------------
    2

0

Ambos os métodos são definidos na classe Object. E ambos estão em sua implementação mais simples. Portanto, quando você precisar, adicione mais implementação a esses métodos, substituindo sua classe.

Para o método Ex: equals () no objeto, apenas verifica sua igualdade na referência. Portanto, se você precisar comparar seu estado também, poderá substituí-lo, como é feito na classe String.


-3

Bah - "Você deve substituir hashCode () em todas as classes que substituem igual a ()."

[do Effective Java, de Joshua Bloch?]

Não é esse o caminho errado? A substituição do hashCode provavelmente implica que você está escrevendo uma classe de chave de hash, mas a substituição de igual certamente não. Existem muitas classes que não são usadas como chaves de hash, mas desejam um método de teste de igualdade lógica por algum outro motivo. Se você escolher "iguais" para ele, poderá ser solicitado a escrever uma implementação de hashCode pela aplicação excessiva de zelo desta regra. Tudo o que consegue é adicionar código não testado na base de código, um mal esperando para enganar alguém no futuro. Também escrever código que você não precisa é anti-ágil. Está errado (e um ide gerado provavelmente será incompatível com seus iguais criados à mão).

Certamente eles deveriam ter exigido uma interface em objetos escritos para serem usados ​​como chaves? Independentemente disso, o Object nunca deveria ter fornecido o padrão hashCode () e igual a () imho. Provavelmente, incentivou muitas coleções de hash quebradas.

De qualquer forma, acho que a "regra" está escrita de trás para frente. Enquanto isso, continuarei evitando usar "iguais" para os métodos de teste de igualdade :-(

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.