Groovy:生成equals和hashCode方法

时间:2009-09-10 19:43:14

标签: java groovy

如果我有一个简单的Groovy类,比如

class Address {

  Integer streetNumber
  String streetName
  String state
  String zip
  Country country    
}

虽然我可以编写(或使用IDE生成)hashCodeequals方法,例如:

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);
}

虽然这样可以正常工作,但我觉得我可以更好地利用Groovy的动力来在更少的代码中实现相同的功能。我想到的一种方法是使用.properties来获取对象的属性名称和值的映射。然后我可以迭代这些属性,在每个属性上调用hashCode()equals()以获得与上面相同的结果。

在我走这条路之前,我只想检查是否有其他人找到了解决这个问题的好方法。我对推出自己的解决方案有点警惕,因为弄乱equals()hashCode()的后果可能很难以追查并且很难追踪。

谢谢, 唐

3 个答案:

答案 0 :(得分:11)

我不是一个常规的开发人员,但我理解,从groovy 1.8开始,您可以使用类型上的@EqualsAndHashCode调用AST转换。

答案 1 :(得分:5)

或者您可以使用Apache Commons LangEqualsBuilderHashCodeBuilder。您可以让构建器使用Reflection,以便它将评估所有字段或确定equals()hashCode()计算中应包含哪个字段。

如果您有兴趣,他们也有ToStringBuilder

答案 2 :(得分:2)

如果你想要一个纯粹的Groovy解决方案,你可以这样做:

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()

您还可以实现执行相同操作的AST转换。您可能还应该检查类是否匹配,就像在传统的equals()方法中一样,但这似乎违反了对我来说鸭子类型的原则。根据自己的喜好调整。

请注意,如果您在脚本中,那么这些类最终会成为脚本中的匿名内部类,因此无论如何都会失败。正常编译的类不会遇到同样的问题。