Groovy 程序结构

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