Alguma razão para preferir getClass () a instanceof ao gerar .equals ()?

174

Estou usando o Eclipse para gerar .equals()e .hashCode(), e há uma opção chamada "Use 'instanceof' para comparar tipos". O padrão é que essa opção seja desmarcada e usada .getClass()para comparar tipos. Existe alguma razão eu preferiria .getClass()mais instanceof?

Sem usar instanceof:

if (obj == null)
  return false;
if (getClass() != obj.getClass())
  return false;

Usando instanceof:

if (obj == null)
  return false;
if (!(obj instanceof MyClass))
  return false;

Costumo marcar a instanceofopção e, em seguida, removo a if (obj == null)verificação. (É redundante, pois os objetos nulos sempre falham instanceof.) Existe algum motivo para que seja uma má idéia?

Kip
fonte
1
A expressão x instanceof SomeClassé falsa se xfor null. Portanto, a segunda sintaxe não precisa da verificação nula.
Rds
5
@rds Sim, o parágrafo imediatamente após o snippet de código também diz isso. Está no trecho de código, porque é isso que o Eclipse gera.
Kip

Respostas:

100

Se você usar instanceof, tornando a sua equalsimplementação finalirá preservar o contrato simetria do método: x.equals(y) == y.equals(x). Se finalparecer restritivo, examine cuidadosamente sua noção de equivalência de objetos para garantir que suas implementações principais mantenham completamente o contrato estabelecido pela Objectclasse.

erickson
fonte
exatamente isso me veio à mente quando li a citação de josh bloch acima. +1 :)
Johannes Schaub - litb 27/02
13
definitivamente a decisão chave. simetria deve aplicar, e instanceof faz com que seja muito fácil de ser assimétrica acidentalmente
Scott Stanchfield
Eu também acrescentaria que "se finalparece restritivo" (em ambos equalse hashCode), use getClass()igualdade em vez de instanceof, a fim de preservar os requisitos de simetria e transitividade do equalscontrato.
Shadow Man
176

Josh Bloch favorece sua abordagem:

A razão de eu favorecer a instanceofabordagem é que, quando você usa a getClassabordagem, você tem a restrição de que os objetos são iguais apenas a outros da mesma classe, do mesmo tipo de tempo de execução. Se você estender uma classe e adicionar alguns métodos inócuos a ela, verifique se algum objeto da subclasse é igual a um objeto da superclasse, mesmo que os objetos sejam iguais em todos os aspectos importantes, você obterá o resposta surpreendente de que eles não são iguais. De fato, isso viola uma interpretação estrita do princípio de substituição de Liskov e pode levar a um comportamento muito surpreendente. Em Java, é particularmente importante porque a maioria das coleções (HashTable, etc.) baseiam-se no método dos iguais. Se você colocar um membro da superclasse em uma tabela de hash como chave e procurá-lo usando uma instância de subclasse, não o encontrará, porque eles não são iguais.

Veja também esta resposta SO .

O capítulo 3 efetivo sobre Java também cobre isso.

Michael Myers
fonte
18
+1 Todo mundo que faz java deve ler esse livro pelo menos 10 vezes!
286 André André
16
O problema com a abordagem instanceof é que ele quebra a propriedade "simétrica" ​​de Object.equals (), na medida em que é possível que x.equals (y) == true mas y.equals (x) == false se x.getClass ()! = y.getClass (). Prefiro não quebrar essa propriedade, a menos que seja absolutamente necessário (ou seja, substituindo equals () para objetos gerenciados ou proxy).
Kevin Sitze
8
A instância da abordagem é adequada quando, e somente quando, a classe base define o que igualdade entre objetos de subclasse deve significar. O uso getClassnão viola o LSP, uma vez que o LSP se refere apenas ao que pode ser feito com instâncias existentes - e não a que tipos de instâncias podem ser construídos. A classe retornada por getClassé uma propriedade imutável de uma instância do objeto. O LSP não implica que seja possível criar uma subclasse em que essa propriedade indique qualquer classe que não seja a que o criou.
supercat
66

Angelika Langers Segredos dos iguais entra nisso com uma discussão longa e detalhada de alguns exemplos comuns e conhecidos, incluindo Josh Bloch e Barbara Liskov, descobrindo alguns problemas na maioria deles. Ela também entra no instanceofvs getClass. Algumas citações dele

Conclusões

Tendo dissecado os quatro exemplos arbitrariamente escolhidos de implementações de igual a (), o que concluímos?

