Jak zastąpić metodę equals w Javie

108

Próbuję przesłonić metodę równości w Javie. Mam klasę, Peoplektóra w zasadzie ma 2 pola danych namei age. Teraz chcę przesłonić equalsmetodę, aby móc sprawdzić między 2 obiektami People.

Mój kod jest następujący

public boolean equals(People other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(other.name) &&  age.equals(other.age);
    } // end else

    return result;
} // end equals

Ale kiedy piszę, age.equals(other.age)to daje mi błąd, ponieważ metoda equals może tylko porównać String, a wiek jest Integer.

Rozwiązanie

Użyłem ==operatora zgodnie z sugestią i mój problem został rozwiązany.

bóbr
źródło
3
Hej, a co powiesz na this.age == other.age? :)
denis.solonenko
1
Jaki jest typ danych dotyczących wieku? int OR Integer? Ponadto jakiej wersji JDK używasz?
Manish
2
„metoda równa się może tylko porównywać String” - Kto ci powiedział, że metoda równa się może tylko porównywać String? equals należą do klasy Object, a każda utworzona klasa będzie miała domyślnie implementację equals. Możesz zadzwonić do równych sobie na DOWOLNE zajęcia Java
Manish

Odpowiedzi:

127
//Written by K@stackoverflow
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        ArrayList<Person> people = new ArrayList<Person>();
        people.add(new Person("Subash Adhikari", 28));
        people.add(new Person("K", 28));
        people.add(new Person("StackOverflow", 4));
        people.add(new Person("Subash Adhikari", 28));

        for (int i = 0; i < people.size() - 1; i++) {
            for (int y = i + 1; y <= people.size() - 1; y++) {
                boolean check = people.get(i).equals(people.get(y));

                System.out.println("-- " + people.get(i).getName() + " - VS - " + people.get(y).getName());
                System.out.println(check);
            }
        }
    }
}

//written by K@stackoverflow
    public class Person {
        private String name;
        private int age;

        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            
if (obj.getClass() != this.getClass()) {
                return false;
            }



            final Person other = (Person) obj;
            if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
                return false;
            }

            if (this.age != other.age) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
            hash = 53 * hash + this.age;
            return hash;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

Wynik:

biegać:

- Subash Adhikari - VS - K false

- Subash Adhikari - VS - StackOverflow false

- Subash Adhikari - VS - Subash Adhikari prawda

- K - VS - StackOverflow false

- K - VS - Subash Adhikari false

- StackOverflow - VS - Subash Adhikari false

- BUDOWANIE POWIODŁO SIĘ (całkowity czas: 0 sekund)

Kim
źródło
7
co jest hash = 53 * hashdlaczego używasz takiego?
kittu
2
Użycie getClass()spowoduje problemy, jeśli klasa zostanie podklasy i zostanie porównana z obiektem nadklasy.
Tuxdude
1
może być bcoz 53 to liczba pierwsza , spójrz na tę odpowiedź stackoverflow.com/a/27609/3425489 , skomentował przy wyborze liczb whashCode()
Shantaram Tupe
1
Zwycięska odpowiedź na to pytanie ma doskonałe wyjaśnienie, dlaczego zastępujesz hashCode () stackoverflow.com/a/27609/1992108
Pegasaurus
7
Rozważ użycie if (getClass ()! = Obj.getClass ()) ... zamiast używania instanceofoperatora lub isAssignableFrom. Będzie to wymagało dokładnego dopasowania typu, a nie dopasowania podtypu. - Wymaganie symetryczne. StringMożesz także porównać lub inne typy obiektów, których możesz użyć Objects.equals(this.name,other.name).
YoYo
22

Wprowadzenie nowej sygnatury metody, która zmienia typy parametrów, nazywa się przeciążeniem :

