Por que C # e Java usam a igualdade de referência como padrão para '=='?

32

Estou pensando há algum tempo por que Java e C # (e tenho certeza de que outras linguagens) assumem como padrão a igualdade de referência ==.

Na programação que faço (que certamente é apenas um pequeno subconjunto de problemas de programação), quase sempre quero igualdade lógica ao comparar objetos em vez de fazer referência à igualdade. Eu estava tentando pensar por que essas duas linguagens seguiram esse caminho, em vez de invertê-lo e ter ==igualdade lógica e usar .ReferenceEquals()como igualdade de referência.

Obviamente, o uso da igualdade de referência é muito simples de implementar e oferece um comportamento muito consistente, mas não parece se encaixar bem com a maioria das práticas de programação que eu vejo hoje.

Não desejo parecer ignorante sobre os problemas ao tentar implementar uma comparação lógica, e que ela deve ser implementada em todas as classes. Também percebo que essas linguagens foram projetadas há muito tempo, mas a questão geral permanece.

Existe algum benefício importante em deixar isso de lado, ou simplesmente parece que o comportamento padrão deve ser a igualdade lógica, e voltar a referenciar a igualdade, se não existir uma igualdade lógica para a classe?

Zíper
fonte
3
Porque variáveis ​​são referências? Desde variáveis agir como ponteiros que faz sentido para eles para ser comparado como ele
Daniel Gratzer
C # usa igualdade lógica para tipos de valor como estruturas. Mas qual deveria ser a "igualdade lógica padrão" para dois objetos de diferentes tipos de referência? Ou para dois objetos em que um é do tipo A herdado de B? Sempre "falso" como para estruturas? Mesmo quando você tem o mesmo objeto referenciado duas vezes, primeiro como A e depois como B? Não faz muito sentido para mim.
Doc Brown
3
Em outras palavras, você está perguntando por que, em C #, se você substituir Equals(), ele não altera automaticamente o comportamento de ==?
svick

Respostas:

29

C # faz isso porque Java fez. O Java fez porque o Java não suporta sobrecarga do operador. Como a igualdade de valor deve ser redefinida para cada classe, ela não pode ser um operador, mas deve ser um método. A IMO foi uma péssima decisão. É muito mais fácil escrever e ler do a == bque a.equals(b)e muito mais natural para programadores com experiência em C ou C ++, mas a == bquase sempre está errado. Os erros do uso de ==onde .equalsera necessário desperdiçavam milhares de horas de programadores.

Kevin Cline
fonte
7
Eu acho que existem tantos apoiadores de sobrecarga de operadores quanto detratores, então eu não diria "foi uma péssima decisão" como uma declaração absoluta. Exemplo: no projeto C ++ em que trabalho, sobrecarregamos o arquivo ==para muitas classes e, há alguns meses, descobri que alguns desenvolvedores não sabiam o que ==estava realmente fazendo. Sempre existe esse risco quando a semântica de alguma construção não é óbvia. A equals()notação me diz que estou usando um método personalizado e que preciso procurar em algum lugar. Conclusão: acho que a sobrecarga do operador é uma questão em aberto em geral.
Giorgio
9
Eu diria que Java não tem sobrecarga de operador definida pelo usuário . Muitos operadores têm significados duplos (sobrecarregados) em Java. Veja, +por exemplo, o que faz adição (de valores numéricos) e concatenação de cadeias ao mesmo tempo.
Joachim Sauer
14
Como pode a == bser mais natural para programadores com experiência em C, já que o C não suporta sobrecarga de operador definida pelo usuário? (Por exemplo, a forma C a comparar cadeias é strcmp(a, b) == 0, não a == b.)
svick
Isso é basicamente o que eu pensava, mas achei que pediria àqueles com mais experiência para garantir que não perdesse algo óbvio.
Zipper
4
@svick: em C, não há tipo de string, nem nenhum tipo de referência. As operações de string são feitas via char *. Parece-me óbvio que comparar dois indicadores de igualdade não é o mesmo que uma comparação de cadeias.
kevin Cline
15

A resposta curta: Consistência

