Pergunta

http://leepoint.net/notes-java/data/expressions /22compareobjects.html

Acontece que é igual a definição de () não é trivial; na verdade, é moderadamente difícil de acertar, especialmente em caso de subclasses. Ao melhor tratamento das questões está em de Horstmann Núcleo Java Vol 1.

Se iguais () deve ser sempre substituído, então o que é uma abordagem boa para não ser encurralado em ter de fazer comparação de objetos? Quais são algumas boas alternativas "design"?

EDIT:

Eu não estou certo de que este está vindo do outro lado da forma que eu pretendia. Talvez a pergunta deveria ser mais longo das linhas de "Por que você quer para comparar dois objetos?" Com base em sua resposta a essa pergunta, existe uma solução alternativa para comparação? Não quero dizer, uma implementação diferente de iguais. Quer dizer, não usando a igualdade em tudo. Acho que o ponto chave é começar com essa pergunta, por que você iria querer comparar dois objetos.

Foi útil?

Solução

Se iguais () deve ser sempre substituído, então o que é uma boa abordagem para não ser encurralado em ter que fazer comparação de objetos?

Você está enganado. Você deve substituir iguais o mais raramente possível.


toda esta informação vem de Effective Java, Second Edition ( Josh Bloch ). O primeiro capítulo edição on este ainda está disponível como um livre de download .

Desde Effective Java:

A maneira mais fácil de evitar problemas é não para substituir o método equals, em que caso cada instância da classe é igual apenas a si mesmo.

O problema com arbitrariamente substituir iguais / hashCode é herança. Alguns igual implementações defendem testá-lo como este:

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

Na verdade, o Eclipse (3.4) editor Java faz exatamente isso quando você gerar o método usando o ferramentas de código. De acordo com Bloch, isso é um erro, uma vez que viola a substituição de Liskov .

Desde Effective Java:

Não há nenhuma maneira de estender um classe instanciável e adicionar um valor enquanto componente preservar os iguais contrato.

Duas maneiras de minimizar os problemas de igualdade são descritos em classes e interfaces capítulo:

  1. composição Favor sobre a herança
  2. Desenho e documento de herança ou então proibi-la

Tanto quanto eu posso ver, a única alternativa é a igualdade de teste em uma forma externa para a classe, e como isso seria realizado dependerá do projeto do tipo e do contexto que você estava tentando usá-lo em.

Por exemplo, você pode definir uma interface que documentos como era para ser comparado. No código abaixo, casos de Serviços pode ser substituída em tempo de execução com uma versão mais recente da mesma classe - caso em que, tendo diferentes ClassLoaders, é igual comparações sempre retornar falso, então substituindo iguais / hashCode seria redundante

.
public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

"Por que você quer para comparar dois objetos?"

O exemplo óbvio é para testar se duas strings são iguais (ou dois Arquivos , ou URIs ). Por exemplo, se você queria construir um conjunto de arquivos para analisar. Por definição, o conjunto contém apenas elementos únicos. de Java Set tipo depende dos iguais / métodos hashCode para impor exclusividade dos seus elementos.

Outras dicas

Eu não acho que é verdade que iguais deve ser sempre substituído. A regra como eu entendo é que substituindo iguais só é significativo nos casos em que você está claro sobre como definir objetos semanticamente equivalentes. Nesse caso, você substituir hashCode (), bem assim que você não tem objetos que você definiu como equivalentes retornam hashcodes diferentes.

Se você não pode definir equivalência significativa, não vejo o benefício.

Como apenas cerca de fazê-lo direito?

Aqui está o meu modelo iguais, que é o conhecimento aplicado a partir de Effective Java por Josh Bloch. Leia o livro para mais detalhes:

@Override
public boolean equals(Object obj) {
    if(this == obj) {
        return true;
    }

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }

mmhh

Em alguns cenários, você pode fazer o objeto unmodifiable (somente leitura) e tê-lo criado a partir de um único ponto (um método de fábrica)

Se dois objetos com os mesmos dados de entrada (parâmetros de criação) são necessários a fábrica irá retornar a mesma instância ref e, em seguida, usando "==" seria suficiente.

Esta abordagem é útil em apenas determinadas circunstâncias. E na maioria das vezes seria um exagero.

Dê uma olhada esta resposta saber como implementar tal coisa.

it

aviso é um monte de código

Para curto ver como funciona a classe wrapper desde java 1.5

Integer a = Integer.valueOf( 2 );
Integer b = Integer.valueOf( 2 );

a == b 

for verdadeiro, enquanto

new Integer( 2 ) == new Integer( 2 )  

é falsa.

Ele mantém internamente o de referência e devolvê-lo se o valor de entrada é o mesmo.

