2017-01-25 32 views
5

pracuję nad projektem, gdzie wiele klas potrzebują odpowiedniego typowe implementacje equals i hashCode: każda klasa ma zestaw końcowych pól zainicjowany w budownictwie z „głęboko” niezmiennych obiektów (null s są przeznaczone do zaakceptowania w niektórych przypadkach) do wykorzystania do mieszania i porównywania.wielokrotnego użytku realizacja równych i hashcode

Aby zmniejszyć ilość kodu standardowego, pomyślałem o napisaniu klasy abstrakcyjnej zapewniającej powszechne implementacje takiego zachowania.

public abstract class AbstractHashable { 

    /** List of fields used for comparison. */ 
    private final Object[] fields; 

    /** Precomputed hash. */ 
    private final int hash; 

    /** 
    * Constructor to be invoked by subclasses. 
    * @param fields list of fields used for comparison between objects of this 
    * class, they must be in constant number for each class 
    */ 
    protected AbstractHashable(Object... fields) { 
     this.fields = fields; 
     hash = 31 * getClass().hashCode() + Objects.hash(fields); 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (obj == this) { 
      return true; 
     } 
     if (obj == null || !getClass().equals(obj.getClass())) { 
      return false; 
     } 
     AbstractHashable other = (AbstractHashable) obj; 
     if (fields.length != other.fields.length) { 
      throw new UnsupportedOperationException(
        "objects of same class must have the same number of fields"); 
     } 
     for (int i=0; i<fields.length; i++) { 
      if (!fields[i].equals(other.fields[i])) { 
       return false; 
      } 
     } 
     return true; 
    } 

    @Override 
    public int hashCode() { 
     return hash; 
    } 

} 

ten ma być stosowany tak:

public class SomeObject extends AbstractHashable { 

    // both Foo and Bar have no mutable state 
    private final Foo foo; 
    private final Bar bar; 

    public SomeObject(Foo foo, Bar bar) { 
     super(foo, bar); 
     this.foo = Objects.requireNonNull(foo); 
     this.bar = bar; // null supported 
    } 

    // other methods, no equals or hashCode needed 

} 

To jest w zasadzie to, co zaproponował here z pewnymi różnicami.

To wydaje mi się proste, ale dobre podejście do zmniejszenia gadatliwości i nadal mają skuteczne wdrożenia equals i hashCode. Jednakże, ponieważ nie przypominam sobie, aby kiedykolwiek widziałem coś podobnego (z wyjątkiem odpowiedzi powiązanej powyżej), chciałbym zapytać konkretnie, czy jest jakiś punkt przeciwko takiemu podejściu (lub ewentualnie pewnej poprawie, którą można by zastosować), przed zastosowaniem w całym projekcie.

+1

Widzę już dwie kwestie. 1. Dwa obiekty będą traktowane jednakowo, jeśli * wszystkie * ich pola będą zgodne. To nie zawsze może być to, czego chcesz. 2. Klasa może rozszerzyć tylko o jedną klasę. Czy naprawdę chcesz zablokować swoje klasy z dziedziczenia innych klas, ponieważ ponowne użycie 'hash' i' hashCode' jest ważniejsze? – CKing

Odpowiedz

5

Widzę dwa problemy z tym podejściem już:

  1. Dwa obiekty zostaną uznane za równe wtedy i tylko wtedy, gdy wszystkie ich pola pasuje. Nie zawsze tak jest w prawdziwym świecie.
  2. Wykonując klasy extend z AbstractHashable, nie można już extend z żadnych innych klas. Jest to dość wysoka cena za ponowne użycie equals i hashCode.

Pierwszy problem można rozwiązać, przekazując więcej metadanych do klasy AbstractHashable, co pozwala określić, które pola są opcjonalne. Na przykład można przekazać kolejną tablicę do AbstractHashTable, która zawiera pozycje indeksu, które mają zostać zignorowane jako elementy za pomocą narzędzia ustawiającego. Drugi problem można rozwiązać, korzystając z kompozycji Kompozycja. Zamiast rozszerzania AbstractHashTable, zmień go tak, aby mógł nawiązać relację z użytkownikami, a nie relację.

