2010-12-11 4 views
3

Ich denke daran, eine reflektive Hilfsmethode für equals und hashCode zu erstellen.Generische reflektive Hilfsmethode für equals und hashCode

  • Im Falle von equals schaut die Hilfsmethode über die Reflection API zu den Feldern von objectA und vergleicht sie mit Feldern von objectB.
  • Im Falle von hashCode schaut die Hilfsmethode über die Reflection API zu den Feldern und berechnet einen HashCode in einer Iterationsschleife.
  • Die gute Sache ist, dass ich keine Sorge über fehlende Felder in meiner Equals oder HashCode-Implementierung habe. Das Schlimme ist, denke ich, Leistung. Was denkst du über diese Idee? Bitte teilen Sie Ihre Meinung!

    Dies ist mein erster Entwurf für equals:

    public final class ReflectiveEqualsHelper { 
    
    public static boolean isEqual(final Object a, final Object b) { 
        if (!isTypeEqual(a, b)) { 
         return false; 
        } 
    
        Field[] fields = getFields(a); 
    
        Object valueA; 
        Object valueB; 
        String fieldName; 
        for (int i = 0; i < fields.length; i++) { 
         fieldName = fields[i].getName(); 
         valueA = getValueByFieldName(a, fieldName); 
         valueB = getValueByFieldName(b, fieldName); 
         if (!compare(valueA, valueB)) { 
          return false; 
         } 
        } 
        return true; 
    } 
    
    @SuppressWarnings("rawtypes") 
    private static Field[] getFields(final Object o) { 
        Class clazz = o.getClass(); 
        Field[] fields = clazz.getDeclaredFields(); 
        return fields; 
    } 
    
    private static Field getField(final Object o, final String name) { 
        try { 
         Field field = o.getClass().getDeclaredField(name); 
         return field; 
        } catch (NoSuchFieldException e) { 
         throw new RuntimeException(e); 
        } 
    } 
    
    private static Object getValueByFieldName(final Object o, final String name) { 
        Field field = getField(o, name); 
        field.setAccessible(true); 
    
        try { 
         Object value = field.get(o); 
         field.setAccessible(false); 
         return value; 
        } catch (IllegalAccessException e) { 
         throw new RuntimeException(e); 
        } 
    
    } 
    
    private static boolean areBothNull(final Object a, final Object b) { 
        return (a == null && b == null); 
    } 
    
    private static boolean isTypeEqual(final Object a, final Object b) { 
        if (areBothNull(a, b)) { 
         return false; 
        } 
    
        return a.getClass().equals(b.getClass()); 
    } 
    
    private static boolean compare(final Object a, final Object b) { 
        if (a == null) { 
         return false; 
        } else if (b == null) { 
         return false; 
        } 
        return a.equals(b); 
    } 
    

    }

    public class ReflectiveEqualsHelperTest { 
    
    @Test 
    public void testIsEqual() { 
        Vector a = new Vector(Long.valueOf(1L), 3L); 
        Vector b = new Vector(Long.valueOf(1L), 3L); 
        Vector c = new Vector(Long.valueOf(2L), 3L); 
        boolean testA = ReflectiveEqualsHelper.isEqual(a, b); 
        boolean testB = ReflectiveEqualsHelper.isEqual(a, c); 
        boolean testC = ReflectiveEqualsHelper.isEqual(b, c); 
        assertTrue(testA); 
        assertFalse(testB); 
        assertFalse(testC); 
    } 
    
    class Vector { 
        public static final int STATIC = 1; 
    
        private Long x; 
        private long y; 
    
        public Vector(Long x, long y) { 
         super(); 
         this.x = x; 
         this.y = y; 
        } 
    
        public Long getX() { 
         return x; 
        } 
    
        public void setX(Long x) { 
         this.x = x; 
        } 
    
        public long getY() { 
         return y; 
        } 
    
        public void setY(long y) { 
         this.y = y; 
        } 
    
        @Override 
        public int hashCode() { 
         final int prime = 31; 
         int result = 1; 
         result = prime * result + ((x == null) ? 0 : x.hashCode()); 
         result = prime * result + (int) (y^(y >>> 32)); 
         return result; 
        } 
    
        @Override 
        public boolean equals(Object obj) { 
         if (this == obj) { 
          return true; 
         } 
         return ReflectiveEqualsHelper.isEqual(this, obj); 
        } 
    } 
    

    }

    Cheers, Kevin

    Antwort

    2

    Dies zu teuer sein wird. Diese Methoden werden viel häufiger aufgerufen, als Sie erwarten würden. Tu es nicht. Verwenden Sie stattdessen ein bisschen anständige IDE wie Eclipse, IntelliJ oder Netbeans und lassen Sie sie die equals() und hashCode() automatisch generieren. In Eclipse zum Beispiel können Sie dies tun, indem Sie irgendwo im Quellcode rechtsklicken>Quelle> HashCode generieren und gleich.

    alt text

    +0

    Ich mache das mit der Sonnenfinsternis und es funktioniert gut für mich Ich habe nur über flexiblere Weise nachgedacht. – eglobetrotter

    +0

    riesige PITA zu * versuchen * und erinnern Sie sich, diese Methoden jedes Mal wieder zu generieren, wenn Sie ein Feld hinzufügen oder entfernen. mehr als oft nicht, Leute werden vergessen, zu subtilen, schwer zu finden Bugs. Sie haben natürlich Recht mit den Kosten der reflektierenden Lösung. –

    4

    einen Blick auf EqualsBuilder in Apache Commons nehmen und es ist reflectionEquals Methoden. Diese Bibliothek hat auch eine HashCodeBuilder + viele andere nützliche Sachen.

    +0

    Cool sieht interessant aus, haben Sie Erfahrung mit dieser Utility-Klasse? Vor allem mit Leistung? – eglobetrotter

    +0

    Ja, ich benutze es für Projekte bei meiner Arbeit. Obwohl aufgrund von Leistung und Flexibilität (möglicherweise sind nicht alle Felder für #equals geeignet), tendiere ich dazu, nicht die # reflectionEquals-Methode, sondern den "Standardweg" mit #append zu verwenden. Sicher, es gibt einige mühsame Programmierung mit #append aber immer noch viel bequemer als das Schreiben aller Kesselplatte entspricht Überprüfung für Hand :) – Uhlen

    +0

    Googles Guava hat auch einen hashCode und toString-Helfer, für Gleiches würde ich Commons EqualsBuilder bleiben. Wenn Sie equals implementieren, müssen Sie darauf achten, dass es mit Ihrer hasCode-Implementierung konsistent ist. –

    2

    Leistung ist sicherlich eine große Sorge wegen der Verwendung von Reflexion, aber es gibt andere.

    Manchmal möchten Sie nicht alle Felder verwenden. Insbesondere bei einer selbstreferentiellen Struktur könnte dies zu einer möglicherweise unendlichen Rekursion führen.

    +0

    Rekursion ist ein guter Punkt, danke! – eglobetrotter

    3

    Ich würde empfehlen Guava's Objects.hashCode. Zum Beispiel:

    public int hashCode() { 
        return Objects.hashCode(getX(), getY(), getZ()); 
    } 
    

    Die Objects.equals Methode sollten Sie Ihre isEquals Methode helfen zu bauen.

    +1

    ... was nichts anderes ist als 'Arrays.hashCode()', also kein Grund, die ganze Guava einzufangen. –

    1

    Ich änderte meine Meinung, um den reflektiven Ansatz wegen des Leistungsproblems zu verwenden. Jetzt verwende ich die EqualsBuilder und die HashCodeBuilder Dienstprogramme des Apache Commons-Projekts (Danke für die Vorschläge und das Feedback), weil sie die Komplexität der Methoden verbergen. Für die schnelle Erzeugung der #equals und Methode verwende ich die Fast Code Eclipse plugin mit einer angepassten Codevorlage:


    <template type="EQUALS_AND_HASHCODE_METHOD"> 
        <variation></variation> 
        <variation-field></variation-field> 
        <allow-multiple-variation></allow-multiple-variation> 
        <class-pattern></class-pattern> 
        <allowed-file-extensions>java</allowed-file-extensions> 
        <number-required-classes>1</number-required-classes> 
        <description>Generates the equals and hashCode method with EqualsBuilder and HashCodeBuilder</description> 
        <template-body> 
        <![CDATA[ 
         @Override 
         public boolean equals(final Object obj) { 
         if (obj == null) { 
          return false; 
         } 
         if (obj == this) { 
          return true; 
         } 
         if (obj.getClass() != getClass()) { 
          return false; 
         } 
    
         ${class_name} rhs = (${class_name}) obj; 
         return new EqualsBuilder().appendSuper(super.equals(obj)) 
         #foreach ($field in ${fields}) 
          .append(${field.name}, rhs.${field.name}) 
         #end 
          .isEquals(); 
         } 
    
         @Override 
         public int hashCode() { 
         return new HashCodeBuilder(17, 37).appendSuper(super.hashCode()) 
         #foreach ($field in ${fields}) 
          .append(${field.name}) 
         #end 
          .toHashCode(); 
         } 
        ]]> 
        </template-body> 
    </template> 
    

    ich Schnell-Code-Plugin bin mit, weil es in der Lage, alle Felder einer ausgewählte Klasse grap. Aber ich bin nicht glücklich mit der Benutzerfreundlichkeit des Plugins.Es wäre schön, wenn die Eclipse-Code-Templating-Engine das auch könnte. Wenn jemand ein ähnliches Plugin kennt, dann lass es mich bitte wissen!

    Prost, Kevin