Perché C # e Java usano l'uguaglianza di riferimento come impostazione predefinita per '=='?


32

Ho riflettuto per un po 'sul perché Java e C # (e sono sicuro che altre lingue) di default fanno riferimento all'uguaglianza ==.

Nella programmazione che faccio (che certamente è solo un piccolo sottoinsieme di problemi di programmazione), quasi sempre voglio l'uguaglianza logica quando si confrontano gli oggetti anziché l'uguaglianza di riferimento. Stavo cercando di pensare al perché entrambe queste lingue seguissero questa strada invece di invertirla e di ==essere l'uguaglianza logica e usare .ReferenceEquals()per l'uguaglianza di riferimento.

Ovviamente usare l'uguaglianza di riferimento è molto semplice da implementare e dà un comportamento molto coerente, ma non sembra adattarsi bene alla maggior parte delle pratiche di programmazione che vedo oggi.

Non voglio sembrare ignaro dei problemi nel cercare di implementare un confronto logico e che debba essere implementato in ogni classe. Mi rendo anche conto che queste lingue sono state progettate molto tempo fa, ma la domanda generale è valida.

C'è qualche grande vantaggio nell'impostare ciò che mi manca semplicemente, o sembra ragionevole che il comportamento predefinito sia l'uguaglianza logica, e tornando all'uguaglianza di riferimento se non esiste un'uguaglianza logica per la classe?


3
Perché le variabili sono riferimenti? Dal momento che le variabili agiscono come puntatori, ha senso confrontarle allo stesso modo
Daniel Gratzer,

C # usa l'uguaglianza logica per tipi di valore come le strutture. Ma quale dovrebbe essere l '"uguaglianza logica predefinita" per due oggetti di diversi tipi di riferimento? O per due oggetti in cui uno è di tipo A ereditato da B? Sempre "falso" come per le strutture? Anche quando si fa riferimento allo stesso oggetto due volte, prima come A, poi come B? Non ha molto senso per me.
Doc Brown,

3
In altre parole, ti stai chiedendo perché in C #, se esegui l'override Equals(), non cambia automaticamente il comportamento di ==?
svick

Risposte:


29

C # lo fa perché lo ha fatto Java. Java ha fatto perché Java non supporta il sovraccarico dell'operatore. Poiché l'uguaglianza di valore deve essere ridefinita per ogni classe, non può essere un operatore, ma deve essere un metodo. IMO questa è stata una decisione sbagliata. È molto più facile sia scrivere che leggere a == brispetto a a.equals(b), e molto più naturale per i programmatori con esperienza C o C ++, ma a == bè quasi sempre sbagliato. I bug dell'uso di ==dove .equalsera richiesto hanno sprecato innumerevoli migliaia di ore di programmazione.


7
Penso che ci siano tanti sostenitori del sovraccarico degli operatori quanti sono i detrattori, quindi non direi "è stata una decisione sbagliata" come un'asserzione assoluta. Esempio: nel progetto C ++ in cui lavoro abbiamo sovraccaricato il programma ==per molte classi e qualche mese fa ho scoperto che alcuni sviluppatori non sapevano cosa ==stesse realmente facendo. C'è sempre questo rischio quando la semantica di qualche costrutto non è ovvia. La equals()notazione mi dice che sto usando un metodo personalizzato e che devo cercarlo da qualche parte. In conclusione: penso che il sovraccarico dell'operatore sia un problema aperto in generale.
Giorgio,

9
Direi che Java non ha un sovraccarico dell'operatore definito dall'utente . Molti operatori hanno significati doppi (sovraccarichi) in Java. Guarda ad +esempio, che fa contemporaneamente (di valori numerici) e concatenazione di stringhe.
Joachim Sauer,

14
Come può a == bessere più naturale per i programmatori con esperienza in C, poiché C non supporta il sovraccarico dell'operatore definito dall'utente? (Ad esempio, il modo C per confrontare le stringhe è strcmp(a, b) == 0, non a == b.)
svick

Questo è fondamentalmente quello che pensavo, ma ho pensato che avrei chiesto a quelli con più esperienza di assicurarsi che non mi mancasse qualcosa di ovvio.
Cerniera

4
@svick: in C non esiste un tipo di stringa né alcun tipo di riferimento. Le operazioni sulle stringhe vengono eseguite tramite char *. Mi sembra ovvio che confrontare due puntatori per l'uguaglianza non è lo stesso di un confronto di stringhe.
Kevin Cline,

15

La risposta breve: coerenza

