Groovy 程序结构
本章讲述Groovy编程语言的程序结构
1. 包名
包名与Java中的作用完全相同。它们允许我们在没有任何冲突的情况下分离代码库。Groovy类必须在类定义之前指定它们的包,否则将使用默认包。
定义包与Java非常相似:
// defining a package named com.yoursite package com.yoursite
要引用com.yoursite.com包中的某个类Foo,需要使用完全限定名com.yoursite.com.Foo,否则可以使用import语句,如下所示。
2. 引入包
为了引用任何类,您需要对其包的限定引用。Groovy遵循Java的概念,允许import语句解析类引用。
例如,Groovy提供了几个生成器类,比如MarkupBuilder。MarkupBuilder位于groovy.xml包中,因此要使用此类,需要导入它,如下所示:
// importing the class MarkupBuilder import groovy.xml.MarkupBuilder // using the imported class to create an object def xml = new MarkupBuilder() assert xml != null
2.1. 默认引入
默认导入是Groovy语言默认提供的导入。例如,查看以下代码:
new Date()
Java中的同一代码需要一个import语句来创建类似这样的Date类:import Java.util.Date。Groovy默认为您导入这些类。
groovy为您添加了以下导入:
import java.lang.* import java.util.* import java.io.* import java.net.* import groovy.lang.* import groovy.util.* import java.math.BigInteger import java.math.BigDecimal
这样做是因为这些包中的类是最常用的。通过导入这些样板代码,代码就减少了。
2.2. 简单引入
简单导入是一个import语句,您可以在其中完全定义类名和包。例如,下面代码中的import语句import groovy.xml.MarkupBuilder是一个简单的import,它直接引用包中的类。
// importing the class MarkupBuilder import groovy.xml.MarkupBuilder // using the imported class to create an object def xml = new MarkupBuilder() assert xml != null
2.3. 星号引入
Groovy与Java一样,提供了一种特殊的方法,使用*从包中导入所有类,即所谓的star import。MarkupBuilder是groovy.xml包中的一个类,与另一个名为StreamingMarkupBuilder的类一起。如果需要同时使用这两个类,可以执行以下操作:
import groovy.xml.MarkupBuilder import groovy.xml.StreamingMarkupBuilder def markupBuilder = new MarkupBuilder() assert markupBuilder != null assert new StreamingMarkupBuilder() != null
这是完全有效的代码。但是使用*导入,我们只需一行就可以达到相同的效果。star导入groovy.xml包下的所有类:
import groovy.xml.* def markupBuilder = new MarkupBuilder() assert markupBuilder != null assert new StreamingMarkupBuilder() != null
*导入的一个问题是它们可能会使本地命名空间混乱。但是通过Groovy提供的各种别名,可以很容易地解决这个问题。
2.4. 静态引入
Groovy的静态导入功能允许您引用导入的类,就像它们是您自己类中的静态方法一样:
import static Boolean.FALSE assert !FALSE //use directly, without Boolean prefix!
这类似于Java的静态导入功能,但比Java更具动态性,因为它允许您定义与导入方法同名的方法,只要您有不同的类型:
import static java.lang.String.format // 导入静态方法 class SomeClass { String format(Integer i) { // 定义与导入的静态方法相同名称的方法 i.toString() } static void main(String[] args) { assert format('String') == 'String' // java编译中会报异常,groovy代码是合法的。 assert new SomeClass().format(Integer.valueOf(1)) == '1' } }
如果类型相同,则导入的类优先。
2.5. 静态引入别名
带有as关键字的静态导入为命名空间问题提供了一个优雅的解决方案。假设您想使用日历实例的getInstance()方法获取日历实例。这是一个静态方法,所以我们可以使用静态导入。但是,我们可以使用别名导入getInstance(),以提高代码可读性,而不是每次都调用getInstance(),如果将getInstance与其类名分离,则会产生误导:
import static Calendar.getInstance as now assert now().class == Calendar.getInstance().class
现在干净了!
2.6. 静态星引入
静态星形导入与常规星形导入非常相似。它将从给定的类导入所有静态方法。
例如,假设我们需要为应用程序计算正弦和余弦。类java.lang.Math具有符合我们需要的名为sin和cos的静态方法。借助静态星型导入,我们可以:
import static java.lang.Math.* assert sin(0) == 0.0 assert cos(0) == 1.0
如您所见,我们可以直接访问sin和cos方法,而不需要Math前缀。
2.7. 引入别名
使用类型别名,我们可以使用我们选择的名称引用完全限定的类名。这可以通过as关键字完成,就像以前一样。
例如,我们可以将java.sql.Date导入为SQLDate,并在与java.util.Date相同的文件中使用它,而不必使用任何一个类的完全限定名:
import java.util.Date import java.sql.Date as SQLDate Date utilDate = new Date(1000L) SQLDate sqlDate = new SQLDate(1000L) assert utilDate instanceof java.util.Date assert sqlDate instanceof java.sql.Date
3. 脚本 versus 类
3.1. public static void main vs script
Groovy同时支持脚本和类。以下面的代码为例:
Main.groovy
class Main { // 定义一个Main类,名称随意 static void main(String... args) { // public static void main(String[]) 是类的主方法 println 'Groovy world!' // main的方法体 } }
这是来自Java的典型代码,在Java中,代码必须嵌入到类中才能执行。Groovy使之更简单,以下代码是等效的:
Main.groovy
println 'Groovy world!'
脚本可以看作是一个类,而不需要声明它,但有一些区别。
3.2. 脚本类
脚本总是编译成类。Groovy编译器将为您编译类,并将脚本体复制到run方法中。因此,前一个示例的编译方式如下:
Main.groovy
import org.codehaus.groovy.runtime.InvokerHelper class Main extends Script { // 继承groovy.lang.Script的Main类 def run() { // groovy.lang.Script需要一个返回值的run方法 println 'Groovy world!' // 脚本体进入到run方法 } static void main(String[] args) { // 自动生成main方法 InvokerHelper.runScript(Main, args) // 在run方法上执行脚本的委托 } }
如果脚本在文件中,则文件的基名称用于确定生成的脚本类的名称。在本例中,如果文件名是Main.groovy,那么脚本类将是Main。
3.3. 方法
可以将方法定义到脚本中,如下所示:
int fib(int n) { n < 2 ? 1 : fib(n-1) + fib(n-2) } assert fib(10)==89
您还可以混合方法和代码。生成的脚本类将把所有方法带入脚本类,并将所有脚本体组装到run方法中:
println 'Hello' // 脚本开始 int power(int n) { 2**n } // 脚本体中定义一个方法 println "2^6==${power(6)}" // 脚本继续
此代码在内部转换为:
import org.codehaus.groovy.runtime.InvokerHelper class Main extends Script { int power(int n) { 2** n} // power方法被复制到自动生成的脚本类中 def run() { println 'Hello' // 第一个语句被复制到run方法中 println "2^6==${power(6)}" // 第二个语句被复制到run方法中 } static void main(String[] args) { InvokerHelper.runScript(Main, args) } }
尽管Groovy会自动为脚本创建一个类,但这些对用户来说都是透明的。脚本会被编译成二进制码,行号会被保存下来。也就是说如果脚本出现异常,可以很好的追踪到哪一行脚本出错。
3.4. 变量
脚本中的变量不需要类型定义。这意味着此脚本:
int x = 1 int y = 2 assert x+y == 3
将表现为:
x = 1 y = 2 assert x+y == 3