Ontem tive uma entrevista técnica por telefone de duas horas (na qual passei, uhuu!), Mas esqueci completamente a seguinte pergunta sobre vinculação dinâmica em Java. E é duplamente intrigante porque eu costumava ensinar esse conceito para alunos de graduação quando era um TA há alguns anos, então a perspectiva de que eu lhes dei informações erradas é um pouco perturbadora ...
Aqui está o problema que me foi dado:
/* What is the output of the following program? */
public class Test {
public boolean equals( Test other ) {
System.out.println( "Inside of Test.equals" );
return false;
}
public static void main( String [] args ) {
Object t1 = new Test();
Object t2 = new Test();
Test t3 = new Test();
Object o1 = new Object();
int count = 0;
System.out.println( count++ );// prints 0
t1.equals( t2 ) ;
System.out.println( count++ );// prints 1
t1.equals( t3 );
System.out.println( count++ );// prints 2
t3.equals( o1 );
System.out.println( count++ );// prints 3
t3.equals(t3);
System.out.println( count++ );// prints 4
t3.equals(t2);
}
}
Afirmei que a saída deveria ter sido duas instruções de impressão separadas de dentro do equals()
método substituído : at t1.equals(t3)
e t3.equals(t3)
. O último caso é bastante óbvio, e com o primeiro caso, embora t1
tenha uma referência do tipo Object, ele é instanciado como tipo Test, portanto a vinculação dinâmica deve chamar a forma substituída do método.
Aparentemente não. Meu entrevistador me incentivou a executar o programa sozinho e, vejam só, havia apenas uma única saída do método substituído: na linha t3.equals(t3)
.
Minha pergunta então é: por quê? Como já mencionei, embora t1
seja uma referência do tipo Object (então a vinculação estática invocaria o equals()
método de Object ), a vinculação dinâmica deve ter o cuidado de invocar a versão mais específica do método com base no tipo instanciado da referência. o que estou perdendo?
fonte
Respostas:
Java usa vinculação estática para métodos sobrecarregados e vinculação dinâmica para os substituídos. Em seu exemplo, o método equals está sobrecarregado (tem um tipo de parâmetro diferente de Object.equals ()), portanto, o método chamado é vinculado ao tipo de referência em tempo de compilação.
Alguma discussão aqui
O fato de ser o método de igual não é muito relevante, a não ser que seja um erro comum sobrecarregá-lo em vez de substituí-lo, que você já conhece com base em sua resposta ao problema na entrevista.
Edit: uma boa descrição aqui também. Este exemplo mostra um problema semelhante relacionado ao tipo de parâmetro, mas causado pelo mesmo problema.
Acredito que se a vinculação fosse realmente dinâmica, qualquer caso em que o chamador e o parâmetro fossem uma instância de Test resultaria na chamada do método substituído. Portanto, t3.equals (o1) seria o único caso que não seria impresso.
fonte
O
equals
método deTest
não substitui oequals
método dejava.lang.Object
. Veja o tipo de parâmetro! ATest
classe está sobrecarregadaequals
com um método que aceita aTest
.Se o
equals
método tiver a intenção de substituir, ele deve usar a anotação @Override. Isso causaria um erro de compilação para apontar esse erro comum.fonte
Curiosamente, no código Groovy (que poderia ser compilado em um arquivo de classe), todas as chamadas, exceto uma, executariam a instrução print. (Aquele que compara um Teste a um Objeto claramente não chamará a função Test.equals (Teste).) Isso ocorre porque o groovy DOES faz tipagem completamente dinâmica. Isso é particularmente interessante porque não possui variáveis que sejam explicitamente digitadas dinamicamente. Eu li em alguns lugares que isso é considerado prejudicial, pois os programadores esperam que o bacana faça a coisa do java.
fonte
Java não oferece suporte a covariância em parâmetros, apenas em tipos de retorno.
Em outras palavras, embora seu tipo de retorno em um método de substituição possa ser um subtipo do que era no substituído, isso não é verdadeiro para os parâmetros.
Se seu parâmetro para igual em Object for Object, colocar um equals com qualquer outra coisa em uma subclasse será um método sobrecarregado, não um método sobrescrito. Portanto, a única situação em que esse método será chamado é quando o tipo estático do parâmetro for Teste, como no caso de T3.
Boa sorte com o processo de entrevista de emprego! Eu adoraria ser entrevistado em uma empresa que faz esse tipo de pergunta em vez das perguntas usuais de algo / estruturas de dados que ensino a meus alunos.
fonte
Acho que a chave está no fato de que o método equals () não está de acordo com o padrão: ele recebe outro objeto Test, não o objeto Object e, portanto, não substitui o método equals (). Isso significa que você realmente o sobrecarregou para fazer algo especial quando recebeu o objeto Test, ao mesmo tempo que deu a ele chamadas de objeto Object Object.equals (Object o). Examinar o código por meio de qualquer IDE deve mostrar dois métodos equals () para Teste.
fonte
O método está sobrecarregado em vez de substituído. Equals sempre leva um objeto como parâmetro.
btw, você tem um item sobre isso no java eficaz de Bloch (que você deve possuir).
fonte
Algumas notas em Dynamic Binding (DD) e Static Binding̣̣̣ (SB) depois de pesquisar um pouco:
1. Cronometragem de execução : (Ref.1)
2. Usado para :
Referência:
fonte
Se outro método for adicionado que substitui em vez de sobrecarregar, isso explicará a chamada de vinculação dinâmica em tempo de execução.
/ * Qual é a saída do programa a seguir? * /
fonte
Eu encontrei um artigo interessante sobre vinculação dinâmica vs. estática. Ele vem com um pedaço de código para simular a vinculação dinâmica. Isso tornou meu código mais legível.
https://sites.google.com/site/jeffhartkopf/covariance
fonte
A resposta à pergunta "por quê?" é assim que a linguagem Java é definida.
Para citar o artigo da Wikipedia sobre covariância e contravariância :
Outras línguas são diferentes.
fonte
É muito claro que não há conceito de substituição aqui. É uma sobrecarga de método. o
Object()
método da classe Object leva parâmetro de referência do tipo Object e esteequal()
método leva parâmetro de referência do tipo Test.fonte
Vou tentar explicar isso por meio de dois exemplos que são as versões estendidas de alguns dos exemplos que encontrei online.
Aqui, para linhas com valores de contagem 0, 1, 2 e 3; temos referência de Object para o1 e t1 no
equals()
método. Portanto, em tempo de compilação, oequals()
método do arquivo Object.class será limitado.No entanto, embora a referência de t1 seja Object , ela tem inicialização da classe Test .
Object t1 = new Test();
.Portanto, em tempo de execução, ele chama o
public boolean equals(Object other)
que é um.
Agora, para valores de contagem como 4 e 6, é novamente direto que t3 que tem referência e inicialização de Teste está chamando o
equals()
método com parâmetro como referências de objeto e é umESTÁ BEM!
fonte