Każdy kompetentny programista Java wie, że musisz użyć String.equals (), aby porównać ciąg, zamiast ==, ponieważ == sprawdza równość referencji.
Kiedy mam do czynienia z łańcuchami, przez większość czasu sprawdzam równość wartości zamiast równości odniesienia. Wydaje mi się, że byłoby bardziej intuicyjne, gdyby język pozwalał na porównywanie wartości ciągów za pomocą ==.
Dla porównania operator C # == sprawdza równość wartości dla ciągu s. A jeśli naprawdę potrzebujesz sprawdzić równość referencji, możesz użyć String.ReferenceEquals.
Inną ważną kwestią jest to, że ciągi są niezmienne, więc zezwolenie na tę funkcję nie jest szkodliwe.
Czy jest jakiś szczególny powód, dla którego nie jest to zaimplementowane w Javie?
java
programming-languages
operators
L46kok
źródło
źródło
==
jest równość obiektu ieq
równość odniesienia ( ofps.oreilly.com/titles/9780596155957/… ).==
operator mapuje tylko,Equals
jeśli==
operator został zaimplementowany w ten sposób. Domyślne zachowanie dla==
jest takie samo jakReferenceEquals
(w rzeczywistościReferenceEquals
jest zdefiniowane jako wersja obiektowa==
)Odpowiedzi:
Myślę, że to tylko konsekwencja, czyli „zasada najmniejszego zdziwienia”. Łańcuch jest przedmiotem, więc byłoby zaskakujące, gdyby traktowano go inaczej niż inne obiekty.
W momencie, gdy pojawiła się Java (~ 1995), posiadanie czegoś takiego
String
było całkowitym luksusem dla większości programistów, którzy byli przyzwyczajeni do przedstawiania łańcuchów jako tablic zakończonych zerem.String
zachowanie było teraz tym, czym było wtedy i to dobrze; subtelna zmiana zachowania później może mieć zaskakujące, niepożądane efekty w działających programach.Na marginesie można użyć
String.intern()
kanonicznej (internowanej) reprezentacji łańcucha, po czym można dokonać porównań==
. Internowanie zajmuje trochę czasu, ale potem porównania będą naprawdę szybkie.Dodanie: w przeciwieństwie do niektórych odpowiedzi, nie chodzi o wspieranie przeciążania operatora .
+
Operator (konkatenacji) działa naString
s chociaż Java nie obsługuje operatora przeciążenia; jest to po prostu traktowane jako specjalny przypadek w kompilatorze, rozwiązującStringBuilder.append()
. Podobnie==
można było potraktować jako specjalny przypadek.Dlaczego więc zadziwić specjalnym przypadkiem,
+
ale nie==
? Ponieważ+
po prostu nie kompiluje się po zastosowaniu doString
obiektów niebędących obiektami, więc szybko to widać. Odmienne zachowanie od==
byłyby znacznie mniej widoczne, a tym samym znacznie bardziej zdumiewające, gdy uderza.źródło
James Gosling , twórca Javy, wyjaśnił to w lipcu 2000 roku :
źródło
==
Operator jest przeciążony obiektów i pierwotnych.+
Operator jest przeciążony nabyte
,short
,int
,long
,float
,double
,String
i prawdopodobnie kilka innych zapomniałem. Byłoby idealnie można przeciążyć==
dlaString
tak dobrze.Spójność w języku. Posiadanie operatora, który działa inaczej, może zaskoczyć programistę. Java nie pozwala użytkownikom przeciążać operatorów - dlatego równość odwołań jest jedynym rozsądnym znaczeniem
==
między obiektami.W Javie:
==
numerycznymi porównuje równość liczbową==
boolowskimi porównuje równość boolowską==
porównuje tożsamość odniesienia.equals(Object o)
do porównywania wartościOtóż to. Prosta reguła i prosta identyfikacja tego, czego chcesz. Wszystko to opisano w sekcji 15.21 JLS . Zawiera trzy podrozdziały, które są łatwe do zrozumienia, wdrożenia i uzasadnienia.
Gdy pozwolisz na przeładowanie
==
, dokładne zachowanie nie jest czymś, co możesz spojrzeć na JLS i położyć palec na konkretnym elemencie i powiedzieć „tak to działa”, kod może być trudny do uzasadnienia. Dokładne zachowanie==
może być zaskakujące dla użytkownika. Za każdym razem, gdy go zobaczysz, musisz wrócić i sprawdzić, co to właściwie oznacza.Ponieważ Java nie pozwala na przeciążanie operatorów, potrzebny jest test równości wartości, który można zastąpić podstawową definicją. Tak więc było to wymagane przez te wybory projektowe.
==
w testach Java numeryczne dla typów liczbowych, równość boolowska dla typów boolowskich i równość referencyjna dla wszystkich innych elementów (które mogą zastąpić.equals(Object o)
robienie, co chcą, dla równości wartości).Nie jest to kwestia „czy istnieje przypadek użycia konkretnej konsekwencji tej decyzji projektowej”, ale raczej „jest to decyzja projektowa mająca na celu ułatwienie tych innych rzeczy, jest to jej konsekwencja”.
Internowanie ciągów , jest jednym z takich przykładów. Zgodnie z JLS 3.10.5 wszystkie literały łańcuchowe są internowane. Inne łańcuchy są internalizowane, jeśli ktoś
.intern()
je wywołuje . To"foo" == "foo"
prawda, jest konsekwencją decyzji projektowych podejmowanych w celu zminimalizowania zajmowanego miejsca w pamięci przez literały String. Poza tym internowanie ciągów jest czymś na poziomie JVM, który ma trochę ekspozycji na użytkownika, ale w przeważającej większości przypadków nie powinno być czymś, co dotyczy programisty (a przypadki użycia dla programistów nie były coś, co było wysoko na liście projektantów przy rozważaniu tej funkcji).Ludzie to zauważą
+
i+=
są przeciążeni ciągiem. Tego jednak nie ma ani tu, ani tam. Pozostaje przypadek, że jeśli==
ma wartość równości wartości dla String (i tylko String), potrzebna byłaby inna metoda (która istnieje tylko w String) dla równości odniesienia. Co więcej, niepotrzebnie skomplikowałoby to metody, które pobierają Object i oczekują, że będą==
się zachowywać w jeden sposób, a.equals()
inne będą wymagały od użytkowników specjalnego przypadku wszystkich tych metod dla String.Konsekwentna umowa
==
dotycząca Obiektów polega na tym, że jest to tylko równość odniesienia i.equals(Object o)
istnieje dla wszystkich obiektów, które powinny testować równość wartości. Skomplikowanie tego komplikuje zdecydowanie zbyt wiele rzeczy.źródło
new String("foo") == new String("foo")
może być prawdziwa (patrz Deduplikacja ciągów ).Java nie obsługuje przeciążania operatora, co oznacza,
==
że dotyczy tylko typów pierwotnych lub referencji. Wszystko inne wymaga wywołania metody. Dlaczego projektanci to zrobili, to pytanie, na które tylko oni mogą odpowiedzieć. Gdybym musiał zgadywać, to prawdopodobnie dlatego, że przeciążenie operatora powoduje złożoność, której nie byli zainteresowani dodaniem.Nie jestem ekspertem w języku C #, ale wydaje się, że projektanci tego języka skonfigurowali go tak, aby każdy element pierwotny był
struct
i każdystruct
był przedmiotem. Ponieważ C # pozwala na przeciążanie operatora, takie ustawienie sprawia, że każda klasa, nie tylkoString
, może pracować w „oczekiwany” sposób z dowolnym operatorem. C ++ pozwala na to samo.źródło
==
oznaczałoby to równość łańcuchów, potrzebowalibyśmy innej notacji dla równości referencji.==
. To skutecznie dodaje przeciążenie operatora, co miałoby ogromne konsekwencje dla sposobu implementacji Java.ClassName.ReferenceEquals(a,b)
) oraz domyślny==
operator iEquals
metoda wskazujące naReferenceEquals
.Zrobiono to inaczej w innych językach.
W Object Pascal (Delphi / Free Pascal) i C # operator równości jest zdefiniowany do porównywania wartości, a nie odniesień, podczas pracy na ciągach.
Zwłaszcza w Pascalu ciąg jest prymitywnym typem (jedna z rzeczy, które naprawdę uwielbiam w Pascalu, otrzymywanie NullreferenceException tylko z powodu niezainicjowanego ciągu jest po prostu irytujące) i mają semantykę kopiowania przy zapisie, dzięki czemu (przez większość czasu) operacje na łańcuchach są bardzo tani (innymi słowy, zauważalny dopiero po rozpoczęciu łączenia łańcuchów wielobajtowych).
Jest to więc decyzja dotycząca projektu języka Java. Kiedy projektowali język, postępowali zgodnie ze sposobem C ++ (jak Std :: String), więc łańcuchy są obiektami, co jest IMHO hackem w celu zrekompensowania C braku prawdziwego typu łańcucha, zamiast przekształcania łańcuchów w prymitywne (którymi są).
Z tego powodu mogę jedynie spekulować, że zrobili to po swojej stronie, a nie kodowanie operatora stanowi wyjątek w kompilatorze ciągów znaków.
źródło
String
powinien być prymitywnym typem w Javie. W przeciwieństwie do innych typów, kompilator musi wiedzieć oString
; ponadto operacje na nim będą na tyle powszechne, że dla wielu rodzajów aplikacji mogą stanowić wąskie gardło wydajności (co można by złagodzić dzięki natywnemu wsparciu). Typowastring
[mała litera] miałaby przypisany obiekt na stercie do przechowywania jego zawartości, ale nigdzie nie byłoby „normalnego” odniesienia do tego obiektu; może zatem być jednokierunkowo pośredniChar[]
lubByte[]
raczej nie musi byćChar[]
pośredni przez inny przedmiot.W Javie nie występuje przeciążanie operatorów, dlatego operatory porównania są przeciążone tylko dla typów pierwotnych.
Klasa „String” nie jest prymitywna, dlatego nie ma przeciążenia dla „==” i korzysta z domyślnego porównywania adresu obiektu w pamięci komputera.
Nie jestem pewien, ale myślę, że w Javie 7 lub 8 wyrocznia uczyniły wyjątek w kompilator rozpoznać
str1 == str2
jakostr1.equals(str2)
źródło
s1
as2
i dać im te same treści, przechodzą one równość (s1.equals(s2)
) porównania, ale nie samo-odniesienia (==
) porównania, bo są dwa różne obiekty. Zmiana semantyki==
oznaczającej równość oznaczałabys1 == s2
ocenę,true
gdzie była ocenianafalse
.Wygląda na to, że Java została zaprojektowana w celu przestrzegania podstawowej zasady, że
==
operator powinien być legalny za każdym razem, gdy jeden operand może zostać przekonwertowany na typ drugiego, i powinien porównywać wynik takiej konwersji z nieprzekształconym operandem.Ta zasada nie jest unikalna dla Javy, ale ma dalekosiężne (i niefortunne) skutki w projektowaniu innych aspektów języka związanych z typem. Lepiej byłoby sprecyzować zachowania w
==
odniesieniu do poszczególnych kombinacji typów operandów i zabronić kombinacji typów X i Y, gdziex1==y1
ix2==y1
nie oznaczałoby tox1==x2
, ale języki rzadko to robią [zgodnie z tą filozofiądouble1 == long1
albo musiałby wskazać, czydouble1
nie jest dokładnym przedstawieniemlong1
lub odmówił skompilowania;int1==Integer1
powinno być zabronione, ale powinien istnieć wygodny i skuteczny nie rzucający sposób testowania, czy obiekt jest liczbą całkowitą w pudełku o określonej wartości (porównanie z czymś, co nie jest liczbą całkowitą w ramce, powinno po prostu powrócićfalse
)].Jeśli chodzi o zastosowanie
==
operatora do ciągów, gdyby Java zabronił bezpośrednich porównań między operandami typuString
iObject
mógłby całkiem dobrze uniknąć niespodzianek w zachowaniu==
, ale nie ma takiego zachowania, które mógłby zastosować dla takich porównań, które nie byłyby zadziwiające. Posiadanie dwóch ciągów referencji przechowywanych w typieObject
zachowuje się inaczej niż referencje trzymane w typieString
byłoby znacznie mniej zadziwiające niż posiadanie któregokolwiek z tych zachowań różni się od legalnego porównania typów mieszanych. JeśliString1==Object1
jest to zgodne z prawem, oznaczałoby to, że jedynym sposobem na zachowanieString1==String2
iObject1==Object2
dopasowanieString1==Object1
byłoby dopasowanie się.źródło
==
na obiektach powinno po prostu wywołać (null-safe) równe i coś innego (np.===
LubSystem.identityEqual
) powinno zostać użyte do porównania tożsamości. Mieszanie prymitywów i obiektów byłoby początkowo zabronione (przed 1.5 nie było żadnego autoboxowania), a następnie można by znaleźć prostą regułę (np. Null-bezpieczne rozpakowywanie, następnie rzutowanie, a następnie porównywanie).int==Integer
zysk operatorafalse
, jeśliInteger
jest zerowy, a inaczej porównać wartości, podejście byłoby w przeciwieństwie do zachowania==
we wszystkich innych okolicznościach, gdzie bezwzględnie wymusza oba operandy do tego samego typu przed porównywanie ich. Osobiście zastanawiam się, czy wprowadzono automatyczne rozpakowywanie, aby umożliwićint==Integer
zachowanie, które nie było nonsensowne ...int
i porównywanie referencji byłoby głupie [ale nie zawsze by się nie udawało ]. W przeciwnym razie nie widzę powodu, aby zezwolić na niejawną konwersję, która może się nie powieść z NPE.==
nie ma to nic wspólnegoidentityEquals
. +++ „oddzielne operatory równości dla równości wartości i odniesienia” - ale które? Rozważałbym zarówno prymitywne, jak==
i porównujące wartości w tym sensie, że analizuje wartość referencji. +++ Gdy ma to na myśli , POWINIEN zrobić autoboxing i porównać referencje przy użyciu równych zeru. +++ Obawiam się, mój pomysł nie jest tak naprawdę mój, ale to, co robi Kotlin.equals
equals
==
equals
int==Integer
==
nigdy nie testował równości odniesienia, mógłby rozsądnie wykonać zerowo bezpieczny test równości wartości. Fakt, że robi równości odniesienia testy, jednak poważnie ogranicza w jaki sposób można obsłużyć porównań mieszany odniesienia / wartość bez niekonsekwencji. Zauważ również, że Java jest ustalona w założeniu, że operatorzy promują oba operandy do tego samego typu, a nie dają specjalnych zachowań opartych na kombinacjach zaangażowanych typów. Na przykład16777217==16777216.0f
zwraca,true
ponieważ wykonuje stratną konwersję pierwszego operandu nafloat
, podczas gdy ...Zasadniczo istnieje bardzo dobry powód, aby chcieć przetestować, czy dwa odwołania do obiektów wskazują na ten sam obiekt. Miałem wiele razy, które napisałem
W takich przypadkach mogę mieć lub nie mieć funkcji równości. Jeśli tak, funkcja równości może porównać całą zawartość obu obiektów. Często po prostu porównuje jakiś identyfikator. „A i B to odniesienia do tego samego obiektu”, a „A i B to dwa różne obiekty o tej samej treści” to oczywiście dwie bardzo różne idee.
Prawdopodobnie to prawda, że w przypadku obiektów niezmiennych, takich jak Strings, jest to mniejszy problem. W przypadku niezmiennych obiektów mamy tendencję do myślenia o tym, że przedmiot i wartość są tym samym. Cóż, kiedy mówię „my”, mam na myśli przynajmniej „ja”.
Oczywiście to zwraca fałsz, ale widzę, że ktoś uważa, że to prawda.
Ale kiedy powiesz, że == porównuje uchwyty referencyjne, a nie zawartość ogólnie dla obiektów, utworzenie specjalnego przypadku dla łańcuchów byłoby potencjalnie mylące. Jak powiedział ktoś tutaj, co jeśli chcesz porównać uchwyty dwóch obiektów String? Czy byłaby jakaś specjalna funkcja, która mogłaby to zrobić tylko dla ciągów?
A co z ...
Czy to fałsz, ponieważ są to dwa różne obiekty, czy prawda, ponieważ są Ciągami, których treść jest równa?
Tak, rozumiem, w jaki sposób programiści są tym zdezorientowani. Zrobiłem to sam, mam na myśli pisz, jeśli myString == „foo”, kiedy miałem na myśli, jeśli myString.equals („foo”). Ale poza przeprojektowaniem znaczenia operatora == dla wszystkich obiektów, nie wiem, jak sobie z tym poradzić.
źródło
==
oznaczają „równe łańcuchy”.==
, tak jak wspomniałeś na końcu.==
podatne na błędy.Jest to ważna kwestia dla
Strings
, a nie tylko dla strun, ale również dla innych niezmiennych obiektów reprezentujących jakąś „wartość”, npDouble
,BigInteger
a nawetInetAddress
.Aby uczynić
==
operatora użytecznym z ciągami znaków i innymi klasami wartości, widzę trzy alternatywy:Niech kompilator wie o wszystkich tych klasach wartości i sposobie porównania ich zawartości. Gdyby to była tylko garść klas z
java.lang
pakietu, zastanowiłbym się nad tym, ale to nie obejmuje przypadków takich jak InetAddress.Zezwalaj na przeciążanie operatora, aby klasa definiowała
==
zachowanie porównawcze.Usuń konstruktory publiczne i zastosuj metody statyczne zwracające instancje z puli, zawsze zwracające tę samą instancję dla tej samej wartości. Aby uniknąć wycieków pamięci, potrzebujesz czegoś takiego jak SoftReferences w puli, który nie istniał w Javie 1.0. A teraz, aby zachować zgodność,
String()
konstruktorów nie można już usuwać.Jedyne, co można dziś zrobić, to wprowadzić przeciążenie operatora, a ja osobiście nie chciałbym, aby Java poszła tą drogą.
Dla mnie najważniejsza jest czytelność kodu, a programista Java wie, że operatory mają ustalone znaczenie, określone w specyfikacji języka, podczas gdy metody są określone przez jakiś kod, a ich znaczenie należy sprawdzić w Javadoc metody. Chciałbym pozostać z tym rozróżnieniem, nawet jeśli oznacza to, że porównania ciągów nie będą mogły korzystać z
==
operatora.Irytuje mnie tylko jeden aspekt porównań Javy: efekt auto-boxowania i -unboxingu. Ukrywa różnicę między typem pierwotnym a typem opakowania. Ale gdy porównasz je
==
, są one BARDZO różne.źródło