Primeiro de tudo: existem duas maneiras substancialmente diferentes de executar a verificação de correspondência de tipo em uma implementação de igual a (). Uma classe pode permitir a comparação de tipos mistos entre objetos de super e subclasse por meio do operador instanceof, ou uma classe pode tratar objetos de tipos diferentes como diferentes por meio do teste getClass (). Os exemplos acima ilustraram muito bem que implementações de equals () usando getClass () são geralmente mais robustas do que aquelas implementações usando instanceof.

A instância do teste está correta apenas para as classes finais ou se pelo menos o método equals () for final em uma superclasse. O último implica essencialmente que nenhuma subclasse deve estender o estado da superclasse, mas só pode adicionar funcionalidades ou campos irrelevantes para o estado e o comportamento do objeto, como campos transitórios ou estáticos.

As implementações usando o teste getClass (), por outro lado, sempre cumprem o contrato equals (); eles estão corretos e robustos. Eles são, no entanto, semanticamente muito diferentes das implementações que usam a instância do teste. Implementações usando getClass () não permitem a comparação de objetos de subclasse com superclasse, nem mesmo quando a subclasse não adiciona nenhum campo e nem deseja substituir igual a (). Uma extensão de classe "trivial", por exemplo, seria a adição de um método de depuração-impressão em uma subclasse definida exatamente para esse propósito "trivial". Se a superclasse proibir a comparação de tipos mistos por meio da verificação getClass (), a extensão trivial não seria comparável à sua superclasse. Se isso é ou não um problema, depende totalmente da semântica da classe e do objetivo da extensão.

Johannes Schaub - litb
fonte
61

O motivo para usar getClassé garantir a propriedade simétrica do equalscontrato. Dos JavaDocs da equals:

É simétrico: para quaisquer valores de referência não nulos x e y, x.equals (y) deve retornar true se e somente se y.equals (x) retornar true.

Usando instanceof, é possível não ser simétrico. Considere o exemplo: Cão estende Animal. Animal equalsfaz uma instanceofverificação de Animal. Dog's equalsfaz uma instanceofverificação de Dog. Dê Animal a e Cão d (com outros campos iguais):

a.equals(d) --> true
d.equals(a) --> false

Isso viola a propriedade simétrica.

Para seguir rigorosamente o contrato da equal, a simetria deve ser garantida e, portanto, a classe precisa ser a mesma.

Steve Kuo
fonte
8
Essa é a primeira resposta clara e concisa para esta pergunta. Um exemplo de código vale mais que mil palavras.
Johnride 28/08/14
Eu estava passando por todas as outras respostas detalhadas que você salvou no dia. Simples, conciso, elegante e totalmente a melhor resposta para essa pergunta.
1
Existe o requisito de simetria, como você mencionou. Mas há também o requisito de "transitividade". Se a.equals(c)e b.equals(c), em seguida, a.equals(b)(a abordagem ingênua de fazer Dog.equalsapenas return super.equals(object)quando !(object instanceof Dog), mas a verificação dos campos extras quando é um exemplo do cão não violaria simetria mas violaria transitividade)
Shadow Man
Para estar seguro, você pode usar uma getClass()verificação de igualdade ou uma instanceofverificação se criar seus métodos equalse . hashCodefinal
Shadow Man
26

Isso é um debate religioso. Ambas as abordagens têm seus problemas.

  • Use instanceof e você nunca pode adicionar membros significativos às subclasses.
  • Use getClass e você viola o princípio de substituição de Liskov.

Bloch tem outro conselho relevante no Effective Java Second Edition :

  • Item 17: Design e documento da herança ou proibi-la
