Ao substituir iguais em Java, por que não trabalhar para usar um parâmetro diferente do objeto?

StackOverflow https://stackoverflow.com/questions/309892

  •  08-07-2019
  •  | 
  •  

Pergunta

Eu corri para um comportamento interessante recentemente. Parece que se eu substituir .equals () para tirar um parâmetro diferente do objeto, ele não é chamado. Alguém pode me explicar por que isso está acontecendo? Ele parece violar o meu entendimento do polimorfismo em OOP, mas talvez eu estou faltando alguma coisa.

Aqui está o código muito mais simples que mostra o que eu estou vendo:

public class MyClass {
  private int x;
  public MyClass(int n) { x = n; }
  public boolean equals(Object o) { return false; }
  public boolean equals(MyClass mc) { return x == mc.x; }
  public static void main(String[] args) {
    List<MyClass> list = new ArrayList<MyClass>();
    list.add(new MyClass(3));
    System.out.println("Contains 3? " + list.contains(new MyClass(3)));
  }
}

Quando isso for executado, ele imprime "Contains 3? false". Parece que a função equals (Object) é chamado, mesmo que não haja outra que iria trabalhar. Por outro lado, se eu escrever é igual como este o código funciona como esperado:

public boolean equals(Object o) {
  if(!(o instanceof MyClass))
    return false;
  MyClass mc = (MyClass)o;
  return x == mc.x;
}

Por que não é descobrir qual versão da função a ser chamada com base no tipo do parâmetro?

Foi útil?

Solução

Você está misturando "substituir" e "sobrecarga".

Substituir - acrescenta a definição de um método existente substituição, para fins de polimorfismo. O método deve ter a mesma assinatura. A assinatura consiste no nome e argumentos tipos. métodos substituídos são seleccionados no tempo de execução com base no tipo de execução do objecto alvo.

Sobrecarga - a adição de um método com o mesmo nome mas uma assinatura diferente. Métodos sobrecarregados são seleccionados em tempo de compilação com base no tipo de tempo de compilação do objecto alvo.

Outras dicas

equals (Object) está substituindo um método de super; você pode não substituir um método de super sem usar exatamente a mesma assinatura (Bem, há algumas exceções, como returntypes covariantes e exceção).

Observe que o método que você está chamando está definido no javadoc para ArrayList<E> como

boolean contains(Object o)
    Returns true if this list contains the specified element. 

em vez de

boolean contains(E o)
    Returns true if this list contains the specified element. 

Implementação de ArrayList.java:

private transient Object elementData[];

public boolean contains(Object elem) {
    return indexOf(elem) >= 0;
}

public int indexOf(Object elem) {
    if (elem == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (elem.equals(elementData[i]))
                return i;
    }
    return -1;
}

Ele usa o método é igual definido na superclasse objeto desde a igual método não é substituído em ArrayList<E> 's implementação.

Ao substituir objeto é igual em java, você deve substituir o método hashCode objeto também.

De qualquer forma que você pode querer tentar o seguinte código:

class A{    
    public int content;    
    A(){
        this(0);
    }    
    A(int value){
        content = value;
    }   
    public boolean equals(Object obj){
        System.out.println("overriding equals method");
        return this.content == ((A) obj).content;
    }    
    public boolean equals(A a){
        System.out.println("overloading equals method");
        return this.content == a.content;
    }    
    public static void main(String[] args){
        A x = new A(1);
        A y = new A(2);
        Object z  = new A(1);
        System.out.println(x.equals(y));
        System.out.println(x.equals(x));
        System.out.println(x.equals(z));
        //override as z is declared as Object at compile time
        //so it will use methods in class Object instead of class A
        System.out.println(x.equals((Object) y));
        System.out.println(x.equals((Object) x));        
    }   
}
//rant: they didn't teach me these in javaschool and I had to learn it the hard way.

A implementação ArrayList do contém método (Object) é obrigado a usar Object.equals (Object) método internamente, para que ele nunca vai saber sobre sua sobrecarga do método equals (MyClass). será encontrado apenas um método de substituição (com correspondência assinatura).

Ok, deixe-me voltar a frase.

(1) Porque o compilador elimina todas as informações relativas ao Generics (apagamento, consulte aqui ), e (2) porque você não pode substituir um método sem exatamente a mesma assinatura (equals (Object)), (3) durante a execução de todos os objetos dentro da lista são tratados como objetos e não como instâncias de MyClass. Assim, o método que é chamado é equals (Object) uma vez que esta é a única que está sido substituído por sua classe.

Você está assumindo que o método contains() em List sabe o tipo do objeto em tempo de execução, o que é incorreto.

Por causa de apagamento, List<MyClass> torna-se apenas um List regular em tempo de execução, de modo que o método contains() vê seu parâmetro como um Object, invocando assim equals() do objeto em vez do definido para MyClass na sua execução.

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