今天来讲一下Groovy的groovy.transform包的第一个故事,先简单介绍一下这个包:transform是Groovy设计者在编译期对语言做出的一些优化,这些优化不会触及到JVM层次的字节码,但是又能为开发者提供更简洁优雅的coding风格,详情我另文介绍。开始说说今天的主角。
1、@ToString
在JVM实现里,所有的类都默认是java.lang.Object的子类,因此所有的类都可以继承和覆写Object类里的toString方法,在打印的时候,这个toString就相当于这个类的名片,可以根据写作者的意愿来返回一个String。一般来说,在Java开发过程中,我们会把一些关键的信息组合到这个String里面来,或者是全部的属性值组合到这个String来,为我们提供一些运行时的数据信息。不过更多的时候,我们会选择不实现这个方法,并非有别的考虑,只是“怕麻烦”。(在Java里,可以借助org.apache.common.lang.builder.ReflectionToStringBuilder的toString方法,以反射的方式来生成属性值的组合)
在groovy里,这个问题得到了更妥善的解决,我们可以给需要实现toString的类加上@toString注解来简单完成此需求,看下面的代码。
public class Person {
String name
String address
int age
}
def p = new Person("name":"xiaoming","address":"网商路599号","age":100)
println p
执行结果是:
com.xiaonanzhi.transform.model.Person@1bf7857f
其中@后面的字符串是不确定的,这个实际上调用的是java.lang.Object对象的toString方法,这个方法是这么实现的:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
@后面的数字实际上对应了Object里面native实现的hashCode方法,实际上对应的是对象的内存地址。现在给Person加上@toString注解,我们再看下结果:
com.xiaonanzhi.transform.model.Person(xiaoming, 网商路599号, 100)
ToString注解帮我们把这个类的属性都输出了,不过貌似没有属性名称啊,真是缺憾啊……且慢,我们再仔细看看ToString注解的文档,原来只要加上includeNames=true的参数,就可以连着属性名打印了,所以现在注解变为:
@ToString(includeNames=true)
输出则变成了
com.xiaonanzhi.transform.model.Person(name:xiaoming, address:网商路599号, age:100)
如此一来我们知道了toString的参数设置方式,就是往注解里填参数,已有的参数提供了丰富的定制,比如cache,可以帮我们缓存住一些复杂的计算结果,不用每次都耗费性能, excludes则可以帮我们去掉那些不希望输出的字段(比如一个有一千字的小说字段,无疑会让我们看调试信息的时候走神),有兴趣的同学可以仔细看末尾的toString文档。
2、@EqualsAndHashCode
第二个出场的兄弟名字有点长,不过做Java开发的人都不陌生——equals和hashCode。他们也都是来自java.lang.Object的两个方法,用于对象的比较。一般实践上认为,equals相等的两个对象,其hashCode也应该相等,这样在使用HashSet等容器的时候才不会有令人错愕的结果。
在groovy里面,对类使用了@EqualsAndHashCode注解后,我们将会自动(在编译期)获得equals和hashCode方法,还是用上面的代码来举例:
先不做任何处理
def p = new Person("name":"xiaoming","address":"网商路599号","age":100)
def p2 = new Person("name":"xiaoming","address":"网商路599号","age":100)
println p==p2
我们知道,在groovy的对象之间,==号是形同equals方法的,所以这里的p==p2,就相当于Java的p.equals(p2),对于未覆写equals的两个对象来说,这时候比较的就是两个对象的内存地址。结果和我们预期的一样。
false
我们用新学习的@EqualsAndHashCode注解来试试看。
@ToString(includeNames=true)
@EqualsAndHashCode
public class Person {
String name
String address
int age
}
加了这个注解之后,我们再执行刚才的脚本,这时候返回的结果为
true
如果我们只对某些参数使用equals和hashCode,我们和上面的@ToString一样,可以参考@EqualsAndHashCode的参数来设置。比如如果我们使用 excludes来排除某个属性的话,则就算两个对象这个属性的值不一样,他们的比较结果也是true。
3、@TupleConstructor
这个注解的含义直接看起来不是很好理解,元组构造器?我们知道,Groovy对JavaBean做了很棒的改造,可以默认为类构造一个Map的构造器。比如刚才的Person类,可以使用:
def p = new Person("name":"xiaoming","address":"网商路599号","age":100)
来创建一个对象。不过还能不能更简单一点呢?这就是我们现在要讲的TupleConstructor,加上这个注解之后,将会为加上此注解的类对应的对象加上一个基于元组的构造器,直接上代码:
@TupleConstructor
@ToString(includeNames=true)
public class Person {
String name
String address
int age
}
def p = new Person("xiaoming","网商路599号",100)
println p
简单吧,详细的信息可以看后面的官方文档,不过值得注意的是,元组的构造器是和字段定义的位置相关的,在某种意义上并不如map构造器那么明了,如果类型不匹配还会报运行时异常,应该是需要慎用的。
4、讲完三个小弟,终于轮到大哥了。Canonical注解(不知道为什么叫这个名字),小弟都讲了不少,轮到大哥是不是有点紧张了?不用紧张,大哥实际上就是三个小弟的合集。你没看错,加了@Canonical注解,这个类就具备了1、2、3个注解的全部能力,颇有点葫芦娃合体变成葫芦小金刚的味道,有木有?
ToString:http://beta.groovy-lang.org/docs/latest/html/gapi/groovy/transform/ToString.html
EqualsAndHashCode: http://beta.groovy-lang.org/docs/latest/html/gapi/groovy/transform/EqualsAndHashCode.html
TupleConstructor: http://beta.groovy-lang.org/docs/latest/html/gapi/groovy/transform/TupleConstructor.html
Canonical : http://beta.groovy-lang.org/docs/latest/html/gapi/groovy/transform/Canonical.html