McDowell
fonte
1
Nada sobre o uso getClassviolaria o LSP, a menos que a classe base documentasse especificamente um meio pelo qual seria possível criar instâncias de subclasses diferentes que se comparam da mesma forma. Que violação de LSP você vê?
Supercat
Talvez isso deva ser reformulado como Use getClass e você deixe subtipos em risco de violações do LSP. Bloch tem um exemplo em Java eficaz - também discutido aqui . Sua lógica é em grande parte a de que isso pode resultar em um comportamento surpreendente para desenvolvedores que implementam subtipos que não adicionam estado. Entendo seu argumento - a documentação é fundamental.
McDowell
2
O único momento em que duas instâncias de classes distintas devem se reportar é igual é se herdarem de uma classe ou interface base comum que define o que significa igualdade [uma filosofia que Java aplicou, duvidosamente IMHO, a algumas interfaces de coleção]. A menos que o contrato da classe base indique que getClass()não deve ser considerado significativo além do fato de que a classe em questão é conversível na classe base, o retorno de getClass()deve ser considerado como qualquer outra propriedade que deve corresponder para que as instâncias sejam iguais.
supercat
1
@eyalzba Onde instanceof_ é usado se uma subclasse adicionar membros ao contrato igual, isso violaria o requisito de igualdade simétrica . O uso de subclasses getClass nunca pode ser igual ao tipo pai. Não que você deva substituir os iguais de qualquer maneira, mas esse é o compromisso se você o fizer.
perfil
2
"Projetar e documentar a herança ou proibi-la" Eu acho muito importante. Meu entendimento é que a única vez que você deve substituir iguais é se você estiver criando um tipo de valor imutável; nesse caso, a classe deve ser declarada final. Como não haverá herança, você pode implementar iguais conforme necessário. Nos casos em que você está permitindo a herança, os objetos devem ser comparados por igualdade de referência.
TheSecretSquad
23

Corrija-me se eu estiver errado, mas getClass () será útil quando você quiser garantir que sua instância NÃO seja uma subclasse da classe com a qual você está comparando. Se você usar instanceof nessa situação, NÃO poderá saber porque:

class A { }

class B extends A { }

Object oA = new A();
Object oB = new B();

oA instanceof A => true
oA instanceof B => false
oB instanceof A => true // <================ HERE
oB instanceof B => true

oA.getClass().equals(A.class) => true
oA.getClass().equals(B.class) => false
oB.getClass().equals(A.class) => false // <===============HERE
oB.getClass().equals(B.class) => true
TraderJoeChicago
fonte
Para mim, esta é a razão #
karlihnos
5

Se você quiser garantir que apenas essa classe corresponda, use getClass() ==. Se você deseja combinar subclasses, instanceofé necessário.

Além disso, instanceof não corresponderá a um nulo, mas é seguro comparar com um nulo. Portanto, você não precisa anulá-lo.

if ( ! (obj instanceof MyClass) ) { return false; }
Clint
fonte
Re seu segundo ponto: ele já mencionou isso na pergunta. "Normalmente, eu checo a opção instanceof e, em seguida, removo a verificação 'if (obj == null)'."
Michael Myers
5

Depende se você considerar se uma subclasse de uma determinada classe é igual ao seu pai.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

aqui eu usaria 'instanceof', porque eu quero que um sobrenome seja comparado ao nome da família

class Organism
{
}

class Gorilla extends Organism
{
}

aqui eu usaria 'getClass', porque a classe já diz que as duas instâncias não são equivalentes.

Pierre
fonte
3

instanceof funciona para instâncias da mesma classe ou de suas subclasses

Você pode usá-lo para testar se um objeto é uma instância de uma classe, uma instância de uma subclasse ou uma instância de uma classe que implementa uma interface específica.

ArryaList e RoleList são ambos instanceof List

Enquanto

getClass () == o.getClass () será verdadeiro somente se os dois objetos (this e o) pertencerem exatamente à mesma classe.

Portanto, dependendo do que você precisa comparar, você pode usar um ou outro.

Se sua lógica é: "Um objeto é igual a outro somente se ambos são da mesma classe", você deve procurar o "igual", que eu acho que é a maioria dos casos.

OscarRyz
fonte
3

Ambos os métodos têm seus problemas.

Se a subclasse alterar a identidade, será necessário comparar as classes reais. Caso contrário, você violará a propriedade simétrica. Por exemplo, diferentes tipos dePerson s não devem ser considerados equivalentes, mesmo que tenham o mesmo nome.

No entanto, algumas subclasses não alteram a identidade e precisam ser usadas instanceof. Por exemplo, se tivermos um monte de Shapeobjetos imutáveis , então um Rectanglecom comprimento e largura de 1 deve ser igual à unidade Square.

Na prática, acho que é mais provável que o caso anterior seja verdadeiro. Geralmente, a subclasse é uma parte fundamental da sua identidade e ser exatamente como seus pais, exceto que você pode fazer uma coisinha não faz com que você seja igual.

James
fonte
-1

Na verdade, instanceof verifica onde um objeto pertence a alguma hierarquia ou não. ex: o objeto Carro pertence à classe Vehical. Portanto, a "nova instância Car () do Vehical" retorna verdadeira. E "new Car (). GetClass (). Equals (Vehical.class)" retorna false, embora o objeto Car pertença à classe Vehical, mas seja classificado como um tipo separado.

Akash5288
fonte