比较与分析Groovy与Java
1.支持函数式编程,不需要main函数
2.默认导入常用的包,包括:
java.io
java.math
java.net
java.util
groovy.lang
groovy.util
3.断言不支持jvm的-ea参数进行开关
4.支持对对象进行布尔求值
5.类不支持default作用域,且默认作用域为public
6.受检查类型异常(Checked Exception)也可以不用捕获
7.一些新的运算符
8.groovy中基本类型也是对象,可以直接调用对象的方法,如:
assert (-12345).abs() == 12345
但浮点运算是基于BigDecimal类
assert 0.25 instanceof BigDecimal
assert 0.1 * 3 == 0.3
assert 1.1 + 0.1 == 1.2
assert 1 / 0.25 == 4
Groovy与Java的比较(中)
9.字符串的处理
String对象和java类似,但没有character的概念,没有迭代每个字符的方法。
使用单引号定义普通字符串,双引号定义的字符串可以包含Groovy运算符,$符号则需要转义("\$"),如:
String name = "Ben"
String greeting = "Good morning, ${name}"
assert greeting == 'Good morning, Ben'
String output = "The result of 2 + 2 is: ${2 + 2}"
assert output == "The result of 2 + 2 is: 4"
还可以使用三个连续的"来定义多行字符串,如:
String getEmailBody(String name) {
return """Dear ${name},
Thank you for your recent inquiry. One of our team members
will process it shortly and get back to you. Some time in
the next decade. Probably.
Warmest and best regards,
Customer Services
"""
}
char类型的使用方法:
char ch = 'D'
assert ch instanceof Character
String str = "Good morning Ben"
str = str.replace(' ' as char, '+' as char)
assert str == "Good+morning+Ben"
10.as运算符,用于没有集成关系的类型间强制类型转换,如:
assert 543667 as String == "543667"
assert 1234.compareTo("34749397" as int) < 0
可通过实现asType(Class) 方法来实现自定义的as行为,默认的方法包括:
11.一些集合类型的语法甜头(Syntax sugar for lists, maps, and ranges)
从语言层面支持List\Map\Range类型,而不是通过SDK中的类
使用[]创建创建和初始化List、Map,如:
List myList = [ "apple", "orange", "lemon" ]
Map myMap = [ 3: "three", 6: "six", 2: "two" ]
assert 3 == [ 5, 6, 7 ].size()
List\Map支持数组风格的用法
List numbers = [ 5, 10, 15, 20, 25 ]
assert numbers[0] == 5 //获取List中的对象
assert numbers[3] == 20
assert numbers[-1] == 25 //逆序获取List对象
assert numbers[-3] == 15
numbers[2] = 3 //更新List对象
assert numbers[2] == 3
numbers < < 30 //添加数据
assert numbers[5] == 30
Map items = [ "one": "apple",
"two": "orange",
"three": "pear",
"four": "cherry" ]
assert items["two"] == "orange" //从Map中获得对象
assert items["four"] == "cherry"
items["one"] = "banana" //更新Map中对象
assert items["one"] == "banana"
items["five"] = "grape" //增加对象到中
assert items["five"] == "grape"
新的类型:Range
Range实现了java.util.List,可以作为List使用,并扩展了包含(..)和排除(..< )运算符
// an inclusive range
def range = 5..8
assert range.size() == 4
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert range.contains(8)
// lets use an exclusive range
range = 5..< 8
assert range.size() == 3
assert range.get(2) == 7
assert range[2] == 7
assert range instanceof java.util.List
assert range.contains(5)
assert ! range.contains(8)
//get the end points of the range without using indexes
def range = 1..10
assert range.from == 1
assert range.to == 10
List fruit = [
"apple",
"pear",
"lemon",
"orange",
"cherry" ]
for (int i in 0..< fruit.size()) { //Iterates through an exclusive range B
println "Fruit number $i is '${fruit[i]}'"
}
List subList = fruit[1..3] //Extracts a list slice C
12.一些省时的特性
行末的分号(;)不是必须的。在没有分号的情况下,groovy计算一行如果是有效的表达式,则认为下一行是新的表达式,否则将联合下一行共同作为一个表达式。分隔多行的表达式,可以用/符号,如:
String fruit = "orange, apple, pear, " \
+ "banana, cherry, nectarine"
方法调用时的圆括号()不是必须的(但建议保留)。但在无参方法调用,或第一个参数是集合类型定义时还是必须的:
println "Hello, world!"
println()
println([1, 2, 3, 4])
方法定义中的return语句不是必须的,没有return的情况下,将返回方法体中最后一行的值,如下面的方法返回value+1:
int addOne(int value) { value + 1 }
Groovy与Java的比较(下)
13.语言级别的正则表达式支持
使用斜线(/)定义正则表达式,避免java中的多次转义,如"\\\\\\w"相当于/\\\w/。
如果要作为java中的Pattern对象使用,可以使用~符号表示,如:
assert ~"London" instanceof java.util.regex.Pattern
assert ~/\w+/ instanceof java.util.regex.Pattern
使用=~运算符进行匹配
assert "Speaking plain English" =~ /plain/
使用==~运算符进行精确匹配
assert !("Speaking plain English" ==~ /plain/)
assert "Speaking plain English" ==~ /.*plain.*/
捕获分组,如:
import java.util.regex.Matcher
String str = "The rain in Spain falls mainly on the plain"
Matcher m = str =~ /\b(\w*)ain(\w*)\b/
if (m) {
for (int i in 0..< m.count) {
println "Found: '${m[i][0]}' - " +
"prefix: '${m[i][1]}'" +
", suffix: '${m[i][2]}'"
}
}
输出:
Found: 'rain' - prefix: 'r', suffix: ''
Found: 'Spain' - prefix: 'Sp', suffix: ''
Found: 'mainly' - prefix: 'm', suffix: 'ly'
Found: 'plain' - prefix: 'pl', suffix: ''
14.简化的javabean
直接使用“.属性名”的方法代替getter,如:
Date now = new Date()
println "Current time in milliseconds: ${ now.time }"
now.time = 103467843L
assert now.time == 103467843L
属性定义不需要setter/getter。未指定作用域的属性,groovy自动认为是private并生为其成setter/getter,也可以根据需要进行覆写。如下除了最后一个字段,都是属性:
class MyProperties {
static String classVar
final String constant = "constant"
String name
public String publicField
private String privateField
}
简化bean的初始化,可以使用Map进行初始化,或键值对的方法,如
DateFormat format = new SimpleDateFormat(
lenient: false,
numberFormat: NumberFormat.getIntegerInstance(),
timeZone: TimeZone.getTimeZone("EST"))
可以使用属性的方式读取map:
Map values = [ fred: 1, peter: 5, glen: 42 ]
assert values.fred == 1
values.peter = 10
assert values.peter == 10
注:groovy将map的key作为字符串处理,除非是数字或者用圆括号包含。这里的fred就是字符串"fred",但引号不是必须的,只有在key包含空格、句点或其他不能作为Groovy标示符的字符存在时才需要。如果需要使用一个变量的值作为key,则使用圆括号,如 [ (fred): 1 ]。
15.groovy不具备的java特性
不能用单引号定义字符类型,但可以使用as运算符将一个字母的字符串转换为字符类型
for循环中不能用逗号分隔多个运算符,如下面的代码是不允许的:
for (int i = 0, j = 0; i < 10; i++, j++) { ... }
不支持DO...WHILE循环,但可以使用while...for运算代替
不支持内部类和匿名类,但支持闭包和在一个文件中定义多个类
16.groovy的重要特性——闭包:
可以看作一个匿名方法定义,可以赋予给一个变量名、作为参数传递给方法调用、或者被方法返回。也可以想象为只有一个方法定义的匿名类。
闭包的语法{ < arguments> -> < body> },如:
List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
fruit.sort { String a, String b -> a.compareToIgnoreCase(b) }
println "Sorted fruit: ${fruit}"
注:sort方法只有一个闭包类型的参数,省略了圆括号;闭包中使用了默认的return值
当没有参数传入时,仍然需要保留箭头的存在{-> ... }
只有一个参数传入时,可以省略箭头,隐式的创建一个it参数,引用当前对象,如:
[ "apple", "pear", "cherry" ].each { println it }
可以将闭包赋予一个变量,如
Closure comparator = { String a, String b ->
a.compareToIgnoreCase(b)
}
List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
fruit.sort(comparator)
println "Sorted fruit: ${fruit}"
assert comparator("banana", "Lemon") < 0
只有一个参数的闭包,可以不传入参数,运行时隐式的传入null参数
当闭包是一个方法的最后一个参数时,可以写在圆括号外面,如:
List list = [ 1, 3, 5, 6 ]
list.inject(0, { runningTotal, value -> runningTotal + value })
可以这样写:
assert 15 == list.inject(0) { runningTotal, value -> runningTotal + value }
便于闭包中具有多行时代码更加清晰
不要滥用闭包。当闭包作为一个属性时,不要在子类中覆写,实在需要这样做,使用方法。使用闭包也无法利用java中很多AOP框架的特性
17.groovy的重要特性——动态编程
动态的使用属性,如下的java代码:
public void sortPeopleByGivenName(List< Person> personList) {
Collections.sort(personList, new Comparator< Person>() {
public int compare(Person p1, Person p2) {
return p1.getFamilyName().compareTo(p2.getFamilyName());
}
} ) ;
}
可使用下面的代替,当需要使用其他字段比较时,不需要修改代码
def sortPeople(people, property) {
people.sort { p1, p2 -> p1."${property}" < => p2."${property}" }
}
将一个String作为属性或方法名进行调用,如:
peopleList.sort()
peopleList."sort"()
动态类型(duck typing:"if it walks like a duck and talks like a duck, it’s probably a duck):运行期解析对象的属性和方法,允许在运行时增加对象的属性和方法而不修改源代码,因此可能出现调用未定义方法的情况。
动态编程带来的危险:
编译器不能检查到类型错误、方法或属性的错误调用,应该养成编写测试的习惯
难以调试,使用“单步跳入(step into)”经常进入一些反射中,使用“运行到光标处(run to cursor)”代替
动态的类型定义使代码难以阅读,使用良好的命名、注释,尽量明确定义变量类型,便于IDE检测ht potential type errors in the call潜在的错误。
18.Groovy JDK中的增强
Collection/Array/String具有size()方法
Collection/Array/String具有each(closure)方法,方便的进行遍历
Collection/Array/String具有find(closure)、findAll(closure)方法,find返回第一个符合条件的对象,findAll返回所有符合条件对象列表,如:
def glen = personList.find { it.firstName == "Glen" }
Collection/Array/String具有collect(closure)方法,对集合中每个对象执行一段方法后,返回结果集,如:
def names = [ "Glen", "Peter", "Alice", "Graham", "Fiona" ]
assert [ 4, 5, 5, 6, 5 ] == names.collect { it.size() }
Collection/Array/String具有sort(closure)方法,包括:
一个参数的闭包,如:
def names = [ "Glen", "Peter", "Ann", "Graham", "Veronica" ]
def sortedNames = names.sort { it.size() }
assert [ "Ann", "Glen", "Peter", "Graham", "Veronica" ] == sortedNames
两个参数的闭包,如:
def names = [ "Glen", "Peter", "Ann", "Graham", "Veronica" ]
def sortedNames = names.sort { name1, name2 ->
name1.size() < => name2.size()
}
assert [ "Ann", "Glen", "Peter", "Graham", "Veronica" ] == sortedNames
Collection/Array具有join(String)方法
def names = [ "Glen", "Peter", "Alice", "Fiona" ]
assert "Glen, Peter, Alice, Fiona" == names.join(", ")
File.text属性读取文件内容作为字符串返回
File.size()方法返回文件的byte值,相当于File.length()方法
File.withWriter(closure)方法,从文件创建一个Writer对象传给闭包,闭包执行完毕后,依赖的输出流自动安全关闭。另外还有若干with...方法查看文档
Matcher.count返回相应Matcher的匹配数量
Number.abs()方法,对数字求绝对值
Number.times(closure)执行n次闭包,将当前执行的次数作为参数传给闭包
19.XML的处理
示例的XML:
< root>
< item qty="10">
< name>Orange< /name>
< type>Fruit< /type>
< /item>
< item qty="6">
< name>Apple< /name>
< type>Fruit< /type>
< /item>
< item qty="2">
< name>Chair< /name>
< type>Furniture< /type>
< /item>
< /root>
处理程序
import groovy.xml.MarkupBuilder
import groovy.util.XmlSlurper
def file = new File("test.xml")
def objs = [
[ quantity: 10, name: "Orange", type: "Fruit" ],
[ quantity: 6, name: "Apple", type: "Fruit" ],
[ quantity: 2, name: "Chair", type: "Furniture" ] ]
def b = new MarkupBuilder(new FileWriter(file)) 创建MarkupBuilder对象
b.root {
动态调用root方法,但builder对象并没有该方法,把它作为一个新的XML对象的根节点,并且把方法名作为根节点名称
objs.each { o ->
item(qty: o.quantity) {
name(o.name)
type(o.type)
}
}
}
遍历集合,创建节点,其中item/name/type也是动态的方法,以方法名作为节点名,方法参数作为节点的属性
def xml = new XmlSlurper().parse(file)
使用XmlSlurper对象解析内存中的XML文件
assert xml.item.size() == 3
assert xml.item[0].name == "Orange"
assert xml.item[0].@qty == "10"
使用动态的属性名读取XML节点
使用@字符读取节点属性
println "Fruits: ${xml.item.findAll {it.type == 'Fruit'}*.name }"
println "Total: ${xml.item.@qty.list().sum {it.toInteger()} }"
20.最佳实践
使用地道的Groovy语法:尽可能使用groovy中简化后的语法风格,减少代码量
实验:使用groovy console或shell可以方便的实验groovy代码
尽可能使用方法,而不是闭包。方法易于理解,也利于和java交互
在方法签名中尽可能的使用确定的类型,便于代码阅读和IDE的错误检测。在使用动态类型时要有清晰完善的文档注释