Como substituir o método equals em Java

108

Estou tentando substituir o método equals em Java. Eu tenho uma classe Peopleque tem basicamente 2 campos de dados namee age. Agora eu quero substituir o equalsmétodo para que eu possa verificar entre 2 objetos Pessoas.

Meu código é o seguinte

public boolean equals(People other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(other.name) &&  age.equals(other.age);
    } // end else

    return result;
} // end equals

Mas quando escrevo age.equals(other.age), me dá um erro, pois o método de igualdade só pode comparar String e idade é Inteiro.

Solução

Usei ==operador conforme sugerido e meu problema está resolvido.

Castor
fonte
3
Ei, que tal this.age == other.age? :)
denis.solonenko
1
Qual é o tipo de dados para idade? int OR Inteiro? Além disso, qual versão do JDK você está usando?
Manish
2
"o método igual só pode comparar String" - Quem disse que o método igual só pode comparar String? O método equals pertence à classe Object e qualquer classe criada terá implementação equals por padrão. Você pode chamar equals em QUALQUER classe Java
Manish

Respostas:

127
//Written by K@stackoverflow
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        ArrayList<Person> people = new ArrayList<Person>();
        people.add(new Person("Subash Adhikari", 28));
        people.add(new Person("K", 28));
        people.add(new Person("StackOverflow", 4));
        people.add(new Person("Subash Adhikari", 28));

        for (int i = 0; i < people.size() - 1; i++) {
            for (int y = i + 1; y <= people.size() - 1; y++) {
                boolean check = people.get(i).equals(people.get(y));

                System.out.println("-- " + people.get(i).getName() + " - VS - " + people.get(y).getName());
                System.out.println(check);
            }
        }
    }
}

//written by K@stackoverflow
    public class Person {
        private String name;
        private int age;

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

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            
if (obj.getClass() != this.getClass()) {
                return false;
            }



            final Person other = (Person) obj;
            if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
                return false;
            }

            if (this.age != other.age) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
            hash = 53 * hash + this.age;
            return hash;
        }

        public int getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

Resultado:

corre:

- Subash Adhikari - VS - K falso

- Subash Adhikari - VS - StackOverflow falso

- Subash Adhikari - VS - Subash Adhikari verdadeiro

- K - VS - StackOverflow falso

- K - VS - Subash Adhikari falso

- StackOverflow - VS - Subash Adhikari falso

- CONSTRUIR COM SUCESSO (tempo total: 0 segundos)

Kim
fonte
7
o que é hash = 53 * hashpor isso que você está usando isso?
gatinho
2
O uso getClass()causará problemas se a classe tiver uma subclasse e for comparada com um objeto da superclasse.
Tuxdude de
1
pode ser bcoz 53 é um número primo , dê uma olhada nesta resposta stackoverflow.com/a/27609/3425489 , ele comentou ao escolher os números emhashCode()
Shantaram Tupe
1
A resposta vencedora para esta pergunta tem uma excelente explicação de por que você substitui hashCode () stackoverflow.com/a/27609/1992108
Pegasaurus
7
Considere usar if (getClass ()! = Obj.getClass ()) ... em vez de usar o instanceofoperador ou isAssignableFrom. Isso exigirá correspondência exata de tipo, em vez de correspondência de subtipo. - Requisito simétrico. Também para comparar Stringou outros tipos de objetos, você pode usar Objects.equals(this.name,other.name).
YoYo
22

A introdução de uma nova assinatura de método que altera os tipos de parâmetro é chamada de sobrecarga :

