Perché non è stato eseguito il confronto del valore di stringa operatore == in Java?

50

Ogni programmatore Java competente sa che è necessario utilizzare String.equals () per confrontare una stringa, piuttosto che == perché == controlla l'uguaglianza di riferimento.

Quando mi occupo di stringhe, il più delle volte controllo l'uguaglianza dei valori piuttosto che l'uguaglianza di riferimento. Mi sembra che sarebbe più intuitivo se il linguaggio consentisse di confrontare i valori delle string usando semplicemente ==.

Come confronto, l'operatore == di C # verifica l'uguaglianza dei valori per la stringa s. E se hai davvero bisogno di verificare l'uguaglianza dei riferimenti, puoi usare String.ReferenceEquals.

Un altro punto importante è che le stringhe sono immutabili, quindi non c'è nulla di male da fare se si consente questa funzione.

C'è qualche ragione particolare per cui questo non è implementato in Java?

    
posta l46kok 02.04.2013 - 12:46
fonte

9 risposte

90

Suppongo sia solo coerenza, o "principio di minimo stupore". String è un oggetto, quindi sarebbe sorprendente se fosse trattato in modo diverso rispetto ad altri oggetti.

All'epoca in cui Java usciva (~ 1995), il fatto di avere qualcosa come String era un lusso totale per la maggior parte dei programmatori che erano abituati a rappresentare stringhe come array con terminazione nulla. Il comportamento di String è ora quello che era allora, ed è buono; modificare in modo subdolo il comportamento in seguito potrebbe avere effetti sorprendenti e indesiderati nei programmi di lavoro.

Come nota a margine, potresti usare String.intern() per ottenere una rappresentazione canonica (internata) della stringa, dopo di che i confronti potrebbero essere fatti con == . L'interning richiede un po 'di tempo, ma dopo, i confronti saranno molto veloci.

Aggiunta: contrariamente a quanto suggeriscono alcune risposte, non riguarda il supporto dell'overloading dell'operatore . L'operatore + (concatenazione) funziona su String s anche se Java non supporta l'overloading dell'operatore; è semplicemente gestito come caso speciale nel compilatore, risolvendosi in StringBuilder.append() . Allo stesso modo, == potrebbe essere stato gestito come caso speciale.

Allora perché stupire con caso speciale + ma non con == ? Perché, + semplicemente non compila quando viene applicato a oggetti% nonString, quindi è rapidamente evidente. Il diverso comportamento di == sarebbe molto meno evidente e quindi molto più sorprendente quando ti colpisce.

    
risposta data 02.04.2013 - 12:53
fonte
32

James Gosling , il creatore di Java, lo ha spiegato in questo modo back nel luglio 2000 :

I left out operator overloading as a fairly personal choice because I had seen too many people abuse it in C++. I've spent a lot of time in the past five to six years surveying people about operator overloading and it's really fascinating, because you get the community broken into three pieces: Probably about 20 to 30 percent of the population think of operator overloading as the spawn of the devil; somebody has done something with operator overloading that has just really ticked them off, because they've used like + for list insertion and it makes life really, really confusing. A lot of that problem stems from the fact that there are only about half a dozen operators you can sensibly overload, and yet there are thousands or millions of operators that people would like to define -- so you have to pick, and often the choices conflict with your sense of intuition.

    
risposta data 02.04.2013 - 13:24
fonte
9

Coerenza nella lingua. Avere un operatore che agisce in modo diverso può sorprendere il programmatore. Java non consente agli utenti di sovraccaricare gli operatori, pertanto l'uguaglianza di riferimento è l'unico significato ragionevole per == tra gli oggetti.

In Java:

  • Tra tipi numerici, == confronta l'uguaglianza numerica
  • Tra i tipi booleani, == confronta l'uguaglianza booleana
  • Tra oggetti, == confronta l'identità di riferimento
    • Utilizza .equals(Object o) per confrontare i valori

Questo è tutto. Regola semplice e semplice per identificare ciò che vuoi. Tutto questo è trattato in sezione 15.21 del JLS . Comprende tre sottosezioni facili da capire, implementare e ragionare.

Una volta che consenti il sovraccarico di == , il il comportamento esatto non è qualcosa che puoi guardare al JLS e mettere il dito su un elemento specifico e dire "così funziona", il codice può diventare difficile da ragionare. Il comportamento esatto di == può sorprendere per un utente. Ogni volta che lo vedi, devi tornare indietro e verificare cosa significhi realmente.