Per rispondere correttamente alla tua domanda, tuttavia, suggerisco di fare un passo indietro e guardare al problema di ciò che significa uguaglianza in un linguaggio di programmazione. Esistono almeno TRE diverse possibilità, che sono utilizzate in varie lingue:

  • Uguaglianza di riferimento : significa che a = b è vero se aeb si riferiscono allo stesso oggetto. Non sarebbe vero se aeb si riferissero a oggetti diversi, anche se tutti gli attributi di aeb fossero uguali.
  • Uguaglianza superficiale : significa che a = b è vero se tutti gli attributi degli oggetti a cui aeb si riferiscono sono identici. L'uguaglianza superficiale può essere facilmente implementata mediante un confronto bit a bit dello spazio di memoria che rappresenta i due oggetti. Si noti che l'uguaglianza di riferimento implica l'uguaglianza superficiale
  • Uguaglianza profonda : significa che a = b è vero se ogni attributo in aeb è identico o profondamente uguale. Si noti che l'uguaglianza profonda è implicita sia dall'uguaglianza di riferimento sia dall'uguaglianza superficiale. In questo senso, l'uguaglianza profonda è la forma più debole di uguaglianza e l'uguaglianza di riferimento è la più forte.

Questi tre tipi di uguaglianza sono spesso usati perché sono convenienti da implementare: tutti e tre i controlli di uguaglianza possono essere facilmente generati da un compilatore (in caso di uguaglianza profonda, il compilatore potrebbe aver bisogno di usare i tag tag per evitare loop infiniti se una struttura per essere confrontato ha riferimenti circolari). Ma c'è un altro problema: nessuno di questi potrebbe essere appropriato.

Nei sistemi non banali, l'uguaglianza degli oggetti è spesso definita come qualcosa tra l'uguaglianza profonda e quella di riferimento. Per verificare se vogliamo considerare due oggetti uguali in un determinato contesto, potremmo richiedere che alcuni attributi vengano confrontati da dove si trova in memoria e altri da una profonda uguaglianza, mentre ad alcuni attributi può essere permesso di essere qualcosa di completamente diverso. Quello che vorremmo davvero è un "quarto tipo di uguaglianza", davvero bello, spesso chiamato in letteratura uguaglianza semantica . Le cose sono uguali se sono uguali, nel nostro dominio. =)

Quindi possiamo tornare alla tua domanda:

C'è qualche grande vantaggio nell'impostare ciò che mi manca semplicemente, o sembra ragionevole che il comportamento predefinito dovrebbe essere l'uguaglianza logica e tornare all'uguaglianza di riferimento se non esiste un'uguaglianza logica per la classe?

Che cosa intendiamo quando scriviamo "a == b" in qualsiasi lingua? Idealmente, dovrebbe essere sempre la stessa: uguaglianza semantica. Ma questo non è possibile.

Una delle considerazioni principali è che, almeno per tipi semplici come i numeri, ci aspettiamo che due variabili siano uguali dopo l'assegnazione dello stesso valore. Vedi sotto:

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

In questo caso, ci aspettiamo che "a sia uguale a b" in entrambe le affermazioni. Qualsiasi altra cosa sarebbe pazza. La maggior parte (se non tutte) delle lingue segue questa convenzione. Pertanto, con tipi semplici (ovvero valori) sappiamo come raggiungere l'uguaglianza semantica. Con gli oggetti, può essere qualcosa di completamente diverso. Vedi sotto:

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

Ci aspettiamo che il primo "se" sarà sempre vero. Ma cosa ti aspetti dal secondo "se"? Dipende davvero. 'DoSomething' può cambiare l'uguaglianza (semantica) di aeb?

Il problema con l'uguaglianza semantica è che non può essere generato automaticamente dal compilatore per gli oggetti, né è evidente dalle assegnazioni . È necessario fornire un meccanismo affinché l'utente definisca l'uguaglianza semantica. Nei linguaggi orientati agli oggetti, quel meccanismo è un metodo ereditato: uguale a . Leggendo un pezzo di codice OO, non ci aspettiamo che un metodo abbia la stessa esatta implementazione in tutte le classi. Siamo abituati all'eredità e al sovraccarico.

Con gli operatori, tuttavia, ci aspettiamo lo stesso comportamento. Quando vedi 'a == b' dovresti aspettarti lo stesso tipo di uguaglianza (da 4 sopra) in tutte le situazioni. Quindi, puntando alla coerenza, i progettisti delle lingue hanno usato l'uguaglianza di riferimento per tutti i tipi. Non dovrebbe dipendere dal fatto che un programmatore abbia ignorato un metodo o meno.

PS: il linguaggio Dee è leggermente diverso da Java e C #: l'operatore equals significa uguaglianza superficiale per tipi semplici e uguaglianza semantica per le classi definite dall'utente (con la responsabilità di implementare l'operazione = che giace con l'utente - non viene fornito alcun valore predefinito). Poiché, per i tipi semplici, l'uguaglianza superficiale è sempre uguaglianza semantica, il linguaggio è coerente. Il prezzo che paga, tuttavia, è che l'operatore uguale è per impostazione predefinita non definito per i tipi definiti dall'utente. Devi implementarlo. E, a volte, è solo noioso.


2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.I progettisti linguistici di Java hanno usato l'uguaglianza di riferimento per gli oggetti e l'uguaglianza semantica per le primitive. Non è ovvio per me che questa sia stata la decisione giusta, o che questa decisione sia più "coerente" che consentire ==di essere sovraccaricato per l'uguaglianza semantica degli oggetti.
Charles Salvia,

