1.介绍
本篇内容为Groovy学习第十四篇内容。本篇内容为Groovy语言中的脚本(Script)和类(classes)的知识。
让我们明白,Groovy的脚本编写的实现过程。让我们知道,为什么groovy中可以不用输入main函数,就可以运行了。
groovy的代码运行时会从哪个开始。你如果对这方面知识有些苦恼。那么这篇内容应该能够进行部分解答。
2. 脚本和类
Groovy支持脚本和类。以下代码为例:创建一个Main.groovy类:
代码语言:javascript复制class Main {
static void main(String... args) {
println 'zinyan.com 欢迎你'
}
}
在Groovy中,也有main函数作为入口。可以按照上面的示例写main函数,也可以用下面的示例写:
代码语言:javascript复制class Main {
def static main(def args) {
println 'zinyan.com 欢迎你'
}
}
甚至我们可以不用写main函数,作为省略。我在前面多篇学习内容中。部分代码没有main也仍然能够正常运行。
只要将内容放到一个.groovy
文件中进行执行就可以了。
上面的示例,就和java中的代码运行必须有一个main函数一样。而在Groovy中,代码可以作为脚本运行
上面的示例和下面的示例,运行效果是等效的:
代码语言:javascript复制 println 'zinyan.com 欢迎你'
直接省略掉类名创建和main函数创建。这种写法,就是Script脚本式了。脚本可以视作一个类对象而且不需要声明它(class xxx)。
2.1 Script 类
我们上面省略掉类声明和main函数,只是我们在编写代码的省略。并不代表就真的省略了main函数。
我们使用脚本式写法时,在Groovy类被编译器进行编译时会自动帮我们进行代码补全而已,就用上面的代码做示例,当我们只写了一行输出代码时系统编译后会产生如下:
代码语言:javascript复制import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {
def run() {
println 'zinyan.com 欢迎你'
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
}
Groovy编译器将为我们编译该类,并将脚本主体复制到run
方法中。
PS:Groovy的语言最终都是被翻译成了java的字节码,才能被JVM进行解析和运行的。
如果脚本位于文件中,则使用文件的名称来确定生成的脚本类的名称。在本例中,如果文件名为Main.groovy
,那么脚本类将为Main。
2.2 方法-methods
Groovy通过脚本写法可以省略很多样板代码。我们如果突然从java的写法中转为到Groovy中。需要学习的就是这个脚本的写作规则。
我们如果通过脚本写法,创建函数方法。原先标准写法是创建类,然后再创建方法。而现在可以直接创建方法,而不用在意类是否创建。
当然,我们如果仍然使用类 函数的创建方式,也是可以的这里不做多介绍,示例如下:
代码语言:javascript复制int fib(int n) {
n < 2 ? 1 : fib(n-1) fib(n-2)
}
println(fib(2)) //输出2
例如我创建上面的示例,我们还可以使用def获取返回值,甚至才用闭包模式创建函数等等,效果如下:
代码语言:javascript复制println '欢迎访问 zinyan.com' //输出: 欢迎访问 zinyan.com
// 创建了一个3的幂运算方法
def power(int n) { 3**n }
//输出:3的6数值结果
println "3^6==${power(6)}" // 输出: 3^6==729
例如上面的代码,我们只需要任意创建一个.groovy
文件,然后将上面的代码复制进去。就可以执行了。Groovy在编译时会自动将我们的代码转为Script类。并将代码移动到run函数中调用。最后得到我们希望的输出结果。
上面的类转换后的效果为:
代码语言:javascript复制import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script {
def power(int n) { 3** n}
def run() {
println '欢迎访问 zinyan.com
println "3^6==${power(6)}"
}
static void main(String[] args) {
InvokerHelper.runScript(Main, args)
}
}
只有在调用的方法才会放在run函数中。我们创建的def power()
会自动转为函数方法。它们都在一个类中。这也是我们能够随意使用方法而不需要先new 的原因所在了。
即使Groovy从脚本创建了一个类,它对用户来说也是完全透明的。特别是,脚本被编译为字节码,行号被保留。这意味着,如果在脚本中抛出异常,堆栈跟踪将显示与原始脚本相对应的行号,而不是我们显示的生成代码。
也就是即时在编译运行过程中出现了错误,堆栈输出的错误行号等信息也会和我们编写的代码行号对应,而不是生成后的代码中的行号对应。
2.3 变量-variables
脚本中的变量不需要类型定义。可以只需要创建变量名就可以了。连def关键字都是可以省略的,示例如下:
代码语言:javascript复制// java中标准创建变量的方法
int x =2022
String z ="zinyan.com"
//Groovy中可以通过def关键字动态创建变量
def x1 =2022
def z1 ='zinyan.com'
//上面两种方式是等效的,而在Scripts 脚本模式下,我们可以将def都进行省略
x2 =2022
z2 ='zinyan.com'
println(x2) //输出:2022
println(z2) //输出:zinyan.com
效果是一样的,但是采用脚本的省略方法还是会有部分差异的。
当我们使用带数据类型的int,String或者def创建变量时,它定义的是一个局部变量,在编译器运行时将会在run方法中创建。
在脚本的其他方法中不可见。
而我们如果未声明变量的类型,那么将会在 groovy.lang.Script#getBinding()
方法中进行绑定。并且可以被脚本中的其他函数进行使用。
补充一下上面介绍的示例代码:
代码语言:javascript复制// java中标准创建变量的方法
int x =2022
String z ="zinyan.com"
//Groovy中可以通过def关键字动态创建变量
def x1 =2022
def z1 ='zinyan.com'
//上面两种方式是等效的,而在Scripts 脚本模式下,我们可以将def都进行省略
x2 =2022
z2 ='zinyan.com'
//创建一个函数
def test1(){
z
}
println(test1()) //打印z的值,正确输出应该是:zinyan.com
但是实际运行结果却显示:
代码语言:javascript复制[Running] groovy "f:Zinyan.groovy"
Caught: groovy.lang.MissingPropertyException: No such property: z for class: Zinyan
groovy.lang.MissingPropertyException: No such property: z for class: Zinyan
at Zinyan.test1(Zinyan.groovy:15)
at Zinyan.run(Zinyan.groovy:18)
[Done] exited with code=1 in 6.366 seconds
我们如果将test1中的z更换为z2,示例如下:
代码语言:javascript复制//创建一个函数
def test1(){
z2
}
println(test1()) //输出:zinyan.com
如果想使定义了类型的变量对所有方法可见的另一种方法是使用@Field
注释。以这种方式注释的变量将成为生成的脚本类的字段,并且,对于局部变量,访问不会涉及脚本绑定。示例如下:
import groovy.transform.Field //必须添加import 否则提示找不到Field对象
@Field
String zin='zinyan.com' //通过@Field注释将这个变量定义为脚本类的类属性值。
//创建一个
def getTestZin(){
zin
}
println(getTestZin()) //输出:zinyan.com
但如果有一个与绑定变量同名的本地变量或脚本字段,可以使用binding.varName
访问绑定变量。示例如下:
zin='zinyan.com'
//创建一个闭包对象
def zin={
return 'Z同学关于Groovy的学习'
}
println(zin) //输出的是:Zinyan$_run_closure1@1f57539 内存地址而不是 zinyan
println(binding.zin) //输出 zinyan.com
如果绑定名和变量名重复了,会怎么样?(groovy不建议两者重名)
代码语言:javascript复制import groovy.transform.Field //必须添加import 否则提示找不到Field对象
//创建一个绑定对象,这个会自动添加到getBinding方法中
zin='zinyan.com'
@Field
String zin ='z 同学'
println(zin) //将会输出 zinyan.com
println(this.zin) //将会输出 zinyan.com
3. 小结
主要学习了Groovy的脚本语言的写法,以及它背后的转换规则。让我们在脚本模式下创建变量时,弄明白哪种情况下创建的变量可以被方法使用。哪种情况下创建的变量无法被方法使用。
简单学习了Groovy的脚本写法的知识。
学习内容参考Groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_scripts_versus_classes