public boolean equals(People other){

Aqui Peopleé diferente de Object.

Quando a assinatura de um método permanece idêntica à de sua superclasse, ela é chamada de substituição e a @Overrideanotação ajuda a distinguir as duas em tempo de compilação:

@Override
public boolean equals(Object other){

Sem ver a declaração real de age, é difícil dizer por que o erro aparece.

Fortran
fonte
18

Não tenho certeza dos detalhes, pois você não postou o código inteiro, mas:

  • lembre-se de substituir hashCode()também
  • o equalsmétodo deveria ter Object, não Peoplecomo seu tipo de argumento. No momento você está sobrecarregando, não substituindo, o método equals, que provavelmente não é o que você deseja, especialmente considerando que você verificará seu tipo posteriormente.
  • você pode usar instanceofpara verificar se é um objeto Pessoas, por exemploif (!(other instanceof People)) { result = false;}
  • equalsé usado para todos os objetos, mas não primitivos. Acho que você quer dizer que idade é um int(primitivo), caso em que apenas use ==. Observe que um inteiro (com 'I' maiúsculo) é um objeto que deve ser comparado com iguais.

Consulte Quais problemas devem ser considerados ao substituir equals e hashCode em Java? para mais detalhes.

Adrian Mouat
fonte
12
@Override
public boolean equals(Object that){
  if(this == that) return true;//if both of them points the same address in memory

  if(!(that instanceof People)) return false; // if "that" is not a People or a childclass

  People thatPeople = (People)that; // than we can cast it to People safely

  return this.name.equals(thatPeople.name) && this.age == thatPeople.age;// if they have the same name and same age, then the 2 objects are equal unless they're pointing to different memory adresses
}
NeverJr
fonte
12

Item 10: Obedeça o contrato geral ao substituir iguais

De acordo com o Effective Java , substituir o equalsmétodo parece simples, mas há muitas maneiras de errar e as consequências podem ser terríveis. A maneira mais fácil de evitar problemas é não sobrescrever o equalsmétodo, caso em que cada instância da classe é igual apenas a si mesma. Esta é a coisa certa a fazer se qualquer uma das seguintes condições se aplicar:

  • Cada instância da classe é inerentemente única . Isso é verdadeiro para classes como Thread, que representam entidades ativas em vez de valores. A implementação equals fornecida por Object tem exatamente o comportamento correto para essas classes.

  • Não há necessidade de a classe fornecer um teste de “igualdade lógica”. Por exemplo, java.util.regex.Pattern poderia ter substituído equals para verificar se duas instâncias de Pattern representavam exatamente a mesma expressão regular, mas os designers não pensaram que os clientes precisariam ou desejariam essa funcionalidade. Nessas circunstâncias, a implementação igual herdada de Object é ideal.

  • Uma superclasse já substituiu equals e o comportamento da superclasse é apropriado para esta classe. Por exemplo, a maioria das implementações de Set herdam suas implementações iguais de AbstractSet, implementações de List de AbstractList e implementações de Map de AbstractMap.

  • A classe é privada ou privada do pacote e você tem certeza de que seu método equals nunca será chamado. Se você for extremamente avesso a riscos, poderá substituir o método equals para garantir que ele não seja invocado acidentalmente:

O equalsmétodo implementa uma relação de equivalência. Possui estas propriedades:

  • Reflexivo: para qualquer valor de referência não nulo x, x.equals(x)deve retornar verdadeiro.

  • Simétrico: para quaisquer valores de referência não nulos xe y, x.equals(y)deve retornar verdadeiro se e somente se y.equals (x) retornar verdadeiro.

  • Transitivo: Para todos os valores de referência não nulos x, y, z, se x.equals(y)os retornos truee y.equals(z)retornos true, em seguida, x.equals(z)deve retornar true.

  • Consistente: Para quaisquer valores de referência não nulos xe y, várias invocações de x.equals(y)devem retornar trueou retornar consistentemente false, desde que nenhuma informação usada nas comparações de igual seja modificada.

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

Aqui está uma receita para um método de igualdade de alta qualidade:

  1. Use o ==operador para verificar se o argumento é uma referência a este objeto. Se sim, retorne verdadeiro. Esta é apenas uma otimização de desempenho, mas vale a pena se a comparação for potencialmente cara.

  2. Use o instanceofoperador para verificar se o argumento tem o tipo correto. Caso contrário, retorna falso. Normalmente, o tipo correto é a classe em que o método ocorre. Ocasionalmente, é alguma interface implementada por esta classe. Use uma interface se a classe implementar uma interface que refina o contrato de igual para permitir comparações entre as classes que implementam a interface. Interfaces de coleção como Set, List, Map e Map.Entry têm esta propriedade.

  3. Converta o argumento para o tipo correto. Como essa conversão foi precedida por um teste de instância, é garantido o sucesso.

  4. Para cada campo “significativo” na classe, verifique se aquele campo do argumento corresponde ao campo correspondente deste objeto. Se todos esses testes forem bem-sucedidos, retorna verdadeiro; caso contrário, retorna falso. Se o tipo na Etapa 2 for uma interface, você deve acessar os campos do argumento por meio de métodos de interface; se o tipo for uma classe, você poderá acessar os campos diretamente, dependendo de sua acessibilidade.

  5. Para campos primitivos cujo tipo não é floatou double, use o ==operador para comparações; para campos de referência de objeto, chame o equalsmétodo recursivamente; para floatcampos, use o Float.compare(float, float)método estático ; e para doublecampos, use Double.compare(double, double). O tratamento especial de flutuador e campos duplos é necessária devido à existência de Float.NaN, -0.0fe os valores duplos análogos; Embora você possa comparar os campos floate doublecom os métodos estáticos Float.equalse Double.equals, isso implicaria no autoboxing em todas as comparações, o que teria um desempenho ruim. Para arraycampos, aplique essas diretrizes a cada elemento. Se cada elemento em um campo de matriz for significativo, use um dos Arrays.equalsmétodos.

  6. Alguns campos de referência de objeto podem conter legitimamente null. Para evitar a possibilidade de a NullPointerException, verifique a igualdade desses campos usando o método estático Objects.equals(Object, Object).

    // Class with a typical equals method
    
    public final class PhoneNumber {
    
        private final short areaCode, prefix, lineNum;
    
        public PhoneNumber(int areaCode, int prefix, int lineNum) {
    
            this.areaCode = rangeCheck(areaCode,  999, "area code");
    
            this.prefix   = rangeCheck(prefix,    999, "prefix");
    
            this.lineNum  = rangeCheck(lineNum,  9999, "line num");
    
        }
    
        private static short rangeCheck(int val, int max, String arg) {
    
            if (val < 0 || val > max)
    
               throw new IllegalArgumentException(arg + ": " + val);
    
            return (short) val;
    
        }
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNum == lineNum && pn.prefix == prefix
                    && pn.areaCode == areaCode;
        }
        ... // Remainder omitted
    
    }

fonte
1
Não se esqueça de mencionar que você também deve substituir hashCode(). Também nota, que desde Java7 escrita equals()e hashCode()métodos tornou-se muito mais fácil usando Objects.equals(), Arrays.equals()e Objects.hashCode(), Arrays.hashCode().
Arnold Schrijver
3
Considere usar em if (getClass() != obj.getClass()) ...vez de usar o operador instanceof. Isso exigirá correspondência exata de tipo, em vez de correspondência de subtipo. - Requisito simétrico.
YoYo
@YoYo está correto ... usar instanceof pode causar falha na propriedade simétrica. Se o for uma subclasse de PhoneNumber como talvez PhoneNumberWithExtension, e se substituir igual ao da mesma maneira usando instanceof, então o.equals (this) falharia no teste instanceof enquanto PhoneNumber.equals passaria e retornaria verdadeiro (assumindo todos os outros campos PhoneNumber são iguais).
ldkronos
5

Já que estou supondo que ageseja do tipo int:

public boolean equals(Object other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(otherPeople.name) &&  age == otherPeople.age;
    } // end else

    return result;
} // end equals
Luchian Grigore
fonte
Isso resultará em um NullPointerExceptionif nameé null.
orien
@orien Não é grande coisa, talvez esteja no contrato que namenunca recebe um nullvalor atribuído ...
fortran
@fortran Então ... talvez não seja grande coisa;)
orien
5

