Groovy:生成equals和hashCode方法

4
如果我有一个简单的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()的后果可能是严重的,并且很难追踪。
谢谢, Don
3个回答

11

我不是Groovy开发人员,但我了解从Groovy 1.8开始,您可以在类型上使用@EqualsAndHashCode来调用AST转换。


请注意,此功能仅适用于Groovy 1.8-beta-1。 Groovy 1.8计划在2010年底发布。有关详细信息,请参见此处http://groovy.codehaus.org/News。 - Dónal
3
请注意,它只考虑属性(不考虑具有public/protected/private访问修饰符的字段),除非您在注释上指定includeFields=true - Partly Cloudy

5

一个警告 - 反射方法具有显著的性能成本,因此通常最好避免使用它们。 - Yohan Liyanage

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()方法一样,但这似乎违反了鸭子类型原则。根据你的喜好进行调整。
请注意,如果你在脚本中,类最终会成为脚本内部的匿名内部类,因此相等性将失败。正常编译的类不会遇到这个问题。

如果 obj 没有 name 或 number 属性,我会收到 groovy.lang.MissingPropertyException: No such property 的异常。 - rochb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接