Zastanawiałem się przez jakiś czas, dlaczego Java i C # (i jestem pewien, że inne języki) domyślnie odnoszą się do równości ==
.
W programowaniu, które wykonuję (co z pewnością jest tylko niewielkim podzbiorem problemów programistycznych), prawie zawsze chcę logicznej równości podczas porównywania obiektów zamiast równości odniesienia. Próbowałem wymyślić, dlaczego oba te języki poszły tą drogą, zamiast odwracać ją i ==
być logiczną równością i używać .ReferenceEquals()
do odniesienia równości.
Oczywiście stosowanie równości referencyjnej jest bardzo proste do wdrożenia i zapewnia bardzo spójne zachowanie, ale nie wydaje się, aby dobrze pasowało do większości praktyk programistycznych, które widzę dzisiaj.
Nie chcę wydawać się nieświadomym problemów z próbą wprowadzenia logicznego porównania i że musi ono zostać zaimplementowane w każdej klasie. Zdaję sobie również sprawę, że te języki zostały zaprojektowane dawno temu, ale ogólne pytanie pozostaje bez odpowiedzi.
Czy jest jakaś znacząca zaleta, że nie wywiązuję się z tego, czego mi po prostu brakuje, czy też wydaje się uzasadnione, że domyślnym zachowaniem powinna być logiczna równość, a w przypadku powrotu do równości referencyjnej logiczna równość nie istnieje dla klasy?
źródło
Equals()
, to nie zmienia automatycznie zachowania==
?Odpowiedzi:
C # robi to, ponieważ Java. Java tak zrobiła, ponieważ Java nie obsługuje przeciążania operatora. Ponieważ równość wartości musi zostać na nowo zdefiniowana dla każdej klasy, nie mógł być operatorem, ale musiał być metodą. IMO to była zła decyzja. O wiele łatwiej jest pisać i czytać
a == b
niż oa.equals(b)
wiele bardziej naturalnie dla programistów z doświadczeniem w C lub C ++, alea == b
prawie zawsze się myli. Błędy z wykorzystaniem==
gdzie.equals
wymagany był zmarnowany niezliczone tysiące programistów godzin.źródło
==
wiele klas i kilka miesięcy temu odkryłem, że kilku programistów nie wiedziało, co==
właściwie robi. Zawsze istnieje takie ryzyko, gdy semantyka jakiegoś konstruktu nie jest oczywista.equals()
Zapis mówi mi, że używam niestandardową metodę i że muszę szukać go gdzieś. Podsumowując: Myślę, że przeciążenie operatora jest ogólnie kwestią otwartą.+
przykład, który jednocześnie dodaje (wartości liczbowe) i konkatenację łańcuchów.a == b
być bardziej naturalne dla programistów z doświadczeniem C, skoro C nie obsługuje przeciążenia operatora zdefiniowanego przez użytkownika? (Na przykład metoda C porównywania łańcuchówstrcmp(a, b) == 0
nie jesta == b
.)char *
. Wydaje mi się oczywiste, że porównanie dwóch wskaźników równości nie jest tym samym, co porównanie ciągów znaków.Krótka odpowiedź: spójność
Aby jednak odpowiedzieć na twoje pytanie, proponuję zrobić krok do tyłu i przyjrzeć się temu, co oznacza równość w języku programowania. Istnieją co najmniej TRZY różne możliwości, które są używane w różnych językach:
Te trzy typy równości są często używane, ponieważ są wygodne do wdrożenia: wszystkie trzy kontrole równości mogą być łatwo wygenerowane przez kompilator (w przypadku głębokiej równości kompilator może wymagać użycia bitów znaczników, aby zapobiec nieskończonym pętlom, jeśli struktura do porównania ma odniesienia cykliczne). Istnieje jednak inny problem: żaden z nich może nie być odpowiedni.
W systemach nietrywialnych równość obiektów jest często definiowana jako coś pomiędzy równością głęboką a równością odniesienia. Aby sprawdzić, czy chcemy uznać dwa obiekty za równe w pewnym kontekście, możemy wymagać porównania niektórych atrybutów z tym, gdzie stoi w pamięci, a innych głębokiej równości, podczas gdy niektóre atrybuty mogą być czymś zupełnie innym. To, co naprawdę chcielibyśmy, to „czwarty typ równości”, naprawdę miły, często nazywany w literaturze równością semantyczną . Rzeczy są równe, jeśli są równe, w naszej domenie. =)
Możemy więc wrócić do twojego pytania:
Co mamy na myśli, gdy piszemy „a == b” w dowolnym języku? Idealnie powinno być zawsze tak samo: równość semantyczna. Ale to nie jest możliwe.
Jednym z głównych założeń jest to, że przynajmniej dla prostych typów, takich jak liczby, oczekujemy, że dwie zmienne są równe po przypisaniu tej samej wartości. Patrz poniżej:
W tym przypadku oczekujemy, że „a jest równe b” w obu instrukcjach. Wszystko inne byłoby szalone. Większość (jeśli nie wszystkie) języków jest zgodna z tą konwencją. Dlatego dzięki prostym typom (czyli wartościom) wiemy, jak osiągnąć równość semantyczną. W przypadku obiektów może to być coś zupełnie innego. Patrz poniżej:
Oczekujemy, że pierwsze „jeśli” zawsze będzie prawdziwe. Ale czego oczekujesz od drugiego „jeśli”? To naprawdę zależy. Czy „DoSomething” może zmienić (semantyczną) równość aib?
Problem z równością semantyczną polega na tym, że kompilator nie może automatycznie wygenerować obiektów, ani nie wynika to z przypisań . Użytkownik musi zapewnić mechanizm definiowania równości semantycznej. W językach obiektowych mechanizm ten jest dziedziczoną metodą: równa się . Czytając fragment kodu OO, nie oczekujemy, że metoda będzie miała taką samą dokładną implementację we wszystkich klasach. Jesteśmy przyzwyczajeni do dziedziczenia i przeciążania.
Jednak w przypadku operatorów oczekujemy tego samego zachowania. Kiedy zobaczysz „a == b”, powinieneś spodziewać się tego samego rodzaju równości (od 4 powyżej) we wszystkich sytuacjach. Tak więc, dążąc do spójności , projektanci języków stosowali równość odniesienia dla wszystkich typów. Nie powinno to zależeć od tego, czy programista zastąpił metodę, czy nie.
PS: Język Dee jest nieco inny niż Java i C #: operator równości oznacza płytką równość dla prostych typów i równość semantyczną dla klas zdefiniowanych przez użytkownika (z odpowiedzialnością za wdrożenie operacji = leżącej po stronie użytkownika - nie podano domyślnej). Ponieważ w przypadku prostych typów płytka równość jest zawsze równością semantyczną, język jest spójny. Cena, którą płaci, polega jednak na tym, że operator równości jest domyślnie niezdefiniowany dla typów zdefiniowanych przez użytkownika. Musisz to zaimplementować. A czasem jest to po prostu nudne.
źródło
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.
Projektanci języka Java używali równości odniesienia dla obiektów i równości semantycznej dla prymitywów. Nie jest dla mnie oczywiste, że była to właściwa decyzja lub że ta decyzja jest bardziej „konsekwentna” niż pozwalająca==
na przeciążenie semantyczną równością obiektów.a
ib
są tego samego typu, wyrażeniea==b
sprawdza, czya
ib
trzyma to samo. Jeśli jeden z nich zawiera odniesienie do obiektu # 291, a drugi zawiera odniesienie do obiektu # 572, nie przechowują tego samego. Na zawartość obiektu # 291 i # 572 może być równoważne, ale same zmienne trzymać różne rzeczy.a == b
i wiedzieć, co robi. Podobnie możesz zobaczyća.equals(b)
i założyć, że przeciążenieequals
. Jeślia == b
połączeniaa.equals(b)
(jeśli są realizowane), czy są porównywane przez odniesienie czy treść? Nie pamiętasz Musisz sprawdzić klasę A. Kod nie jest już tak szybki do odczytania, jeśli nie jesteś nawet pewien, jak się nazywa. Byłoby tak, jakby dozwolone były metody o tej samej sygnaturze, a wywoływana metoda zależy od bieżącego zakresu. Takie programy byłyby niemożliwe do odczytania.Ponieważ to drugie podejście byłoby mylące. Rozważać:
Czy ten kod powinien się wydrukować
"ok"
, czy powinien rzucićNullPointerException
?źródło
W przypadku Java i C # korzyść polega na tym, że są zorientowane obiektowo.
Z punktu widzenia wydajności - łatwiejszy do pisania kod powinien również być szybszy: ponieważ OOP zamierza reprezentować logicznie odrębne elementy przez różne obiekty, sprawdzenie równości referencji byłoby szybsze, biorąc pod uwagę, że obiekty mogą stać się dość duże.
Z logicznego punktu widzenia - równość obiektu z innym nie musi być tak oczywista, jak porównanie właściwości obiektu dla równości (np. Jak logicznie interpretowana jest null == null? To może różnić się w zależności od przypadku).
Myślę, że sprowadza się to do spostrzeżenia, że „zawsze chcesz logicznej równości zamiast równości odniesienia”. Konsensus między projektantami języków był prawdopodobnie przeciwny. Osobiście trudno mi to ocenić, ponieważ brakuje mi szerokiego spektrum doświadczenia w programowaniu. Z grubsza używam równości odniesienia bardziej w algorytmach optymalizacyjnych, a równości logicznej więcej w obsłudze zestawów danych.
źródło
.equals()
porównuje zmienne według ich zawartości. zamiast==
tego porównuje obiekty według ich zawartości ...używanie obiektów jest bardziej dokładne
.equals()
źródło