Ao comparar objetos em Java, você faz uma verificação semântica , comparando o tipo e identificando o estado dos objetos para:

  • em si (mesma instância)
  • em si (clone ou cópia reconstruída)
  • outros objetos de diferentes tipos
  • outros objetos do mesmo tipo
  • null

Regras:

  • Simetria :a.equals(b) == b.equals(a)
  • equals()sempre cede trueou false, mas nunca um NullpointerException, ClassCastExceptionou qualquer outro lançável

Comparação:

  • Verificação de tipo : ambas as instâncias precisam ser do mesmo tipo, o que significa que você deve comparar as classes reais para igualdade. Isso geralmente não é implementado corretamente quando os desenvolvedores usam instanceofpara comparação de tipo (o que só funciona enquanto não houver subclasses e viola a regra de simetria quando A extends B -> a instanceof b != b instanceof a).
  • Verificação semântica do estado de identificação : certifique-se de entender por qual estado as instâncias são identificadas. As pessoas podem ser identificadas pelo número do seguro social, mas não pela cor do cabelo (pode ser tingido), nome (pode ser alterado) ou idade (muda o tempo todo). Apenas com objetos de valor você deve comparar o estado completo (todos os campos não transitórios), caso contrário, verifique apenas o que identifica a instância.

Para sua Personaula:

public boolean equals(Object obj) {

    // same instance
    if (obj == this) {
        return true;
    }
    // null
    if (obj == null) {
        return false;
    }
    // type
    if (!getClass().equals(obj.getClass())) {
        return false;
    }
    // cast and compare state
    Person other = (Person) obj;
    return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}

Classe de utilitário genérico reutilizável:

public final class Equals {

    private Equals() {
        // private constructor, no instances allowed
    }

    /**
     * Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
     *
     * @param instance       object instance (where the equals() is implemented)
     * @param other          other instance to compare to
     * @param stateAccessors stateAccessors for state to compare, optional
     * @param <T>            instance type
     * @return true when equals, false otherwise
     */
    public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
        if (instance == null) {
            return other == null;
        }
        if (instance == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (!instance.getClass().equals(other.getClass())) {
            return false;
        }
        if (stateAccessors == null) {
            return true;
        }
        return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
    }
}

Para sua Personclasse, usando esta classe de utilitário:

public boolean equals(Object obj) {
    return Equals.as(this, obj, t -> t.name, t -> t.age);
}
Peter Walser
fonte
1

se idade for int, você deve usar == se for um objeto Integer, então você pode usar equals (). Você também precisa implementar o método hashcode se substituir equals. Detalhes do contrato estão disponíveis no javadoc da Object e também em várias páginas na web.

Ashwinee K Jha
fonte
0

Aqui está a solução que usei recentemente:

public class Test {
    public String a;
    public long b;
    public Date c;
    public String d;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Test)) {
            return false;
        }
        Test testOther = (Test) obj;
        return (a != null ? a.equals(testOther.a) : testOther.a == null)
                && (b == testOther.b)
                && (c != null ? c.equals(testOther.c) : testOther.c == null)
                && (d != null ? d.equals(testOther.d) : testOther.d == null);
    }

}
SSharma
fonte