前言:
知识点记录来源于【Groovy程序设计】一书中,本文仅作知识点记录供日后使用查询,不做教程使用。
groovy支持java语法,并且保留了java的语义,所以我们可以随心所欲的混用两种语言。
1.从Java到Groovy
先看一个简单的java代码的for循环,使用groovyConsole运行一下,如下:
可以看出简单的循环,“无用”的java代码可真多,实际上在groovy中,去掉每一行的分号也是被允许的。
注意:这里先说明,groovy代码编写的时候不必导入所有的常用类和包,例如,使用Calendar使用可以毫无困难的引入,同时groovy自动导入下列包:java.lang、java.util、java.io和java.net 等等。还会导入java.math.BigDecimal和java.math.BigInteger两个类等,此外,还导入了groovy.lang和groovy.util这些Groovy包。
同时,在groovy中,去掉类和方法的定义也是可以的,上面的java循环使用groovy改造后如下:
for (int i = 0; i < 3; i++) {// 注意,代码最后没有分号了哦System.out.print("ho ")}System.out.println("Merry Groovy!!!")
甚至可以更进一步,groovy理解 println()方法,因为该方法添加到了java.lang.Object中了,同时groovy还有一个使用Range对象的更为轻量级的for循环,而且groovy对括号也很“不在意”,因此最终循环代码可以如下:
for(i in 0..2) {print 'ho '
}
println 'Merry Groovy!'
比java代码更简单明了!
1.1.实现循环的方法
上面用到了for循环以及改造为range 0…2 的方法,但是groovy还提供了更多更优雅的迭代方式。
比如 upto(),这是groovy在java.lang.Integer类中添加的一个便于使用的实例方法,可用于迭代,如下:
在 0 上调用了 upto(),这里的0就是Integer的是一个实例,输出现实的是所选范围内的每个值,0 1 2。
代码中的 $it 代表的是,上下文中进行循环时的索引值,后续会具体说明,这里只需要知道即可。it前面的 $ 表示让print()打印该变量的值,而不是it这两个字符。
使用 uptp()方法时,可以设置范围的上限和下限,如果范围从0开始,可以使用times(),例如上面的0.uptp(2),可以改为如下:
3.times {print "$it "
}
结果会和 0.upto(2)相同的结果,0 1 2
要在循环时跳过一些值,可以使用 step()方法, 如下:
最后,我们再回顾从Java到Groovy中的for range循环的groovy示例,是不是也可以写成下面这样:
3.times {print "ho "}
println "Merry Groovy!"
1.2.代码中执行命令
String扩展了execute()方法,当执行的时候,groovy创建了一个扩展了java.lang.Process的类的实例,执行实例如下:
print "svn help".execute().text
调用text时,实际是在调用groovy在类Process类上添加的getText()方法,功能就是将该进程的标准输出到一个String对象中。除了svn help,还可以执行如下:
print "groovy -v".execute().text
print "ls -l".execute().text
注意:如果你是win系统,执行这些命令是不行的(linux和unix可以),明确的说需要调用cmd命令(语法也不同,例如ls需要改成dir),例如:
print "cmd /c groovy -v".execute().text
1.3.操作符?.
经常检查对象引用是否为null?groovy可以使用?.进行判断,如下:
def foo(str){// if (str != null) {str.reverse()}str?.reverse()
}
println foo('evil')
println foo(null)
操作符 ?. 只有在引用不为null的时候才会调用执行的方法或属性,运行结果如下:
live
null
1.4.异常处理
java开发中,必须强制处理编译异常,例如Thread的sleep()方法,必须处理InterruptedException异常,File的处理必须处理FileNotFoundExcetion,如下:
但是groovy不需要,groovy中如果不想处理异常,groovy会默认自动传递给调用者由调用者进行处理,如下:
没有任何异常捕获处理和抛出,由调用者处理异常,如下:
如上,捕获指定的异常使用catch即可,但是如果想要捕获所有的异常而不是某一个,可以如下:
使用catch(ex),ex前面没有任何类型,这样就可以捕获所有的异常了,但是需要注意,ex不会捕获Exception之外的Error或Throwable,如果需要,使用 catch(Throwable throwable)
2.可选形参
groovy中的可选形参必须位于方法参数列表的末尾,定义可选形参,需要在形参列表上给它赋一个值,如下,方法log()中有一个可选的base形参,如果不提供实参,groovy认为它的默认值就是10:
def log(x, base=10){...
}
// 调用log
println log(1024)
println log(1024, 10)
println log(1024, 2)
groovy还会把末尾的数组形参作为是可选的,这其实就相当于java中的可变参数,如下:
def task(name, String[] details){println "$name - $detailds"
}
// 调用
task('Call')
task('Call', '123')
task('Call', '123', '456')
task('Call', '123', '456', '789')
// 输出结果
Call - []
Call - [123]
Call - [123, 456]
Call - [123, 456, 789]
从输出结果可以看出,groovy把末尾的参数(除了name参数之外的所有剩余参数)收集起来了赋值给数组。
3.使用多赋值
向方法传递参数获取方法的返回,如果希望方法返回多个结果,这很实用,groovy可以从方法返回的多个结果一次性赋值给多个变量,我们可以让方法返回一个数组,然后左侧用多个变量逗号分割,放在圆括号中接收即可。
其实这个类似于java中的Pair<left, right>的用法,但是java中的Pair需要自己get然后赋值,groovy不需要而且更简洁,如下:
// 空格分割参数, 返回数组
def splitname(fullname){ fullname.split(' ' )}
// 左侧两个变量接收
def (firstname, lastname) = splitname('James Bond')
// 打印结果
println "$firstname - $lastname"
结果输出:
James - Bond
可以发现,你都不用创建变量firstname和lastname,直接使用接受即可。
还可以使用该特性交换变量,不用创建中间变量过度了,如下:
def name1 = "test1"
def name2 = "test2"println "name1:$name1, name2:$name2"// 交换,注意上面已经定义了name1和name2,这里直接()即可,外面无需追加def再次定义
(name1, name2) = [name2, name1]
println "交换之后:"
println "name1:$name1, name2:$name2"
结果输出:
name1:test1, name2:test2
交换之后:
name1:test2, name2:test1
注意:如果左边变量个数 和 右侧数组的个数 不一致,groovy会这样处理,左侧的多余变量置为null,右侧的多余值被丢弃。
4.实现接口
在groovy中可以把一个映射或一个代码块转化为接口,因此可以快速直线带有多个方法的接口。这里使用线程Thread的执行举例,如下:
// 模拟java创建线程并执行
new Thread(() -> {println("线程执行1");}).run();
使用groovy:
new Thread(println("线程执行2") as Runnable).run()
看到了吗,groovy不需要Runabled的方法声明(java8开始出现了lambda),也不需要匿名内部类的实例,借助 as 操作符,相当于实现了Runabled接口。
上面是一个方法的实现,如果多个方法的接口中,只打算实现其中的某些方法而不是全部(只关心自己需要的方法),其实也很简单,只需要创建一个映射,以每个方法的名字作为键,方法对应的代码块作键值,同时用 冒号(:)分割方法名和代码块即可,如下(截图):
5.布尔求值
groovy的布尔求值和java的不同,根据上下文,groovy会自动把表达式计算为布尔值,先看一下例子,如下:
// java代码
String str = "hello";
int value = 4;
if (str){} // 错误语法
if (value){} // 错误语法
java要求if中的表达式必须是一个布尔表达式,例如 if(obj != null) 这种,但是groovy可不是,它会尝试判断。
如果在需要布尔表达式的地方发了一个对象引用,例如 if 中,groovy会检查这个引用是否为null,null视为false,非null视为true,如下:
def str = "groovy"
if (str) { println "hi, " + str }
// 输出结果
hi, groovy
如上,groovy将str视为表达式计算布尔值。
其实上面说的根据引用对象是否为null判断布尔值也不是对的,因为如果对象不为null,表达式的结果还和对象的类型有关,例如集合List,只有集合不为null,且至少有一个元素,才会认为true,示例代码如下:
def list = [];
println "集合, 空判断: " + (list ? "true" : "false")list = null;
println "集合, null判断: " + (list ? "true" : "false")list = [1];
println "集合, 有值判断: " + (list ? "true" : "false")
输出结果如下:
集合, 空判断: false
集合, null判断: false
集合, 有值判断: true
不仅仅集合,其他一些类型也是不仅判断null,groovy如何判断的呢,如下图:
6.操作符重载
groovy支持操作符重载,就是每个操作都会映射到一个标准的方法。下面演示一个示例:(截图)
通过 ++ 操作符实现了字符a到c的循环打印,该操作符映射的是String类的next()方法,还可以进一步使用range的简介循环:
集合也有操作符,例如 << 操作符表示向集合中添加元素,相当于leftShift()方法。
输出结果
7.陷阱
groovy确实有很多不错的功能,但是同时也存在一些“陷阱”。
7.1.groovy的==等价于java的equals()
java的==和equals()本来就已经够混乱的了,groovy又再一次加剧了“悲剧”的发生。
groovy中,==操作符映射到了java的equals()方法,那么如果我们想要对比对象的引用地址是否想等(就是java中的 ==)怎么办呢,必须使用groovy中的 is()方法,如下:
def str1 = "hello"def str2 = str1def str3 = new String("hello")def str4 = "Hello"// trueprintln "str1 == str2: ${str1 == str2}"// trueprintln "str1 == str3: ${str1 == str3}"// falseprintln "str1 == str4: ${str1 == str4}"// trueprintln "str1.is(str2): ${str1.is(str2)}"// falseprintln "str1.is(str3): ${str1.is(str3)}"// falseprintln "str1.is(str4): ${str1.is(str4)}"
实际上groovy的==并不总是映射到java的equals()方法,只有该类没有实现Comparable的时候才成立,否则groovy的==会映射到该类的compareTo()方法。
通过输出发现,实现了Comparable接口的,==映射到了compareTo()方法,而不是equals()方法,所以,在比较对象的时候,首先确定自己想要对比的是值和引用地址,然后再确定是不是使用了正确的操作符(==还是is方法)
7.2.编译时类型检查默认关闭
groovy类型是可选的,然而groovy编译时大部分情况下不会检查完整的类型,而是在运行时遇到类型执行强制类型转换,例如:
这段代码可以正常编译,但是运行时就会报错,因为groovy不会检查,运行时候 hello 强制转换为int类型的时候就报错了,所以groovy把这工作留给了运行时处理。
所以groovy中的 x = y,实际上就是 x = (ClassOfX)y。类似的,如果调用了一个不存在的方法,编译也是通过的,运行时就会报错:
为什么groovy这么做,后续会说明,其实这反而对groovy来说是一个有点哟!
7.3.新的关键字
groovy的 in ,def 都对于java来说是新的关键字,需要注意。
同时注意 it,这对groovy来说也是一个特殊的关键字。
7.4.创建基本类型的数组
groovy创建一个int数组,如下:
int[] size = [1,2,3]
定义了一个数组,如果去掉了 int[],groovy就会认为定义了一个集合:
def size = [1,2,3]
这样就是一个集合了,如果还想这么定义一个数组,就需要做出一点改变,如下:
def size = [1,2,3] as int[]