2009-09-10 6 views
4

Si j'ai une simple classe Groovy tels queGroovy: générer des égaux et des méthodes hashCode

class Address { 

    Integer streetNumber 
    String streetName 
    String state 
    String zip 
    Country country  
} 

Bien que je pourrais écrire (ou utiliser un IDE pour générer) hashCode et equals méthodes comme:

boolean equals(o) { 
    if (this.is(o)) return true; 

    if (!o || getClass() != o.class) return false; 

    Address that = (Address) o; 

    if (streetNumber? !streetNumber.equals(that.streetNumber) : that.streetNumber!= null) return false; 
    if (streetName? !streetName.equals(that.streetName) : that.streetName!= null) return false; 
    if (state? !state.equals(that.state) : that.state!= null) return false; 
    if (zip? !zip.equals(that.zip) : that.zip!= null) return false; 
    if (country? !zip.equals(that.zip) : that.zip!= null) return false; 

    return true; 
} 

int hashCode() { 
    int result = (streetNumber ? streetNumber.hashCode() : 0); 
    result = 31 * result + (streetName ? streetName.hashCode() : 0); 
    result = 31 * result + (state ? state.hashCode() : 0); 
    result = 31 * result + (zip ? zip.hashCode() : 0); 
    return 31 * result + (country ? country.hashCode() : 0); 
} 

Bien que cela fonctionne bien, je pense que je pourrais faire un meilleur usage du dynamisme de Groovy pour obtenir la même chose avec beaucoup moins de code. Une approche qui vient à l'esprit utilise .properties pour obtenir une carte des noms de propriété et des valeurs d'un objet. Je peux alors itérer sur ces propriétés, en appelant hashCode() ou equals() sur chacun d'eux pour obtenir le même résultat que ci-dessus. Avant de suivre ce chemin, je veux juste vérifier si quelqu'un d'autre a trouvé une bonne solution à ce problème. Je suis un peu méfiant de rouler ma propre solution, car les conséquences de foirer equals() ou hashCode() sont potentiellement désastreuses et difficiles à traquer.

Merci, Don

Répondre

11

Je ne suis pas un développeur groovy, mais j'ai compris que depuis groovy 1.8 vous pouvez invoquer la transformation AST en utilisant @EqualsAndHashCode sur le type.

+0

FYI, cette fonctionnalité n'est disponible que dans Groovy 1.8-beta-1. Groovy 1.8 est prévu pour la fin de 2010. Voir ici pour plus de détails http://groovy.codehaus.org/News –

+2

et notez qu'il ne considère que _properties_ (pas les champs qui ont un public/protected/modificateur d'accès privé) sauf si vous spécifiez * includeFields = true * sur l'annotation –

5

Ou vous pouvez simplement utiliser l » EqualsBuilder et HashCodeBuilderApache Commons Lang. Vous pouvez soit laisser les constructeurs utiliser Reflection de sorte qu'il va évaluer tous les champs ou identifier quel champ doit être inclus dans les calculs equals() et hashCode(). Ils ont également un ToStringBuilder si vous êtes intéressé.

+0

Merci, cela fonctionne très bien dans Groovy! –

+0

Un mot d'avertissement - les méthodes de réflexion ont un coût de performance significatif, il est donc généralement préférable d'éviter ceux-ci. –

2

Si vous voulez une solution purement Groovy, vous pouvez faire quelque chose comme ceci:

interface DefaultEquality {} 

DefaultEquality.metaClass.hashCode = { 
    delegate.properties.inject(1) { hash, property -> 
     if (property.key == "class" || property.key == "metaClass") { 
      hash 
     } else { 
      31 * hash + (property.value?.hashCode() ?: 0) 
     } 
    } 
} 

DefaultEquality.metaClass.equals = { obj -> 
    def outerDelegate = delegate 
    outerDelegate.properties.inject(true) { equals, property -> 
     if (property.key == "metaClass") { 
      equals 
     } else { 
      equals && outerDelegate[property.key] == obj[property.key] 
     } 
    } 
} 


class Foo implements DefaultEquality { 
    String name 
    Integer number 
} 

def a1 = new Foo() 
def b1 = new Foo(name: "Delphyne") 
def c1 = new Foo(number: 1) 
def d1 = new Foo(name: "Delphyne", number: 1) 

def a2 = new Foo() 
def b2 = new Foo(name: "Delphyne") 
def c2 = new Foo(number: 1) 
def d2 = new Foo(name: "Delphyne", number: 1) 

assert a1 == a2 && a1.hashCode() == a2.hashCode() 
assert b1 == b2 && b1.hashCode() == b2.hashCode() 
assert c1 == c2 && c1.hashCode() == c2.hashCode() 
assert d1 == d2 && d1.hashCode() == d2.hashCode() 

Vous pouvez également mettre en œuvre une transformation AST qui fait la même chose. Vous devriez probablement aussi vérifier que les classes correspondent, comme dans une méthode equals() traditionnelle, mais cela semble violer le principe de la dactylographie du canard. Ajustez selon vos goûts.

Notez que si vous êtes dans un script, les classes finissent par être une classe interne anonyme à l'intérieur du script, donc l'égalité échouerait de toute façon. Les classes compilées normales ne souffriront pas du même problème.

+0

Je reçois une exception groovy.lang.MissingPropertyException: Aucune propriété si obj ne possède pas de propriété name ou number. – rochb

Questions connexes