原文 http://groovy.codehaus.org/Groovy+style+and+language+feature+guidelines+for+Java+developers
Groovy编程风格和语言特性指南(针对Java开发人员)
作为一个Java开发人员,当他/她开始学习Groovy时,总是会用Java的方式,循序渐进地学习Groovy,以使他/她的工作效率提高和写出更地道的Groovy代码。本文的目的是引导这类开发人员学习一些常用的的Groovy语法风格,学习新的运算符,新的特性如闭包,等等。本文不是详细描述,只能作为一个快速的介绍,和进一步学习的基础部分,欢迎添加,丰富该文档。
没有分号
对于一个有C / C + + / C#/ Java背景的开发人员,我们是如此习惯使用分号,它们无处不在。更糟的是,Groovy支持99%的Java的语法,并且某些时候复制一些Java代码到你的Groovy程序里是如此的容易,这样你的世界就会充斥着无数的分号。但是......在Groovy中,分号是可选的,你可以忽略它们,将其删除是更地道的Groovy风格。
可选关键字return
在Groovy中,方法体内的最后一个表达式会被返回,而不需要return关键字。尤其是针对简短的方法和闭包,省略return显得更简洁:
String toString() {return "a server" }
String toString() {"a server" }
但有时候,这并不易于理解,如当你使用一个变量,并且这个变量出现在多行:
def props() {
def m1 = [a:1, b:2]
m2 = m1.findAll { k, v -> v %2 ==0 }
m2.c =3
m2
}
在这种情况下,无论是在最后一个表达式之前加上一个换行,还是显式地使用return,可能会产生更好的可读性。
我自己,有时会使用return关键字,有时没有,它往往是一个品味的问题。例如在使用闭包时,我们通常会省略return。所以,即使关键字是可选的,但是不意味着强制性的,如果你认为它妨碍你代码的可读性就别使用它。
注意,如果使用“def”关键字而不是某个具体类型定义的方法时,有时返回的最后一个表达式可能会让你吃惊。所以通常更喜欢使用一个特定的返回类型,如void或某个类型。在我们上面的例子中,假设我们忘了把m2最为最后一条语句返回,最后的表达式将是m2.c == 3,这将返回... 3,而不是你期望的map。
有一些语句,如if / else语句,try / catch语句,也可以返回一个值,因为他们也可以有一个“最后表达式”:
def foo(n) {i
f(n ==1) {
"Roshan"
}else {
"Dawrani"
}
}
assert foo(1) =="Roshan"
assert foo(2) =="Dawrani"
Def和具体类型
我们正在谈论def和类型,我经常看到开发人员把'def'和具体类型一起使用。但是,“def”在这里是多余的。需要做出一个选择,要么使用“def”,要么使用一个具体类型。
所以,不要这样写:
def String name ="Guillaume"
较好的是这样:
String name ="Guillaume"
在Groovy中,def表明的实际类型是Object(这样你就可以给def定义的变量分配任何对象,和在用def定义的方法里返回任何类型的对象)。
默认的public修饰符
默认情况下,Groovy中认为,类和方法是具有public访问权限的。所以,你没有必要使用public修饰符。只有当它不是public的,你才应该使用可见性修饰符。
因此,对于以下写法:
public class Server { public String toString() {return "a server" } }
更简洁的写法是:
class Server { String toString() {"a server" } }
你可能想知道包范围的可见性修饰符,事实上,Groovy允许省略public,即表示默认情况下不支持包范围的可见性,但实际上有一个特殊的Groovy注解,能让你使用的包可见性:
class Server { @PackageScope Cluster cluster }
省略圆括号
Groovy允许你省略顶级层次表达式的圆括号,如用println的命令:
println "Hello"
method a, b
VS:
println("Hello")
method(a, b)
当一个闭包是一个方法调用的最后一个参数时,如在使用Groovy的each函数,你可以把闭包放在括号外,甚至可以省略括号:
list.each( { println it } )
list.each(){ println it }
list.each { println it }
总是喜欢第三种形式,这更自然,一对空括号只是无用的语法噪音!
有一些情况,Groovy并不是允许你删除括号。正如我所说的,顶级的表达式可以省略,但嵌套的方法调用,或者赋值语句的右边(?可以省略?),你不能忽略括号。
def foo(n) { n }
println foo1 // won't work
def m = foo1
类,一等公民
后缀.class有点像Java的的instanceof,但在Groovy中已不再需要。
例如:
connection.doPost(BASE_URI +"/modify.hqu", params, ResourcesResponse.class)
使用的GString和一等公民,写法如下:
connection.doPost("${BASE_URI}/modify.hqu", params, ResourcesResponse)
Getter和Setter
在Groovy中,一个getter和setter方法形成了我们所谓的“属性”,并提供了一个捷径来访问和设置这些属性。和Java的调用方式不同,你可以使用一个类似字段访问的方式:
resourceGroup.getResourcePrototype().getName() == SERVER_TYPE_NAME
resourceGroup.resourcePrototype.name == SERVER_TYPE_NAME
resourcePrototype.setName("something")
resourcePrototype.name = "something"
用Groovy编写的bean,通常被称为POGOs(Plain and Old GroovyObjects),你不必创建字段的getter / setter,编译器会替你做。
因此,不要这样写:
class Person {
private String name
String getName() {
return name
}
void setName(String name) {
this.name = name
}
}
可以简单的写成:
class Person { String name }
正如你看到的,一个独立的没有修饰符的“字段”实际上使得Groovy编译器生成一个私有字段和相应的getter和setter。
当然,在Java中使用POGOs时,getter和setter确实存在,并可以照常使用。
尽管编译器会创建通用的getter / setter方法的逻辑,然而如果你愿意在这些getter / setter方法中做任何附加的或不同的逻辑,你必须可以提供它们,编译器将使用你的逻辑,而不是默认生成。
使用命名参数和默认构造函数初始化bean
一个bean,如:
class Server { String name; Cluster cluster }
你可以使用命名参数的默认构造函数(首先构造函数被调用,然后依次调用setter):
def server =new Server(name:"Obelix", cluster: aCluster)
而不是在声明后调用各个setter:
def server =new Server()
server.name ="Obelix"
server.cluster = aCluster
使用with()重复操作同一个bean
使用命名参数的默认构造函数创建新的实例很有趣,但如果你正在更新的一个得到的实例,你一定要重复一遍又一遍的'server'前缀?不,多亏了Groovy给所有对象添加的with()方法:
server.name = application.name
server.status = status
server.sessionCount = 3
server.start()
server.stop()
VS:
server.with {
name = application.name
status = status
sessionCount = 3
start()
stop()
}
Equals和 ==
Java的==其实是Groovy的is()方法,Groovy的==是一个聪明的equals()方法!
要比较的对象引用,你应该使用a.is(B),而不是==。
对于通常的equals()比较,你应该更喜欢Groovy的==,因为它也可以避免NullPointerException异常,无论左边或者右边的变量是不是null。
不要这样写:
status !=null && status.equals(ControlConstants.STATUS_COMPLETED)
这样做:
status == ControlConstants.STATUS_COMPLETED
GString(内插变量,多行)
Java中我们经常使用双引号,加好和\n换行符来使用字符串和变量串联。利用内插字符串(称为GString),这样的字符串看起来更好并且减少打字的痛苦:
throw new Exception("Unable to convert resource: " + resource)
VS:
throw new Exception("Unable to convert resource: ${resource}")
在大括号中,你可以把任何一种表达方式放入其中,而不仅仅是变量。对于简单变量或变量的属性,你甚至可以丢弃的大括号:
throw new Exception("Unable to convert resource: $resource")
你甚至可以延迟评估这些表达式,使用闭包的符号$ { -> resource}。当GString的将被强制转换为String,它会评估闭包,并获得toString()的返回值的。示例:
int i =3
def s1 ="i's value is: ${i}"
def s2 ="i's value is: ${-> i}"
i++
assert s1 =="i's value is: 3" // eagerly evaluated, takes the value on creation
assert s2 =="i's value is: 4" // lazily evaluated, takes the new value into account
Java中字符串和其连接的表达Java是很冗长的:
throw new PluginException("Failed to execute command list-applications:" +
" The group with name " +
parameterMap.groupname[0] +
" is not compatible group of type " +
SERVER_TYPE_NAME)
您可以使用\延续字符(这是一个多行字符串):
throw new PluginException("Failed to execute command list-applications: \
The group with name ${parameterMap.groupname[0]} \
is not compatible group of type ${SERVER_TYPE_NAME}")
或者使用多行三重引号的字符串:
throw new PluginException("""Failed to execute command list-applications:
The group with name ${parameterMap.groupname[0]}
is not compatible group of type ${SERVER_TYPE_NAME)}""")
你也可以在该字符串上调用stripIndent()方法剥离出现在多行的字符串的左侧的缩进。
另请注意Groovy中的单引号和双引号的区别:单引号总是创建Java字符串,没有内插变量,而双引号可以创建Java字符串,也可以创建GString当有内插变量时。
对于多行字符串,你可以使用三个引号,也就是说:GString用三双引号,单纯的字符串用三个单引号。
原生语法的数据结构
Groovy提供原生的语法来构造如list,map,正则表达式,和范围值等数据结构。确保在你的Groovy程序利用它们。
下面是一些例子:
def list = [1,4,6,9]
// by default, keys are Strings, no need to quote them
// you can wrap keys with () like [(variableStateAcronym): stateName] to insert a variable or object as a key.
def map = [CA:'California', MI:'Michigan']
def range =10..20
def pattern = ~/fo*/
// equivalent to add()
list <<5
// call contains()
assert 4 in list
assert 5 in list
assert 15 in range
// subscript notation
assert list[1] ==4
// add a new key value pair
map << [WA:'Washington']
// subscript notation
assert map['CA'] =='California'
// property notation
assert map.WA =='Washington'
// matches() strings against patterns
assert 'foo' =~ pattern
Groovy的开发工具包
当你需要对集合进行迭代并关注在数据结构上时,Groovy中提供各种额外的方法,包装了Java的核心数据结构,就像each{},find{},findAll{},every{},collect{},inject{}。这些方法添加了函数编程的风格,并编写复杂的算法更容易。由于语言的动态性质,经过包装,许多新的方法应用到不同的类型。你可以找到很多关于字符串,文件,流,集合的非常有用的方法,更多请看:
http://groovy.codehaus.org/groovy-jdk/
switch的威力
Groovy的switch比C-风格的语言--通常只接受基本类型功能更强大。Groovy的switch语句接受几乎任何一种类型。
def x =1.23
def result =""
switch (x) {
case "foo": result ="found foo"
// lets fall through
case "bar": result +="bar"
case [4,5,6,'inList']:
result ="list"
break
case 12..30:
result ="range"
break
case Integer:
result ="integer"
break
case Number:
result ="number"
break
default: result ="default"
}
assert result =="number"
更普遍的是,具有isCase()方法的类型,也可以出现在case子句中。
Import使用别名
在Java中,当使用不同包里的具有相同的名称的两个类时,如java.util.List和java.awt.List,你可以导入一个类,但另一个必须使用全名。
还有,有时候在你的代码中,当频繁使用一个很长的类名时,代码会变得冗长。
为了改善这种情况下,Groovy允许import使用别名:
import java.util.List as juList
import java.awt.List as aList
import java.awt.WindowConstants as WC
你还可以导入静态的方法:
import static pkg.SomeClass.foo
foo()
Groovy的True
所有对象都可以被“强转成”一个布尔值:一切null,void或empty的都是false,如果不是,则计算结果为true。
因此,if (name) {}写法完全可以替代if (name != null && name.length > 0) {}。集合类也是如此。
于是,你可以在类似while(),if(),三元运算符,Elvis操作符(见下文)等中运用此技巧。
你甚至可以自定义的Groovy的true,通过添加一个的布尔asBoolean()方法到你的类中!
安全引用导航
Groovy支持.运算符的一个变体,可以安全导航一个对象图。
在Java中,当你对对象路径图中的一个节点感兴趣,你需要检查是否为NULL,这往往导致写出来的复杂的if语句或嵌套的if语句:
if (order !=null) {
if (order.getCustomer() !=null) {
if (order.getCustomer().getAddress() !=null) {
System.out.println(order.getCustomer().getAddress());
}
}
}
利用?.安全引用操作符,可以简化代码:
println order?.customer?.address
整个调用链中会检查空值,任何元素为null都不会抛出NullPointerException异常,如果有一个为null则返回值也是null。
断言
要检查参数,返回值,以及更多的东西,你可以使用“assert”语句。
与Java的断言相反,断言并不需要被激活才能工作,Groovy中它总是打开的。
def check(String name) {
// name non-null and non-empty according to Groovy Truth
assert name
// safe navigation + Groovy Truth to check
assert name?.size() >3
}
您还可以看到Groovy的“强大的断言”语句提供的漂亮的输出:被断言的每个子表达式的值的图形视图。
Elvis操作符提供默认值
Elvis操作符是一种特殊的三元运算符的快捷方式,用于方便的使用默认值。
我们经常要这样写代码:
def result = name !=null ? name :"Unknown"
由于Groovy的特性,空检查name!=null可以简化成name。
再进一步,因为你总是返回name,与其在这个三元表达式中重复name两次,不如我们以某种方式删除问号和冒号之间的部分,通过使用Elvis操作符,上述代码变成:
def result = name ?:"Unknown"
捕获任何异常
如果你真的不关心你的try代码块内抛出的异常,你可以简单地捕捉所有的异常并忽略其类型。但是不是像下面一样捕捉throwables:
try {// ...}catch (Throwable t) {// something bad happens}
而是捕捉所有东西('任何'或'所有',或任何让你觉得这是一切的东西):
try {// ...}catch (any) {// something bad happens}
关于强弱类型的建议
最后我会以如何使用可选的类型结束本文。Groovy中你可以自己决定是否使用显式的强类型,或者使用“def”。
我有一个相当简单的经验法则:每当你写的代码会被其他人当作一个公共API使用,你应该总是使用强类型,它有助于增强规约,避免可能的传递错误类型参数,提供更好的文档,也有利于IDE的代码完成;如果代码仅是供自己使用,如私有方法,或当IDE可以很容易地推断出的类型,那么你可以自由决定。