public boolean equals(People other){

Tutaj Peoplejest inaczej niż Object.

Gdy sygnatura metody pozostaje identyczna z sygnaturą jej nadklasy, nazywa się ją nadpisywaniem, a @Overrideadnotacja pomaga rozróżnić te dwa elementy w czasie kompilacji:

@Override
public boolean equals(Object other){

Nie widząc rzeczywistej deklaracji age, trudno powiedzieć, dlaczego pojawia się błąd.

fortran
źródło
18

Nie jestem pewien szczegółów, ponieważ nie opublikowałeś całego kodu, ale:

  • Pamiętaj, aby zastąpić hashCode()także
  • equalsmetoda powinna mieć Object, a nie Peoplejako jego typu argumentów. W tej chwili przeciążasz, a nie nadpisujesz, metodę equals, która prawdopodobnie nie jest tym, czego chcesz, zwłaszcza biorąc pod uwagę, że później sprawdzisz jej typ.
  • możesz użyć, instanceofaby sprawdzić, czy jest to obiekt People, npif (!(other instanceof People)) { result = false;}
  • equalsjest używany dla wszystkich obiektów, ale nie prymitywów. Myślę, że masz na myśli wiek jest int(prymitywny), w takim przypadku po prostu użyj ==. Zauważ, że liczba całkowita (przez duże „I”) to obiekt, który należy porównać z równymi.

Zobacz Jakie problemy należy wziąć pod uwagę podczas zastępowania równości i hashCode w Javie? po więcej szczegółów.

Adrian Mouat
źródło
12
@Override
public boolean equals(Object that){
  if(this == that) return true;//if both of them points the same address in memory

  if(!(that instanceof People)) return false; // if "that" is not a People or a childclass

  People thatPeople = (People)that; // than we can cast it to People safely

  return this.name.equals(thatPeople.name) && this.age == thatPeople.age;// if they have the same name and same age, then the 2 objects are equal unless they're pointing to different memory adresses
}
NeverJr
źródło
12

Punkt 10: Przestrzegaj umowy generalnej, gdy ma pierwszeństwo

Według Effective Java , zastąpienie equalsmetody wydaje się proste, ale istnieje wiele sposobów, aby to zrobić źle, a konsekwencje mogą być tragiczne. Najłatwiejszym sposobem uniknięcia problemów jest nie nadpisywanie equalsmetody, w którym to przypadku każda instancja klasy jest równa tylko sobie. Jest to właściwe postępowanie, jeśli ma zastosowanie którykolwiek z poniższych warunków:

  • Każda instancja klasy jest z natury wyjątkowa . Dotyczy to klas, takich jak Thread, które reprezentują aktywne jednostki, a nie wartości. Implementacja equals dostarczona przez Object ma dokładnie właściwe zachowanie dla tych klas.

  • Klasa nie musi przeprowadzać testu „logicznej równości”. Na przykład java.util.regex.Pattern mógł przesłonić równa się, aby sprawdzić, czy dwa wystąpienia Patternu reprezentują dokładnie to samo wyrażenie regularne, ale projektanci nie sądzili, że klienci będą potrzebować lub chcieć tej funkcji. W takich okolicznościach implementacja equals odziedziczona po Object jest idealna.

  • Nadklasa już zastąpiła równa się i zachowanie nadklasy jest odpowiednie dla tej klasy. Na przykład większość implementacji Set dziedziczy implementacje equals z AbstractSet, implementacje List z AbstractList i implementacje Map z AbstractMap.

  • Klasa jest prywatna lub prywatna dla pakietu i masz pewność, że jej metoda equals nigdy nie zostanie wywołana. Jeśli jesteś wyjątkowo niechętny ryzyku, możesz zastąpić metodę equals, aby upewnić się, że nie zostanie wywołana przypadkowo:

equalsSposób realizuje stosunek równoważności. Posiada następujące właściwości:

  • Odruchowy: Dla każdej wartości referencyjnej niż null x, x.equals(x)należy wrócić prawdziwe.

  • Symetryczne: dla wszelkich wartości odniesienia niezerowych xi y, x.equals(y)musi zwracać wartość true wtedy i tylko wtedy, gdy y.equals (x) zwraca wartość true.

  • Przechodni: Dla każdej wartości odniesienia nie zerowych x, y, zjeżeli x.equals(y)wraca truei y.equals(z)zwrotów true, to x.equals(z)należy zwrócić true.

  • Spójne: w przypadku wszelkich niezerowych wartości odniesienia xi ywielokrotne wywołania x.equals(y)muszą konsekwentnie zwracać truelub konsekwentnie zwracać false, pod warunkiem, że żadne informacje używane w porównaniach równości nie są modyfikowane.

  • Dla każdej wartości referencyjnej niż null x, x.equals(null)musi powrócić false.

Oto przepis na metodę równa się wysokiej jakości:

  1. Użyj ==operatora, aby sprawdzić, czy argument jest odniesieniem do tego obiektu. Jeśli tak, zwróć wartość true. To tylko optymalizacja wydajności, ale warto to zrobić, jeśli porównanie jest potencjalnie drogie.

  2. Użyj instanceofoperatora, aby sprawdzić, czy argument ma poprawny typ. Jeśli nie, zwróć false. Zwykle prawidłowym typem jest klasa, w której występuje metoda. Czasami jest to jakiś interfejs zaimplementowany przez tę klasę. Użyj interfejsu, jeśli klasa implementuje interfejs, który udoskonala kontrakt equals, aby umożliwić porównania między klasami, które implementują interfejs. Tę właściwość mają interfejsy kolekcji, takie jak Set, List, Map i Map.Entry.

  3. Prześlij argument na właściwy typ. Ponieważ rzutowanie było poprzedzone testem instancji, gwarantujemy sukces.

  4. Dla każdego „znaczącego” pola w klasie sprawdź, czy to pole argumentu pasuje do odpowiedniego pola tego obiektu. Jeśli wszystkie te testy zakończą się powodzeniem, zwraca true; w przeciwnym razie zwraca false. Jeśli typ w kroku 2 to interfejs, musisz uzyskać dostęp do pól argumentu za pomocą metod interfejsu; jeśli typ jest klasą, możesz mieć bezpośredni dostęp do pól, w zależności od ich dostępności.

  5. W przypadku pól pierwotnych, których typem nie jest floatlub double, ==do porównań użyj operatora; w przypadku pól odwołań do obiektów wywołaj equalsmetodę rekurencyjnie; w przypadku floatpól użyj Float.compare(float, float)metody statycznej ; a na doublepolach użyj Double.compare(double, double). Szczególne traktowanie pływaka i podwójnych obszarach jest konieczne ze względu na istnienie Float.NaN, -0.0fa analogicznymi wartościami podwójnych; Chociaż można porównać floati doublepola za pomocą metod statycznych Float.equalsi Double.equals, wiązałoby się to z automatycznym blokowaniem każdego porównania, co miałoby słabą wydajność. W przypadku arraypól zastosuj te wytyczne do każdego elementu. Jeśli każdy element w polu tablicy jest znaczący, użyj jednej z Arrays.equalsmetod.

  6. Niektóre pola odwołań do obiektów mogą zgodnie z prawem zawierać null. Aby uniknąć możliwości NullPointerException, należy sprawdzić takie pola pod kątem równości przy użyciu metody statycznej Objects.equals(Object, Object).

    // Class with a typical equals method
    
    public final class PhoneNumber {
    
        private final short areaCode, prefix, lineNum;
    
        public PhoneNumber(int areaCode, int prefix, int lineNum) {
    
            this.areaCode = rangeCheck(areaCode,  999, "area code");
    
            this.prefix   = rangeCheck(prefix,    999, "prefix");
    
            this.lineNum  = rangeCheck(lineNum,  9999, "line num");
    
        }
    
        private static short rangeCheck(int val, int max, String arg) {
    
            if (val < 0 || val > max)
    
               throw new IllegalArgumentException(arg + ": " + val);
    
            return (short) val;
    
        }
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNum == lineNum && pn.prefix == prefix
                    && pn.areaCode == areaCode;
        }
        ... // Remainder omitted
    
    }
    

źródło
1
Nie zapomnij wspomnieć, że musisz również nadpisać hashCode(). Również pamiętać, że od Java7 piśmie equals()i hashCode()metod stało się znacznie łatwiejsze przy użyciu Objects.equals(), Arrays.equals()a Objects.hashCode(), Arrays.hashCode().
Arnold Schrijver
3
Rozważ użycie if (getClass() != obj.getClass()) ...zamiast używania operatora instanceof. Będzie to wymagało dokładnego dopasowania typu, a nie dopasowania podtypu. - Wymaganie symetryczne.
YoYo
@YoYo jest poprawne ... użycie instanceof może spowodować błąd właściwości symetrycznej. Jeśli o jest podklasą PhoneNumber, jak może PhoneNumberWithExtension, i zastępuje równa się w ten sam sposób, używając instanceof, to o.equals (this) nie powiedzie testu instanceof, podczas gdy PhoneNumber.equals przejdzie go i zwróci true (zakładając, że wszystkie inne pola PhoneNumber są równe).
ldkronos
5

Ponieważ zgaduję, agejest typu int:

public boolean equals(Object other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(otherPeople.name) &&  age == otherPeople.age;
    } // end else

    return result;
} // end equals
Luchian Grigore
źródło
Spowoduje to, NullPointerExceptionjeśli namejest null.
orien
@orien Nic wielkiego, może jest to w umowie, która namenigdy nie ma przypisanej nullwartości ...
fortran
@fortran Więc ... może to nic wielkiego;)
orien
5