Como você sabe Integer é somente leitura

Algo semelhante acontece com a classe String a partir do qual essa pergunta era sobre.

Talvez eu estou perdendo o ponto, mas a única razão para o uso é igual ao invés de definir o seu próprio método com um nome diferente é porque muitas das coleções (e provavelmente outras coisas no JDK ou seja lá o que é chamado hoje em dia) esperam que o método para definir um resultado coerente iguais. Mas, além disso, eu posso pensar de três tipos de comparações que você quer fazer em iguais:

  1. Os dois objetos são realmente a mesma instância. Isso não faz sentido para o uso é igual porque você pode usar ==. Além disso, e me corrija se eu esqueci como ele funciona em Java, o padrão é igual a método faz isso usando os códigos de hash gerados automaticamente.
  2. Os dois objetos têm referências às mesmas instâncias, mas não são a mesma instância. Isso é útil, uh, às vezes ... especialmente se eles são objetos persistiu e se referem ao mesmo objeto no DB. Você teria que definir o seu método equals para fazer isso.
  3. Os dois objetos têm referências a objetos que são iguais em valor, embora eles podem ou não ser as mesmas instâncias (em outras palavras, você comparar os valores de todo o caminho através da hierarquia).

Por que você iria querer comparar dois objetos? Bem, se eles são iguais, você iria querer fazer uma coisa, e se eles não estão, você gostaria de fazer outra coisa.

Dito isto, depende do caso em questão.

A principal razão para iguais substituição () na maioria dos casos é para verificar se há duplicatas dentro de certos Collections. Por exemplo, se você quiser usar um conjunto para conter um objeto que você criou você precisará substituir equals () e hashCode () dentro de seu objeto. O mesmo se aplica se você quiser usar o seu objeto personalizado como uma chave em um mapa.

Isto é crítico, como eu tenho visto muitas pessoas cometem o erro na prática de adicionar seus objetos personalizados para Define ou Mapas sem substituir equals () e hashCode (). A razão que esta pode ser especialmente insidioso é o compilador não vai reclamar e você pode acabar com vários objetos que contêm os mesmos dados, mas têm diferentes referências em uma coleção que não permite duplicatas.

Por exemplo, se você tinha um feijão simples chamado NameBean com um único atributo String 'nome', você poderia construir duas instâncias de NameBean (por exemplo, NAME1 e NAME2), cada um com o mesmo 'name' valor do atributo (por exemplo, "Alice" ). Você pode então adicionar tanto name1 e name2 a um jogo e o conjunto seria o tamanho 2 ao invés de tamanho 1, que é o que se pretende. Da mesma forma, se você tem um mapa como o Mapa, a fim de mapear o feijão nome para algum outro objeto, e você mapeou primeira name1 para a cadeia "primeiro" e depois mapeados name2 para a cadeia "segunda" você terá os dois pares de chave / valor no mapa (por exemplo, name1 -> "primeiro", name2 -> "segundo"). Então, quando você faz um mapa de pesquisa que irá retornar o valor mapeado para a referência exacta de passagem em que é ou name1, name2, ou outra referência com o nome "Alice", que irá retornar nulo.

Aqui está um exemplo concreto precedido pela saída de executá-lo:

Output:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

Código:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}

A igualdade é fundamental para a lógica (ver lei de identidade), e não há muito de programação que você pode fazer sem ele. Como para comparar instâncias de classes que você escreve, bem, isso é até você. Se você precisa ser capaz de encontrá-los em coleções ou usá-los como chaves no Maps, você vai precisar de verificações de igualdade.

Se você já escreveu mais do que algumas bibliotecas não triviais em Java, você saberá que a igualdade é difícil de acertar, especialmente quando as únicas ferramentas no peito são equals e hashCode. Igualdade acaba sendo intimamente ligado com hierarquias de classe, o que torna para o código quebradiços. Além do mais, nenhum tipo de verificação é fornecida uma vez que estes métodos apenas ter parâmetros do tipo de objeto.

Há uma maneira de fazer verificação de igualdade (e hashing) muito menos propenso a erros e mais de tipo seguro. No Funcional Java biblioteca, você encontrará Equal<A> (e uma correspondente Hash<A> ) onde a igualdade é desacoplada em uma única classe. Ele tem métodos para compor casos Equal para suas classes de instâncias existentes, bem como wrappers para coleções, Iterables , HashMap e HashSet , que o uso Equal<A> e Hash<A> vez de equals e hashCode.

O que há de melhor sobre esta abordagem é que você nunca pode esquecer de iguais escrever e método de hash quando eles são chamados. O sistema tipo irá ajudá-lo a se lembrar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top