Dato che Java non consente il sovraccarico degli operatori, è necessario un test di uguaglianza dei valori che sia possibile sovrascrivere la definizione di base di. Pertanto, è stato affidato a queste scelte progettuali. == nei test Java numerici per i tipi numerici, l'uguaglianza booleana per i tipi booleani e l'uguaglianza di riferimento per tutto il resto (che può sovrascrivere .equals(Object o) per fare ciò che vogliono per l'uguaglianza dei valori).

Questo non è un problema di "c'è un caso d'uso per una particolare conseguenza di questa decisione progettuale", ma piuttosto "questa è una decisione di progettazione per facilitare queste altre cose, questa è una conseguenza di esso."

String interning , ne è un esempio. Secondo JLS 3.10.5 , tutti i valori letterali delle stringhe sono internati. Altre stringhe sono internate se si invoca .intern() su di esse. Quella "foo" == "foo" è vera è una conseguenza delle decisioni di progettazione prese per minimizzare l'impronta di memoria occupata dai letterali String. Oltre a ciò, l'interning delle stringhe è qualcosa che è a livello di JVM che ha un po 'di esposizione all'utente, ma nella stragrande maggioranza dei casi, non dovrebbe essere qualcosa che riguarda il programmatore (e i casi d'uso per i programmatori non erano qualcosa che era in cima alla lista per i designer quando si considera questa funzione).

Le persone faranno notare che + e += sono sovraccaricati per String. Tuttavia, questo non è né qui né lì. Resta il caso che se == ha un significato di uguaglianza di valore per String (e solo String), occorrerebbe un metodo diverso (che esiste solo in String) per l'uguaglianza di riferimento. Inoltre, questo complicherebbe inutilmente i metodi che accettano Object e aspettano che == si comporti in un modo e .equals() per comportarsi in un altro, richiedendo agli utenti casi speciali tutti i quei metodi per String.

Il contratto coerente per == su Oggetti è che è solo l'uguaglianza di riferimento e che .equals(Object o) esiste per tutti gli oggetti che dovrebbero testare per l'uguaglianza di valore. Complicare questo complica troppe cose.

    
risposta data 19.10.2015 - 22:34
fonte
2

Java non supporta l'overloading dell'operatore, il che significa che == si applica solo a tipi o riferimenti primitivi. Qualsiasi altra cosa richiede l'invocazione di un metodo. Perché i designer hanno fatto questa domanda è solo loro possono rispondere. Se dovessi indovinare, è probabilmente perché l'overloading dell'operatore porta complessità a cui non erano interessati ad aggiungere.

Non sono esperto in C #, ma i progettisti di quel linguaggio sembrano averlo impostato in modo tale che ogni primitiva sia un struct e ogni struct è un oggetto. Poiché C # consente il sovraccarico dell'operatore, tale disposizione rende molto facile per qualsiasi classe, non solo String , di funzionare autonomamente in "modo previsto" con qualsiasi operatore. C ++ consente la stessa cosa.

    
risposta data 02.04.2013 - 12:59
fonte
2

Questo è stato reso diverso in altre lingue.

In Object Pascal (Delphi / Free Pascal) e C #, l'operatore di uguaglianza è definito per confrontare valori, non riferimenti, quando si opera su stringhe.

In particolare in Pascal, string è un tipo primitivo (una delle cose che amo molto di Pascal, ottenere NullreferenceException solo perché una stringa non inizializzata è semplicemente irritante) e avere una semantica copy-on-write rendendo così (il più delle volte) operazioni con le stringhe molto economiche (in altre parole, visibili solo dopo aver avviato la concatenazione di stringhe multi-megabyte).

Quindi, è una decisione sul design del linguaggio per Java. Quando hanno progettato il linguaggio hanno seguito il modo C ++ (come Std :: String), quindi le stringhe sono oggetti, che è IMHO un hack per compensare di C privo di un tipo di stringa reale, invece di rendere le stringhe primitive (quali sono).

Quindi, per una ragione, posso solo supporre che l'abbiano resa semplice da parte loro e non codificando l'operatore come un'eccezione sul compilatore alle stringhe.

    
risposta data 02.04.2013 - 23:08
fonte
1

In Java, non vi è alcun sovraccarico dell'operatore, ed è per questo che gli operatori di confronto sono sovraccaricati solo per i tipi primitivi.

La classe 'String' non è una primitiva, quindi non ha un sovraccarico per '==' e usa il valore predefinito di confrontare l'indirizzo dell'oggetto nella memoria del computer.

Non ne sono sicuro, ma penso che in Java 7 o 8 oracle abbia fatto un'eccezione nel compilatore per riconoscere str1 == str2 come str1.equals(str2)

    
risposta data 02.04.2013 - 12:53
fonte
0

Java sembra essere stato progettato per mantenere una regola fondamentale che l'operatore == dovrebbe essere legale ogni volta che un operando può essere convertito nel tipo dell'altro e deve confrontare il risultato di tale conversione con il non convertito operando.

Questa regola non è propriamente unica per Java, ma ha alcuni effetti di vasta portata (e IMHO spiacevoli) sulla progettazione di altri aspetti legati alla tipologia del linguaggio. Sarebbe stato più pulito specificare i comportamenti di == riguardo a particolari combinazioni di tipi di operandi, e proibire combinazioni di tipi X e Y dove x1==y1 e x2==y1 non implicherebbero x1==x2 , ma le lingue raramente lo fanno che [in base a tale filosofia, double1 == long1 dovrebbe indicare se double1 non è una rappresentazione esatta di long1 , oppure rifiutarsi di compilare; int1==Integer1 dovrebbe essere vietato, ma dovrebbe esserci un mezzo di non lancio conveniente ed efficiente per testare se un oggetto è un intero in box con un valore particolare (il confronto con qualcosa che non è un intero in scatola dovrebbe semplicemente restituire false )] .

Per quanto riguarda l'applicazione dell'operatore == alle stringhe, se Java avesse vietato confronti diretti tra operandi di tipo String e Object , avrebbe potuto benissimo evitare sorprese nel comportamento di == , ma c'è nessun comportamento che potrebbe implementare per tali confronti che non sarebbe sorprendente. Avere due riferimenti di stringa mantenuti nel tipo Object si comportano diversamente dai riferimenti mantenuti nel tipo String sarebbe stato molto meno sorprendente di avere uno di questi comportamenti diverso da quello di un confronto di tipo misto legale. Se String1==Object1 è legale, ciò implicherebbe che l'unico modo per i comportamenti di String1==String2 e Object1==Object2 di corrispondere a String1==Object1 sarebbe che si unissero l'un l'altro.

    
risposta data 24.12.2013 - 20:24
fonte
0

In generale, ci sono ottime ragioni per voler essere in grado di verificare se due riferimenti a oggetti puntano allo stesso oggetto. Ho avuto un sacco di volte che ho scritto

Address oldAddress;
Address newAddress;
... populate values ...
if (oldAddress==newAddress)
... etc ...

Potrei o non potrei avere una funzione di uguale in questi casi. Se lo faccio, la funzione di uguale può confrontare l'intero contenuto di entrambi gli oggetti. Spesso confronta solo qualche identificatore. "A e B sono riferimenti allo stesso oggetto" e "A e B sono due oggetti diversi con lo stesso contenuto" sono, naturalmente, due idee molto diverse.

Probabilmente è vero che per gli oggetti immutabili, come le stringhe, questo è meno di un problema. Con oggetti immutabili, tendiamo a pensare all'oggetto e il valore come se fosse la stessa cosa. Bene, quando dico "noi", intendo "I", almeno.

Integer three=new Integer(3);
Integer triangle=new Integer(3);
if (three==triangle) ...

Ovviamente questo restituisce false, ma posso vedere qualcuno che pensa che dovrebbe essere vero.

Ma una volta che si dice che == confronta gli handle di riferimento e non i contenuti per gli oggetti in generale, creare un caso speciale per le stringhe potrebbe essere potenzialmente confuso. Come ha detto qualcun altro qui sopra, e se volessi confrontare i manici di due oggetti String? Ci sarebbe qualche funzione speciale per farlo solo per le stringhe?

E che dire ...

Object x=new String("foo");
Object y=new String("foo");
if (x==y) ...

È falso perché sono due oggetti diversi o veri perché sono stringhe il cui contenuto è uguale?

Quindi sì, capisco come i programmatori vengano confusi da questo. L'ho fatto io stesso, intendo scrivere se myString == "foo" quando intendevo se myString.equals ("pippo"). Ma a meno di ridisegnare il significato dell'operatore == per tutti gli oggetti, non vedo come affrontarlo.

    
risposta data 14.03.2016 - 14:49
fonte
0

Questa è una domanda valida per Strings , e non solo per le stringhe, ma anche per altri oggetti immutabili che rappresentano un certo "valore", ad es. Double , BigInteger e anche InetAddress .

Per rendere l'operatore == utilizzabile con stringhe e altre classi di valore, vedo tre alternative:

  • Chiedi al compilatore di conoscere tutte queste classi di valore e il modo di confrontare i loro contenuti. Se fosse solo una manciata di classi del pacchetto java.lang , lo considererei, ma questo non copre casi come InetAddress.

  • Permetti il sovraccarico dell'operatore in modo che una classe ne definisca il comportamento di confronto == .

  • Rimuovere i costruttori pubblici e disporre di metodi statici che restituiscono le istanze da un pool, restituendo sempre la stessa istanza per lo stesso valore. Per evitare perdite di memoria, è necessario qualcosa come SoftReferences nel pool, che non esisteva in Java 1.0. E ora, per mantenere la compatibilità, i costruttori String() non possono più essere rimossi.

L'unica cosa che potrebbe ancora essere fatta oggi sarebbe quella di introdurre un sovraccarico dell'operatore, e personalmente non mi piacerebbe che Java seguisse quella strada.

Per me, la leggibilità del codice è molto importante, e un programmatore Java sa che gli operatori hanno un significato fisso, definito nelle specifiche del linguaggio, mentre i metodi sono definiti da un certo codice, e il loro significato deve essere ricercato nel metodo Javadoc. Mi piacerebbe rimanere con questa distinzione anche se ciò significa che i confronti tra stringhe non saranno in grado di utilizzare l'operatore == .

C'è solo un aspetto dei confronti di Java che mi infastidisce: l'effetto di auto-boxing e -unboxing. Nasconde la distinzione tra il primitivo e il tipo di wrapper. Ma quando li confronti con == , sono MOLTO diversi.

    int i=123456;
    Integer j=123456;
    Integer k=123456;
    System.out.println(i==j);  // true or false? Do you know without reading the specs?
    System.out.println(j==k);  // true or false? Do you know without reading the specs?
    
risposta data 17.08.2017 - 13:21
fonte

Leggi altre domande sui tag