Porównując obiekty w Javie, wykonujesz kontrolę semantyczną , porównując typ i określając stan obiektów z:

  • sama (ta sama instancja)
  • sama (sklonowana lub zrekonstruowana kopia)
  • inne obiekty różnego typu
  • inne obiekty tego samego typu
  • null

Zasady:

  • Symetria :a.equals(b) == b.equals(a)
  • equals()zawsze ustępuje truelub false, ale nigdy NullpointerException, ClassCastExceptionani żaden inny przedmiot do rzucania

Porównanie:

  • Sprawdzanie typu : obie instancje muszą być tego samego typu, co oznacza, że ​​należy porównać rzeczywiste klasy pod kątem równości. Często jest to niepoprawnie zaimplementowane, gdy programiści używają instanceofdo porównywania typów (które działa tylko tak długo, jak nie ma podklas i narusza regułę symetrii, kiedy A extends B -> a instanceof b != b instanceof a).
  • Semantyczna kontrola stanu identyfikacji : upewnij się, że rozumiesz, według którego stanu są identyfikowane instancje. Osoby można rozpoznać po numerze ubezpieczenia społecznego, ale nie po kolorze włosów (można je farbować), nazwisku (można zmienić) czy wieku (cały czas się zmienia). Tylko z obiektami wartości należy porównywać stan pełny (wszystkie pola nieprzejściowe), w przeciwnym razie sprawdzać tylko to, co identyfikuje instancję.