Hanno usato "l'equivalente dell'uguaglianza di riferimento" anche per le primitive. Quando usi "int i = 3" non ci sono puntatori per il numero, quindi non puoi usare riferimenti. Con le stringhe, una "specie di" tipo primitivo, è più evidente: devi usare ".intern ()" o un'assegnazione diretta (String s = "abc") per usare == (uguaglianza di riferimento).
Hbas,

1
PS: C #, d'altra parte, non era coerente con le sue stringhe. E IMHO, in questo caso, è molto meglio.
Hbas,

@CharlesSalvia: in Java, se ae bsono dello stesso tipo, l'espressione a==bverifica se ae bmantiene la stessa cosa. Se uno di essi contiene un riferimento all'oggetto # 291 e l'altro contiene un riferimento all'oggetto # 572, non hanno la stessa cosa. Il contenuto dell'oggetto # 291 e # 572 può essere equivalente, ma le variabili stesse contengono cose diverse.
supercat

2
@CharlesSalvia È progettato in modo tale da poter vedere a == be sapere cosa fa. Allo stesso modo, puoi vedere a.equals(b)e presumere che un sovraccarico equals. Se a == bchiama a.equals(b)(se implementato), si sta confrontando per riferimento o per contenuto? Non ricordi? Devi controllare la classe A. Il codice non è più veloce da leggere se non sei nemmeno sicuro di come viene chiamato. Sarebbe come se fossero consentiti metodi con la stessa firma e il metodo chiamato dipende dall'ambito corrente. Tali programmi sarebbero impossibili da leggere.
Neil,

0

Stavo cercando di pensare al perché entrambe queste lingue hanno seguito questa strada invece di invertirla e avere == essere l'uguaglianza logica e usare .ReferenceEquals () per l'uguaglianza di riferimento.

Perché quest'ultimo approccio sarebbe confuso. Ritenere:

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

Questo codice "ok"dovrebbe essere stampato o dovrebbe generare un NullPointerException?


-2

Per Java e C # il vantaggio sta nel fatto che sono orientati agli oggetti.

Dal punto di vista delle prestazioni , anche la scrittura più semplice del codice dovrebbe essere più rapida: poiché OOP intende che elementi distinti logicamente siano rappresentati da oggetti diversi, controllare l'uguaglianza di riferimento sarebbe più rapido, tenendo conto del fatto che gli oggetti possono diventare piuttosto grandi.

Da un punto di vista logico - l'uguaglianza di un oggetto con un altro non deve essere così ovvia come il confronto con le proprietà dell'oggetto per l'uguaglianza (es. Come viene interpretato logicamente null == null? Ciò può differire da caso a caso).

Penso che ciò che si riduce, è la tua osservazione che "vuoi sempre l'uguaglianza logica sull'uguaglianza di riferimento". Il consenso tra i progettisti del linguaggio era probabilmente il contrario. Personalmente trovo difficile valutarlo, poiché mi manca l'ampio spettro dell'esperienza di programmazione. In parole povere, utilizzo maggiormente l'uguaglianza di riferimento negli algoritmi di ottimizzazione e l'uguaglianza logica più nella gestione dei set di dati.


7
L'uguaglianza di riferimento non ha nulla a che fare con l'orientamento agli oggetti. Al contrario, in realtà: una delle proprietà fondamentali dell'orientamento agli oggetti è che gli oggetti che hanno lo stesso comportamento sono indistinguibili. Un oggetto deve essere in grado di simulare un altro oggetto. (Dopotutto, OO è stato inventato per la simulazione!) L'uguaglianza di riferimento ti consente di distinguere tra due oggetti diversi che hanno lo stesso comportamento, ti consente di distinguere tra un oggetto simulato e uno reale. Pertanto, l'uguaglianza di riferimento interrompe l' orientamento agli oggetti. Un programma OO non deve utilizzare l'uguaglianza di riferimento.
Jörg W Mittag,

@ JörgWMittag: per eseguire correttamente un programma orientato agli oggetti è necessario che ci sia un modo per chiedere all'oggetto X se il suo stato è uguale a quello di Y [una condizione potenzialmente transitoria], e anche un modo per chiedere all'oggetto X se è equivalente a Y [X è equivalente a Y solo se il suo stato è garantito per essere eternamente uguale a Y]. Avere metodi virtuali separati per l'equivalenza e l'uguaglianza di stato sarebbe buono, ma per molti tipi, la disuguaglianza di riferimento implica una non equivalenza e non c'è motivo di dedicare tempo all'invio di metodi virtuali per dimostrarlo.
supercat

-3

.equals()confronta le variabili in base al loro contenuto. invece di ==confrontare gli oggetti con il loro contenuto ...

l'uso degli oggetti è più preciso .equals()


3
L'ipotesi non è corretta. .equals () fa qualunque cosa .equals () sia stato codificato per fare. Normalmente dipende dai contenuti, ma non deve esserlo. Inoltre, non è più preciso usare .equals (). Dipende solo da ciò che stai cercando di realizzare.
Cerniera
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.