1. 概述
2. 环境配置
在Maven项目中使用Groovy时,需要在pom.xml中添加以下配置:
<build>
<plugins>
// ...
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.5</version>
</plugin>
</plugins>
</build>
<dependencies>
// ...
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.10</version>
</dependency>
</dependencies>
最新版Maven插件可查看这里,groovy-all最新版可查看这里。
3. 核心特性
Groovy提供了许多实用特性。下面我们来看看这门语言的基础构件及其与Java的差异。
3.1. 动态类型
Groovy最重要的特性之一是支持动态类型。
类型定义是可选的,实际类型在运行时确定。看这两个类:
class Duck {
String getName() {
'Duck'
}
}
class Cat {
String getName() {
'Cat'
}
}
这两个类都定义了getName方法,但没有显式声明接口。
现在假设有一个包含鸭子和猫的列表,它们都有getName方法。在Groovy中我们可以这样操作:
Duck duck = new Duck()
Cat cat = new Cat()
def list = [duck, cat]
list.each { obj ->
println obj.getName()
}
代码能正常编译,输出结果为:
Duck
Cat
3.2. 隐式布尔转换
类似JavaScript,Groovy在需要时会将对象自动转换为布尔值(例如在if语句或取反操作时):
if("hello") {...}
if(15) {...}
if(someObject) {...}
记住以下转换规则:
- ✅ 非空集合、数组、映射 → true
- ✅ 至少有一个匹配的Matcher → true
- ✅ 还有元素的迭代器和枚举 → true
- ✅ 非空字符串、GString和CharSequence → true
- ✅ 非零数字 → true
- ✅ 非空对象引用 → true
*若要自定义隐式布尔转换,可以定义asBoolean()方法。*
3.3. 默认导入
以下包会自动导入,无需显式声明:
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
4. AST转换
AST(抽象语法树)转换允许我们介入Groovy编译过程,按需定制编译行为。这些转换在编译时完成,运行时没有性能损耗。我们可以创建自定义AST转换,也可以直接使用内置转换。
下面介绍几个值得关注的注解。
*4.1. @TypeChecked注解*
该注解强制编译器对标注代码进行严格类型检查。类型检查机制可扩展,必要时甚至能提供比Java更严格的检查。
看下面这个例子:
class Universe {
@TypeChecked
int answer() { "forty two" }
}
尝试编译时会报错:
[Static type checking] - Cannot return value of type java.lang.String on method returning type int
@TypeChecked可应用于类和方法。
*4.2. @CompileStatic注解*
该注解让编译器像Java代码一样执行编译时检查,然后进行静态编译,绕过Groovy元对象协议。
- 标注类时:类中所有方法、属性、内部类等都会被类型检查
- 标注方法时:仅对该方法内的闭包和匿名内部类进行静态编译
5. 属性
在Groovy中,我们可以创建POGO(普通Groovy对象),功能类似Java的POJO,但更简洁——编译时会自动为公共属性生成getter/setter。⚠️ 注意:仅当这些方法未显式定义时才会生成。
这让我们既可以用开放字段定义属性,又能在需要时重写取值/赋值行为。
看这个对象:
class Person {
String name
String lastName
}
默认类、字段和方法作用域都是public,所以这是公共类,两个字段也是公共的。
编译器会将它们转为私有字段,并添加*getName()、setName()、getLastName()和setLastName()*方法。如果显式定义了某字段的getter/setter,编译器就不会再生成。
5.1. 简化写法
Groovy为属性访问提供了简化语法。不必像Java那样调用getter/setter,可以直接用字段访问语法:
resourceGroup.getResourcePrototype().getName() == SERVER_TYPE_NAME
resourceGroup.resourcePrototype.name == SERVER_TYPE_NAME
resourcePrototype.setName("something")
resourcePrototype.name = "something"
6. 操作符
下面看看Groovy在Java操作符基础上新增的特性。
6.1. 空安全解引用
最常用的是空安全解引用操作符“?”,调用null对象的方法或属性时能避免NullPointerException。在链式调用中特别有用,因为链中任何环节都可能返回null。
例如可以安全调用:
String name = person?.organization?.parent?.name
上例中如果person、person.organization或organization.parent为null,整个表达式返回null。
6.2. Elvis操作符
Elvis操作符“?:”可以简化三元表达式。下面两种写法等价:
String name = person.name ?: defaultName
和
String name = person.name ? person.name : defaultName
两者都在person.name为Groovy真值(非null且长度非零)时将其赋值给name。
6.3. 太空船操作符
太空船操作符“<=>”是关系操作符,功能类似Java的*compareTo()*:比较两个对象,根据参数值返回-1、0或+1。
- 左参数 > 右参数 → 返回1
- 左参数 < 右参数 → 返回-1
- 参数相等 → 返回0
最大优势是能优雅处理null,x <=> y永远不会抛出NullPointerException:
println 5 <=> null
上例会输出1。
7. 字符串
Groovy提供多种字符串字面量表示法。支持Java的双引号字符串,也允许使用单引号。
多行字符串(其他语言常称为heredoc)使用三引号(单双引号均可):
def multiline = """
第一行
第二行
"""
双引号字符串支持使用*${}*语法插值:
def name = "Bill Gates"
def greeting = "Hello, ${name}"
实际上*${}*中可放任意表达式:
def name = "Bill Gates"
def greeting = "Hello, ${name.toUpperCase()}"
包含*${}表达式的双引号字符串称为GString,否则是普通String*对象:
def a = "hello"
assert a.class.name == 'java.lang.String'
def b = 'hello'
assert b.class.name == 'java.lang.String'
def c = "${b}"
assert c.class.name == 'org.codehaus.groovy.runtime.GStringImpl'
8. 集合与映射
看看Groovy如何处理基础数据结构。
8.1. 列表
Java中创建ArrayList并添加元素:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
Groovy中只需:
List list = ['Hello', 'World']
列表默认是java.util.ArrayList类型,也可显式调用构造函数声明。
没有专门的Set语法,但可以用类型强制转换:
Set greeting = ['Hello', 'World']
或
def greeting = ['Hello', 'World'] as Set
8.2. 映射
映射语法类似,但需指定键值对(用冒号分隔):
def key = 'Key3'
def aMap = [
'Key1': 'Value 1',
Key2: 'Value 2', // 注意:Key2会被当作变量名而非字符串
(key): 'Another value' // 使用括号强制作为变量求值
]
初始化后会得到包含以下条目的LinkedHashMap:Key1 -> Value1, Key2 -> Value 2, Key3 -> Another Value。
访问映射条目的多种方式:
println aMap['Key1']
println aMap[key]
println aMap.Key1
9. 控制结构
9.1. 条件语句:if-else
Groovy支持标准的if/else语法:
if (...) {
// ...
} else if (...) {
// ...
} else {
// ...
}
9.2. 条件语句:switch-case
def x = 1.23
def result = ""
switch (x) {
case "foo":
result = "found foo"
break
case [4, 5, 6, 'inList']:
result = "is in list"
break
case 12..30:
result = "is in range"
break
case Number:
result = "is number"
break
case { it > 3 }:
result = "greater than 3"
break
default:
result = "default"
}
assert result == "is number"
9.3. 循环:while
Groovy支持标准的while循环:
def x = 0
def y = 5
while ( y-- > 0 ) {
x++
}
9.4. 循环:for
Groovy推崇这种简洁的for循环结构:
for (variable in iterable) { body }
for循环遍历iterable。常用可迭代对象包括:范围、集合、映射、数组、迭代器和枚举。实际上任何对象都可以是可迭代的。
如果循环体只有一条语句,花括号可省略。以下是遍历范围、列表、数组、映射和字符串的示例:
def x = 0
for ( i in 0..9 ) {
x += i
}
x = 0
for ( i in [0, 1, 2, 3, 4] ) {
x += i
}
def array = (0..4).toArray()
x = 0
for ( i in array ) {
x += i
}
def map = ['abc':1, 'def':2, 'xyz':3]
x = 0
for ( e in map ) {
x += e.value
}
x = 0
for ( v in map.values() ) {
x += v
}
def text = "abc"
def list = []
for (c in text) {
list.add(c)
}
对象迭代使Groovy的for循环成为强大的控制结构。它是使用闭包迭代对象方法(如Collection的each方法)的有效替代方案。
主要区别在于:for循环体不是闭包,只是一个代码块:
for (x in 0..9) { println x }
而这是一个闭包:
(0..9).each { println it }
虽然外观相似,但构造方式完全不同。
闭包是独立对象,具有特殊特性。它可以在不同位置创建并传递给each方法。而for循环体直接在其出现位置生成为字节码,没有特殊作用域规则。
10. 异常处理
最大区别是:不强制处理受检异常。
处理通用异常时,可将可能抛出异常的代码放在try/catch块中:
try {
someActionThatWillThrowAnException()
} catch (e) { // 注意:这里原文有语法错误,已修正
// 记录错误消息和/或以某种方式处理
}
不声明捕获的异常类型时,任何异常都会被捕获。
11. 闭包
简单说,闭包是可传递给变量的匿名可执行代码块,能访问其定义上下文中的数据。
类似匿名内部类,但不实现接口或继承基类。也类似Java中的lambda表达式。
有趣的是,Groovy能充分利用为支持lambda而新增的JDK特性,特别是流式API。任何需要lambda表达式的地方都可以使用闭包。
看下面例子:
def helloWorld = {
println "Hello World"
}
变量helloWorld现在持有闭包引用,可通过调用其call方法执行:
helloWorld.call()
Groovy允许使用更自然的方法调用语法——它会自动调用call方法:
helloWorld()
11.1. 参数
和方法一样,闭包可以有参数。有三种变体:
// 无参数
def noParams = { println "No params" }
// 隐式单参数(默认名为it)
def printTheParam = { println it }
// 显式多参数
def power = { int x, int y ->
return Math.pow(x, y)
}
调用方式:
noParams()
printTheParam('hello')
printTheParam 'hello' // Groovy允许省略括号
println power(2, 3)
参数类型定义与变量相同。定义类型后只能传入该类型,但也可省略类型传入任意值:
def say = { what ->
println what
}
say "Hello World"
11.2. 可选返回
闭包的最后一条语句可隐式返回,无需写return语句。这能最大限度减少样板代码。计算数字平方的闭包可简化为:
def square = { it * it }
println square(4)
此闭包使用了隐式参数it和可选return语句。
12. 总结
本文快速介绍了Groovy语言及其核心特性。我们从基础概念(如基本语法、条件语句和操作符)开始,也演示了一些高级特性(如操作符和闭包)。
若想了解更多语言细节和语义,可直接访问官方文档。