Para responder sua pergunta corretamente, sugiro que retrocedamos e analisemos a questão do que igualdade significa em uma linguagem de programação. Existem pelo menos TRÊS possibilidades diferentes, usadas em vários idiomas:

  • Igualdade de referência : significa que a = b é verdadeiro se aeb se referirem ao mesmo objeto. Não seria verdade se aeb se referisse a objetos diferentes, mesmo se todos os atributos de aeb fossem iguais.
  • Igualdade rasa : significa que a = b é verdadeiro se todos os atributos dos objetos aos quais aeb se referem são idênticos. A igualdade rasa pode ser facilmente implementada por uma comparação bit a bit do espaço de memória que representa os dois objetos. Observe que igualdade de referência implica igualdade rasa
  • Igualdade profunda : significa que a = b é verdadeiro se cada atributo em aeb for idêntico ou profundamente igual. Observe que a igualdade profunda está implícita tanto na igualdade de referência quanto na igualdade superficial. Nesse sentido, a igualdade profunda é a forma mais fraca de igualdade e a igualdade de referência é a mais forte.

Esses três tipos de igualdade são frequentemente usados ​​porque são convenientes de implementar: todas as três verificações de igualdade podem ser facilmente geradas por um compilador (no caso de profunda igualdade, o compilador pode precisar usar bits de tag para evitar loops infinitos se uma estrutura ser comparado tem referências circulares). Mas há outro problema: nada disso pode ser apropriado.

Em sistemas não triviais, a igualdade de objetos é frequentemente definida como algo entre igualdade profunda e de referência. Para verificar se queremos considerar dois objetos iguais em um determinado contexto, podemos exigir que alguns atributos sejam comparados com o local em que estão na memória e outros com profunda igualdade, enquanto alguns atributos podem ter algo completamente diferente. O que realmente gostaríamos é de um “quarto tipo de igualdade”, muito bom, muitas vezes chamado na literatura de igualdade semântica . As coisas são iguais se forem iguais, em nosso domínio. =)

Para que possamos voltar à sua pergunta:

Existe algum benefício importante em deixar isso de lado, ou simplesmente parece que o comportamento padrão deve ser a igualdade lógica, e voltar à referência de igualdade se uma igualdade lógica não existir para a classe?

O que queremos dizer quando escrevemos 'a == b' em qualquer idioma? Idealmente, deve ser sempre o mesmo: igualdade semântica. Mas isso não é possível.

Uma das principais considerações é que, pelo menos para tipos simples como números, esperamos que duas variáveis ​​sejam iguais após a atribuição do mesmo valor. Ver abaixo:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

Nesse caso, esperamos que 'a seja igual a b' nas duas declarações. Qualquer outra coisa seria insana. A maioria (se não todos) dos idiomas segue esta convenção. Portanto, com tipos simples (também conhecidos como valores), sabemos como alcançar a igualdade semântica. Com objetos, isso pode ser algo completamente diferente. Ver abaixo:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

Esperamos que o primeiro 'se' seja sempre verdadeiro. Mas o que você espera no segundo 'se'? Isso realmente depende. Pode 'DoSomething' alterar a igualdade (semântica) de aeb?

O problema da igualdade semântica é que ele não pode ser gerado automaticamente pelo compilador para objetos, nem é óbvio pelas atribuições . Um mecanismo deve ser fornecido para o usuário definir igualdade semântica. Nas linguagens orientadas a objetos, esse mecanismo é um método herdado: igual . Lendo um pedaço de código OO, não esperamos que um método tenha a mesma implementação exata em todas as classes. Estamos acostumados a herança e sobrecarga.

Com os operadores, porém, esperamos o mesmo comportamento. Quando você vê 'a == b', deve esperar o mesmo tipo de igualdade (dos 4 acima) em todas as situações. Assim, visando à consistência, os designers de idiomas usaram igualdade de referência para todos os tipos. Não deve depender se um programador substituiu um método ou não.

PS: A linguagem Dee é um pouco diferente de Java e C #: o operador equals significa igualdade rasa para tipos simples e igualdade semântica para classes definidas pelo usuário (com a responsabilidade de implementar a operação = que fica com o usuário - nenhum padrão é fornecido). Como para tipos simples, a igualdade superficial é sempre semântica, a linguagem é consistente. O preço pago, no entanto, é que o operador igual é, por padrão, indefinido para os tipos definidos pelo usuário. Você tem que implementá-lo. E, às vezes, isso é apenas chato.

