14. Groovy 语言结构-脚本和类知识学习

2022-12-07 18:31:24 浏览数 (1)

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注释。以这种方式注释的变量将成为生成的脚本类的字段,并且,对于局部变量,访问不会涉及脚本绑定。示例如下:

代码语言:javascript复制
import groovy.transform.Field  //必须添加import 否则提示找不到Field对象

@Field
String zin='zinyan.com'  //通过@Field注释将这个变量定义为脚本类的类属性值。

//创建一个
def getTestZin(){
    zin
}
println(getTestZin()) //输出:zinyan.com

但如果有一个与绑定变量同名的本地变量或脚本字段,可以使用binding.varName访问绑定变量。示例如下:

代码语言:javascript复制
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

0 人点赞