Jednakże, jak ja nie przypominam sobie, aby kiedykolwiek widział coś podobnego (z wyjątkiem odpowiedzi połączonego powyżej), chciałbym zapytać, czy konkretnie jest tam jakiś punkt przed takim podejściem

ten podejście zdecydowanie wpłynie na czytelność Twojego kodu. Jeśli możesz wymyślić bardziej czytelne podejście (powiedzmy, używając adnotacji), nie widzę niczego złego w chęci ponownego użycia implementacji equals i hashCode.

To wszystko zostało powiedziane, nowoczesne IDE takie jak Eclipse można łatwo wygenerować realizację equals i hashCode dla ciebie tak jest naprawdę istnieje potrzeba, aby wymyślić rozwiązanie tego rodzaju? Wierzę, że nie.

+1

Dzięki, to są dwa interesujące punkty (szczególnie pierwszy, o którym nie myślałem). Autogeneration by Eclipse jest tym, czego używałem, gdy wpadłem na to pytanie: nie podobał mi się cały kod podstawowy, nawet jeśli jest on generowany automatycznie (również dlatego, że zmiany takie jak dodanie pola stają się bardziej podatne na błędy), ale prawdopodobnie zajmę się tym na razie. – rrobby86

0

Opierając się na moim doświadczeniu, wydaje mi się to złe, ponieważ zwiększysz błąd, który możesz uzyskać w czasie wykonywania w porównaniu do czasu kompilacji (pamiętaj, że wszystkie użycie tych obiektów na listach itp. Może teraz spowodować nieoczekiwane zachowanie). Co jeśli kolejność pól w klasach jest inna? Po drugie, czy nadużywasz dziedziczenia? Maybee wybiera pewien framework (https://projectlombok.org/), który generuje kod hashcode i jest równy adnotacji?

+0

* Co się dzieje, jeśli kolejność pól w klasach jest inna *. To nigdy nie będzie problemem, ponieważ konstruktor w podklasach (na przykład 'SomeObject') zawsze będzie wymuszał stałą kolejność, w której pola będą intializowane. Poza tym porównujesz obiekty tej samej klasy, więc jak zmieni się kolejność pól? – CKing

+0

Masz rację, porównujesz obiekty tej samej klasy, ale dlaczego masz, jeśli (fields.length! = Other.fields.length)? – HoleInVoid

+0

OP dodała tę kontrolę w skrajnych przypadkach, w których programiści nie używają tego samego konstruktora do tworzenia instancji obiektu. – CKing

0

Rozwiązanie powinno działać.

Jednak czuje się nieco słaby z punktu widzenia OOP:

  • jesteś kopiowanie danych (pola w nadrzędnej vs polu Object), dzięki czemu obiekt jest dwa razy większy; także jeśli kiedykolwiek potrzeba, aby uczynić je zmienny, którą trzeba utrzymać stan, w dwóch miejscach
  • hierarchii klasa jest zmuszony, tylko do zapewnienia równych/hashCode

Nowoczesne IDE wygenerować takie metody dość łatwo, jeśli chcesz, możesz również użyć frameworków (takich jak Lombok) lub bibliotek (takich jak Guava's Objects/Java 8's Objects), aby uprościć te metody.

0

Proponuję rzucić okiem na klasy Apache'a: HashCodeBuilder i EqualsBuilder. Ma również oparte na refleksach metody, takie jak reflectionHashCode i reflectionEquals.

+0

Dziękuję za sugestię. Jednak odbicie nie ma optymalnych osiągów i myślę, że może być zauważalne dla obiektu posiadającego duży wykres zależnych obiektów (co może się zdarzyć w moim przypadku). – rrobby86

Powiązane problemy