背景
使用gradle 构建的项目中会使用groovy语言来完成项目构建的配置文件build.gradle
的编写, 为了更好的写这个配置文件, 所以花了点时间深入学习了一下groovy这么语言.groovy可以看做是JVM上的一门脚本语言, 它可以无缝的使用JDK中的jar包,它也是Java语言的扩展.groovy中具有很多脚本语言的共性(比如弱类型, 闭包等等), groovy中存在两个看起来类似的概念,即是方法与闭包.因此本文总结了groovy中方法与闭包的共同点与区别.
不同点
方法与闭包作用域问题
首先看如下的情况
def val = "hello"
def test_func() {
println val
}
test_func()
按照以往脚本的编程经验, 上述函数可以正确的输出结果, 然而在groovy中却报错
groovy.lang.MissingPropertyException: No such property: val for class: ConsoleScript42
定义的test方法不可以获取到方法外定义的val
变量.原因是val
的作用域不包含test_func方法. 理解这一点需要了解groovy的一种运行方式. Groovy本身也是运行于JVM中, 它需要被编译成class字节码文件运行, 当我们将上述的groovy脚本编译成class文件, 然后通过反编译工具查看反编译的Java文件大致如下:
public class test{
public static void main(String... args){
// call run
}
public Object run(){
}
public Object test_func(){
}
}
可以看到创建的Java 类中会有一个run方法, 这个run方法的作用域其实就是编写groovy脚本的顶层属性的scope(val 变量所在的scope), 而test_func这个方法只是类中的一个方法而已,它和run方法是并列的两个不同的方法,作用域不同自然不能够相互访问其中的属性.
方法递归与闭包递归
一个简单的递归例子, 比如使用递归的方法求解一个list中的最大值, 使用函数的代码实现如下:
def max_val(list, val) {
if(list.size() == 1) return list[0]>val?list[0]:val
else return max_val(list[1..<list.size()], list[0]>val?list[0]:val)
}
与其他脚本语言一样,比较简单, 但是如果使用闭包实现, 代码如下:
def max_val_clo = {list,val->{
if(list.size() == 1) return list[0]>val?list[0]:val
else return call(list[1..<list.size()], list[0]>val?list[0]:val)
}
}
关键的就是在闭包里面使用call
指代本身,一些教程里面在这里实现递归可能会用闭包的名字(此处是max_val_clo)或者是this.call
, 但是在最新的groovy中, 这些语法都会出错. 在初次接触groovy的闭包的时候就了解到闭包可以使用call方法接受参数运行,如下:
def clo = {val -> println "hello ${val}"}
clo("world")
clo.call("world")
所以在闭包中使用递归则用call
直接表示闭包本身,不需要其他额外的语法.
重载问题
函数支持重载而闭包没有重载这个概念.如下一个简单的方法重载:
def func(arg1, arg2) {
arg1*arg2
}
def func(arg1){
arg1*2
}
println "${func(2, 3)}"
println "${func(4)}"
Groovy中定义的函数重载是参数数量不同函数名称相同,实际上如果函数的形参类型不同也可以重载,比如如下的函数使用了静态的参数类型就可以重载
def func(String arg1) {
arg1*2
}
def func(Number arg1){
arg1*2
}
println "${func(2)}"
println "${func('hello')}"
然而闭包就不能够进行重载
def clo = {
arg -> arg*3
}
def clo = {
arg1,arg2 -> arg1*arg2
}
println "${clo(2)}"
运行报错
The current scope already contains a variable of the name clo
其实这个问题非常好理解, 闭包一般需要与一个闭包变量关联,上述的错误其实就是重复定义变量的错误, 所以闭包里面不存在重载这个说法.
相同点
静态类型支持
Groovy同其他的脚本语言一样是弱类型的语言, 声明变量的时候不需要指明类型, 同样定义函数或者闭包的时候也不需要指明变量的类型.但是有时候为了让调用更加的安全, 也可以指定参数或者变量的类型.
def func1(arg1, arg2) {
arg1*arg2
}
println "${func1(2, 3)}"
println "${func1(3.5, 2)}"
println "${func1('test', 3)}"
运行结果:
6
7.0
testtesttest
一个简单的乘法运算如果不限定参数类型可能会得到一些意想不到不到的结果, 所以当我们的函数或者闭包需要更安全的调用时,还是需要指定参数的类型以及返回值类型
def func2(Number arg1, Number arg2) {
arg1*arg2
}
def clo = {
Number arg1, Number arg2 -> arg1*arg2
}
println "function result is ${func2(1, 2)}"
println "closure result is ${clo(1,2)}"
println "function result is ${func2('test', 2)}"
println "closure result is ${clo('test', 2)}"
后两行的运行会报错, 与预想的一样,输入类型不支持字符串类型
ConsoleScript1.func2() is applicable for argument types: (String, Integer) values: [test, 2]
Possible solutions: func2(java.lang.Number, java.lang.Number)
默认参数支持
闭包与函数都支持默认参数, 且默认参数都放置于参数列表的最后
def func(arg1, arg2='world') {
println "${arg1} ${arg2}"
}
def clo = {
arg1, arg2='world' -> println "${arg1} ${arg2}"
}
func('hello')
clo('hello')
func('nihao')
clo('nihao')
运行结果:
hello world
hello world
nihao world
nihao world
小结
本文主要分析了一下Groovy中函数与闭包的相同点与不同点,这些只是在工作和学习过程中的一些比较浅显的总结,如果后续更加深入的了解了Groovy语言以及函数与闭包的其他特点,还会继续更新本文.