天天看点

Groovy语言规范之程序结构1.包名 Package names2.导包 Imports3.脚本vs类 Script versus classes

前言:

官方关于Programm Structure的介绍:Programm Structure

下文将会介绍Groovy的程序结构。

1.包名 Package names

这里的包名同Java中的包名发挥着同样的角色。包名允许我们分隔代码从而避免冲突。Groovy类必须在定义之前指定他们的包,并且假设默认的包名存在。

定义包的方式和Java非常相似

// defining a package named com.yoursite
package com.yoursite
           

你可以调用Foo类使用com.yoursite.com.Foo,当然下面也将介绍import声明的方式引用类。

2.导包 Imports

为了引用任意一个类,而不需要包名。Groovy遵从了Java的方式允许使用import声明来解决类的导入。

例如,Groovy提供了几个builder类,例如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. 默认导入 Default imports

默认导入时Groovy语言默认导入的。例如下面的代码:

new Date() 
           

同样的代码在Java中则需要导包声明: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. 简单导包 Simple 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. 星号导入 Star import

像Java一样,Groovy提供了一个特殊的方式导入包下的所有的类通过使用*,也就是所谓的星号导包。实行星号导包前的导入方式:

import groovy.xml.MarkupBuilder
import groovy.xml.StreamingMarkupBuilder
def markupBuilder = new MarkupBuilder()
assert markupBuilder != null
assert new StreamingMarkupBuilder() != null
           

使用星号导包后:

import groovy.xml.*

def markupBuilder = new MarkupBuilder()

assert markupBuilder != null

assert new StreamingMarkupBuilder() != null

           

2.4.静态导包 Static import

在Groovy的静态导包允许你导入类之后可以向静态方法一样在你自己的类中。和Java的静态导入相似,但是在Java1.4之上才能正常工作。

用法和Java基本相似,不再赘述:

import static Boolean.FALSE

assert !FALSE //use directly, without Boolean prefix!

           

2.5.静态导包别名 Static import aliasing

这种用法比较类似于C语言中的typedef的用法。

使用as关键字的静态导包为命名空间问题提供了一个简洁的解决方案。加入你想要获得一个Calendar实例,使用getInstance()方法。它是静态方法,所以你可以使用静态导入。如果采用静态发导入,在调用时将不带类名因此容易误导使用者。我们可以使用别名的形式进行导入,以增强阅读性。

import static Calendar.getInstance as now

assert now().class == Calendar.getInstance().class

           

这样变得非常简洁易读。

2.6.静态星号导入 Static star import

星号静态导入和规则的星号导入很相似。将会导入该类的所有的静态的方法。

例如,下面的例子:

import static java.lang.Math.*
assert sin(0) == 0.0
assert cos(0) == 1.0
           

2.7 别名导入 Import aliasing

使用别名,我们可以使用自定义的名称来指向一个完全的类。同上,可以通过as关键字来实现。

考虑以下类,假设由第三方的类库提供的:

package thirdpartylib
public class MultiplyTwo {
    def static multiply(def value) {
        return value * 3 //intentionally wrong.
    }
}
           

假如我们这样使用这个库:

def result = new MultiplyTwo().multiply(2)
           

现在假设有这样一种情况,在使用了这个第三方库并且贯穿了你所有的代码,我们发现它没有给出一个正确的结果。我们怎样在一处修改,而不是修改源码,而不改变其他使用的地方?Groovy提供了简洁的方案。

使用简单的别名导入,我们可以修复这个bug像这样:

import thirdpartylib.MultiplyTwo as OrigMultiplyTwo

class MultiplyTwo extends OrigMultiplyTwo {
    def multiply(def value) {
        return value * 2 // fixed here
    }
}

// nothing to change below here
def multiplylib = new MultiplyTwo()

// assert passes as well
assert 4 == new MultiplyTwo().multiply(2)

           

这就是通过as关键字重命名导入的类解决了这个问题。

别名导入在解决星号导入时的命名冲突同样有用。

3.脚本vs类 Script versus classes

3.1. public static void main vs 脚本 public static void main vs script

Groovy同时支持class和script(即类和脚本)。使用一下代码作为例子:

Main.groovy

class Main {    //定义一个Main类,名字是任意的                              
    static void main(String... args) {          
        println 'Groovy world!'                 
    }
}
           

你会发现以上是Java中的典型代码,代码嵌入类中执行。而Groovy是的这些变得更加容易,以下代码是等价的:

Main.groovy

println 'Groovy world!'
           

你可以把脚本想象成是一个不用声明的类。

3.2 脚本类 Script class

脚本总是会被编译成类。Groovy编译器会为你编译这个类,会把脚本体复制进run方法。上面的例子会被编译如下:

Main.groovy

import org.codehaus.groovy.runtime.InvokerHelper
class Main extends Script { //Main类继承了groovy.lang.Script类                    
    def run() { //groovy.lang.Script需要run方法返回一个值                                
        println 'Groovy world!'                 
    }
    static void main(String[] args) { //自动生成          
        InvokerHelper.runScript(Main, args)     
    }
}
           

如果脚本是一个文件,文件的基本名称会被用于生成脚本类。在这个例子中,如果文件名是Main.groovy,之后脚本类的会被命名为Main。

3.3. 方法Methods

定义方法在脚本中是可以的,如下:

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'      //脚本被复制到了这里面                   
        println "2^6==${power(6)}"              
    }
    static void main(String[] args) {
        InvokerHelper.runScript(Main, args)
    }
}
           

尽管Groovy通过你的脚本创建了一个类,不过这个类对用户来说依然是易懂的。特别的,脚本被编译成字节码,行号被保留。这就意味着如果脚本中出现了一个异常,异常记录将会返回相应的原始脚本的行号,而不是你自动生成类的行号。

3.4. 变量Variables

变量在脚本中不需要类型声明。这就意味着,如下脚本:

int x = 1
int y = 2
assert x+y == 3

           

等同于:

x = 1
y = 2
assert x+y == 3

           

然而,二者之间在语义学上是不同的,表现如下:

  • 如果声明变量如同第一个例子,它是本地变量。将会被声明在run方法,在脚本外部不可用。特别的,这种变量在脚本的其他方法中不可见。
  • 如果变量没有被声明,变量会被绑定到脚本。并且对其他方法可见,你使用脚本与其他程序交互需要传递数据在脚本和程序之间,这种声明是非常有用的。

如果你希望变量变成一个类的字段而不是在Binding中,你可以使用@Field annotation.

继续阅读