Dla Twojej Personklasy:

public boolean equals(Object obj) {

    // same instance
    if (obj == this) {
        return true;
    }
    // null
    if (obj == null) {
        return false;
    }
    // type
    if (!getClass().equals(obj.getClass())) {
        return false;
    }
    // cast and compare state
    Person other = (Person) obj;
    return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}

Ogólna klasa użytkowa wielokrotnego użytku:

public final class Equals {

    private Equals() {
        // private constructor, no instances allowed
    }

    /**
     * Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
     *
     * @param instance       object instance (where the equals() is implemented)
     * @param other          other instance to compare to
     * @param stateAccessors stateAccessors for state to compare, optional
     * @param <T>            instance type
     * @return true when equals, false otherwise
     */
    public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
        if (instance == null) {
            return other == null;
        }
        if (instance == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (!instance.getClass().equals(other.getClass())) {
            return false;
        }
        if (stateAccessors == null) {
            return true;
        }
        return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
    }
}

Dla swojej Personklasy, używając tej klasy użytkowej:

public boolean equals(Object obj) {
    return Equals.as(this, obj, t -> t.name, t -> t.age);
}
Peter Walser
źródło
1

jeśli wiek jest równy int, powinieneś użyć ==, jeśli jest to obiekt Integer, możesz użyć equals (). Musisz również zaimplementować metodę hashcode, jeśli nadpisujesz równa się. Szczegóły umowy są dostępne w javadoc of Object, a także na różnych stronach internetowych.

Ashwinee K Jha
źródło
0

Oto rozwiązanie, z którego ostatnio korzystałem:

public class Test {
    public String a;
    public long b;
    public Date c;
    public String d;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Test)) {
            return false;
        }
        Test testOther = (Test) obj;
        return (a != null ? a.equals(testOther.a) : testOther.a == null)
                && (b == testOther.b)
                && (c != null ? c.equals(testOther.c) : testOther.c == null)
                && (d != null ? d.equals(testOther.d) : testOther.d == null);
    }

}
SSharma
źródło