新的和改进的语法元素,结构和句法结构
一方面,Groovy添加新的语法元素、循环结构和语言级构建器;一方面,改进已有Java语法元素和结构,以提高其易用性。接下来,将详细讨论Groovy中一些常用的语法。
语言级断言(Assertions)
您一定注意到前面多数例子都用到assertion。事实上,为编写本书实例代码,断言的确帮了我很大的忙,因为它可广泛应用于判断结果输出的正确性,因此编写代码实例变得简单。断言也是学习、体验Groovy的最佳途径。断言通常用于编写自校验码(self- checking code),显示当前程序的状态,并起到解释代码的作用。然而,他们比单纯的代码注释显得更加有用,这是因为因为代码运行时断言也随着被执行。同样的原因,断言也要比利用输出语句(print statements)把输出结果打印到控制台要有效得多。清单2.4给出了断言的一个例子。
清单2.4 断言(Assertions)的应用
x = 1
assert x // x must be not null
assert (x==1)
assert ['a'] // A list must be nonempty
assert ['a':1] // A map must be nonempty
assert 'a' // A String must be nonempty
assert 1 // A number must not be equal to zero
assert !null // Null will always fail
assert true // A true Boolean value returns true
从清单中您可发现,Groovy的断言要比Java中的assert关键字要功能强大得多,原因是Groovy断言能接受任何类型,而Java断言只能用于Boolean条件而已。Groovy会按照一定规则将非布尔型对象强制为布尔值:空集合和字符串,零,空对象均被转型为false;反之为true。
Java中的断言可以被禁用,而Groovy断言则相反,他们能被执行,不会被禁用。
如果Groovy中断言失败,程序会会抛出一条的错误信息,如:
a = [1,2,3]
assert a.size() == 2, "list ${a} must be of size 2"
运行上面的代码会出现错误,并会抛出如下信息:
Exception thrown: java.lang.AssertionError:list [1,2,3] must be of size 2.
Expression: (a.size() == 2)
闭包(Closure)
简单地说,闭包是一个能被传递和执行的代码块。闭包可有参数、返回值,可引用其他变量等等。闭包定义方式为:
{arg1, arg2 .. -> statements}
“->”字符用来分离可选参数列表(the optional arguments)和闭包代码体(statements)。
尽管闭包的概念及语法在Java开发者看来是陌生的,但总的来说闭包还是容易学习可立即上手的。闭包的很多高级应用将会在第5章详细介绍。本节只是简单介绍一下闭包,让您对它有所了解。
清单2.5显示如何使用闭包的一个简单例子。
清单2.5 闭包的应用
//Simple closure with no arguments
def clos1 = { println "hello world!" }
//Calling the closure
clos1()
//A closure with arguments
def clos2 = {arg1,arg2 -> println arg1+arg2}
clos2(3,4)
//A closure defined inside a method. The closure is bound to the
//variables within its scope
def method1(book){
def prefix = "The title of the book is: "
return {println prefix + book}
}
def clos3 = method1("Groovy")
clos3()
您或许会问,闭包与Java方法到底存在怎样的差别呢?是这样的,闭包是匿名的代码块,它可以在类或方法外声明,只在被调用时才被执行。闭包通常被赋给一个变量,可看作这个闭包的标识符,并通过它来调用闭包。闭包真正强大的功能就是该变量能在程序中传递,这就意味着您可以编写以闭包作为参数的闭包和方法。
为了更好说明问题,看下面这个例子,类Emloyee只有一个calculateRaise方法,该方法以闭包为参数,来计算如何给雇员加薪。例如,您想给某些员工薪水提高50%,而另一部分员工只加薪300美元。代码详见清单2.6。
清单2.6 闭包为方法参数的例子
public class Employee{
def salary
public double calculateRaise(c){
return c(salary)
}
}
Employee employee1 = new Employee(salary:1000)
def raise1 = {salary -> (salary * 1.5)}
assert employee1.calculateRaise(raise1) == 1500
Employee employee2 = new Employee(salary:500)
def raise2 = {salary -> (salary + 300)}
assert employee2.calculateRaise(raise2) == 800
如果用Java编写上述代码,您很可能要定义一个Raise接口,该接口只有一个calculateRaise方法。然后创建两个Raise接口实现,并且实现calculateRaise方法。最后,创建Employee类,并且含有一个以Raise实例作为参数的方法,然后调用calculateRaise方法。然而相比较而言,Groovy的实现方式更简单直接,不需要引入太多的数据类型。可以这么说,如果使用了闭包,您将很少用得到接口了。
集合数据类型(Collective Data Types)
第一章曾提到,Groovy最强大的特性之一便是对集合语言级支持,包括列表(lists),映射(maps)和范围(ranges)。其中,列表和映射是Java程序员最熟悉不过的了,然而在Groovy中显得更强大和灵活;范围是Java中不存在的新数据结构。下面将简单介绍这些集合的新句法结构,具体地将在第3章进行讨论。
列表(Lists)
Groovy中列表的语法类似于Java中的数组,但是不要被其外表所迷惑。Groovy列表远比Java数组强大,因为Java数组的长度固定,且不容易添加元素到数组中。Groovy中可按下面的方式定义列表:
def a = [item1, item2, item3]
数组的定义方式:
def a = new Object[4] //必须制定数组的长度
或:
def a = [item1, item2, item3].toArray()
集合中可以包含不同类型的元素,任何java.lang.Object的子类均可添加到列表中。下面代码在Groovy是正确无误的:
a = ['Hi', 1, File]
清单2.7展示了Groovy列表的基本应用。
清单2.7 Groovy列表
def a = [] //Empty list
a += [1,2,3] //Adding elements to a list
assert a == [1,2,3]
assert a.size == 3
a << 4 << 5 //Another way of adding elements to a list
assert a == [1,2,3,4,5]
a.add(6) //A third way of adding elements to a list
assert a == [1,2,3,4,5,6]
//Accessing elements of a list
assert a[0] == 1 //Using a subscript
assert a.get(0) == 1 //Using get
assert a.getAt(0) == 1 //Using getAt
assert a[-1] == 6 //Last element index starts at -1 backwards
//Modifying elements in a list
a.putAt(1,1)
assert a == [1,1,3,4,5,6]
assert a.set(1,2) == 1 //Will return the old value
assert a == [1,2,3,4,5,6]
//Iterating over a list
a.each{ println "$it"}
//Printing items in a list with their index
a.eachWithIndex{it, index -> println item : "$it", index : "$index"}
//Removing items from a list
a -= 1 //Remove number 1
assert a == [2,3,4,5,6]
a = a.minus([2,3,4]) //Remove the sublist [2,3,4]
assert a == [5,6]
映射(Maps)
映射,是一种将键(Key)映射到值(Value)的对象:一个映射不能包含重复的键;每个键只能映射到一个值。Groovy中定义映射方式为:
def a = [key1:value1, key2:value2]
键和值可以为任意类型。清单2.8为Groovy映射的基本应用
//Creating a map
def map = ['name':'Bashar','age':26,skills:['Java','Groovy'], 'author':true]
assert map.size() == 4
//Adding a key/value pair to a map
map += ['city':'Tucson']
assert map == ['name':'Bashar','age':26,skills:['Java','Groovy'],
'author':true, 'city':'Tucson']
//Alternative way of adding a key/value pair to a map
map['state'] = 'AZ'
assert map == ['name':'Bashar','age':26,skills:['Java','Groovy'],
'author':true, 'city':'Tucson', 'state':'AZ']
//Accessing map elements
assert map.city == 'Tucson'
assert map['city'] == 'Tucson'
assert map.get('city') == 'Tucson'
assert map.getAt('city') == 'Tucson'
assert map.skills[0] == 'Java'
//Keys are unique
assert ['name':'Bashar','name':'Abdul'] == ['name':'Abdul']
//Iterating over a map
map.each{ it -> println it.key + ":" + it.value}
map.eachWithIndex{ it, index -> println "item $index - " + it.key + ":" + it.value}
范围(Ranges)
Groovy语言级支持范围(Range)语法,并且可以指定是否包含边界值。从概念上讲,范围定义了由左边界值和右边界值指定的区间,并实现了如何从区间左边移到区间右边。范围可用于数值、字符串、日期,以及任何实现了Comprable接口并定义了next和previous方法的对象。按如下方式定义范围:
def range = start ..end
与其他结构结合使用时,范围会显得方便有效,尤其借助于范围的each方法。清单 2.9演示了如何使用Groovy中的范围。
清单 2.9 Range的应用
//Creating a range
def range = 1..10
assert range == [1,2,3,4,5,6,7,8,9,10]
range = 'a'..'c'
assert range == ['a','b','c']
//Excluding the last element from a range
range = 1..<8
assert range == [1,2,3,4,5,6,7]
//Using a range with each method
(1..5).each{println it}
//Using a range to create a list (slicing)
assert [*1..4] == [1,2,3,4]
assert [1,*2..4] == [1,2,3,4]