Hbas
fonte
2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Os designers de linguagem do Java usaram igualdade de referência para objetos e igualdade semântica para primitivas. Não é óbvio para mim que essa foi a decisão certa ou que essa decisão é mais "consistente" do que permitir ==sobrecarregar a igualdade semântica de objetos.
Charles Salvia
Eles usaram "o equivalente à igualdade de referência" também para os primitivos. Quando você usa "int i = 3", não há ponteiros para o número, portanto você não pode usar referências. Com strings, um tipo "primitivo" de tipo, é mais evidente: você precisa usar o ".intern ()" ou uma atribuição direta (String s = "abc") para usar == (igualdade de referência).
Hbas
1
PS: C #, por outro lado, não era consistente com suas strings. E IMHO, neste caso, isso é muito melhor.
Hbas
@CharlesSalvia: em Java, se ae bsão do mesmo tipo, a expressão a==btesta se ae bmantém a mesma coisa. Se um deles possui uma referência ao objeto # 291 e o outro uma referência ao objeto # 572, eles não têm a mesma coisa. O conteúdo do objeto # 291 e # 572 pode ser equivalente, mas as próprias variáveis ​​mantêm coisas diferentes.
Supercat
2
@CharlesSalvia Ele foi projetado de forma que você possa ver a == be saber o que faz. Da mesma forma, você pode ver a.equals(b)e presumir que uma sobrecarga equals. Se as a == bchamadas a.equals(b)(se implementadas), são comparadas por referência ou por conteúdo? Não se lembra? Você deve verificar a classe A. O código não será mais tão rápido de ler se você não tiver certeza do que está sendo chamado. Seria como se métodos com a mesma assinatura fossem permitidos, e o método que está sendo chamado depende de qual é o escopo atual. Tais programas seriam impossíveis de ler.
855 Neil
0

Eu estava tentando pensar no motivo pelo qual essas duas linguagens seguiram esse caminho, em vez de invertê-lo e ter == ser igualdade lógica e usar .ReferenceEquals () para igualdade de referência.

Porque a última abordagem seria confusa. Considerar:

if (null.ReferenceEquals(null)) System.out.println("ok");

Esse código "ok"deve ser impresso ou deve gerar um NullPointerException?

Atsby
fonte
-2

Para Java e C #, o benefício reside em serem orientados a objetos.

Do ponto de vista do desempenho - o código mais fácil de escrever também deve ser mais rápido: como o OOP pretende que elementos logicamente distintos sejam representados por objetos diferentes, a verificação da igualdade de referência seria mais rápida, levando em consideração que os objetos podem se tornar muito grandes.

Do ponto de vista lógico - a igualdade de um objeto para outro não precisa ser tão óbvia quanto comparar as propriedades do objeto para igualdade (por exemplo, como é nulo == nulo logicamente interpretado? Isso pode diferir de caso para caso).

Eu acho que o que se resume é a sua observação de que "você sempre quer igualdade lógica sobre igualdade de referência". O consenso entre os designers de idiomas foi provavelmente o oposto. Pessoalmente, acho difícil avaliar isso, pois não tenho o amplo espectro de experiência em programação. Grosso modo, utilizo mais a igualdade de referência nos algoritmos de otimização e a igualdade lógica no tratamento de conjuntos de dados.

Rafael Emshoff
fonte
7
A igualdade de referência não tem nada a ver com orientação a objetos. Muito pelo contrário, na verdade: uma das propriedades fundamentais da orientação a objetos é que objetos com o mesmo comportamento são indistinguíveis. Um objeto deve ser capaz de simular outro objeto. (Afinal, o OO foi inventado para simulação!) A igualdade de referência permite distinguir entre dois objetos diferentes que têm o mesmo comportamento, além de distinguir entre um objeto simulado e um real. Portanto, a igualdade de referência quebra a orientação a objetos. Um programa OO não deve usar a Igualdade de Referência.
Jörg W Mittag
@ JörgWMittag: Para fazer um programa orientado a objetos corretamente, é necessário perguntar ao objeto X se seu estado é igual ao de Y [uma condição potencialmente transitória] e também um meio de perguntar ao objeto X se é equivalente a Y [X é equivalente a Y somente se for garantido que seu estado seja eternamente igual a Y]. Ter métodos virtuais separados para equivalência e igualdade de estado seria bom, mas para muitos tipos, a desigualdade de referência implicará não equivalência e não há razão para gastar tempo no despacho do método virtual para provar isso.
Supercat
-3

.equals()compara variáveis ​​pelo seu conteúdo. em vez ==disso, compara os objetos pelo seu conteúdo ...

usando objetos é mais preciso tu usar .equals()

Nuno Dias
fonte
3
Você assume que está incorreto. .equals () faz o que .equals () foi codificado para fazer. É normalmente pelo conteúdo, mas não precisa ser. Também não é mais preciso usar .equals (). Depende apenas do que você está tentando realizar.
Zipper