一、 基础语法
Groovy 的设计哲学是 "约定优于配置" 和 "最小化语法噪音"。
1.1 类型推断与 def 关键字
在 Java 10 引入 var 之前,Groovy 早就拥有了 def。
- Java :必须显式声明类型(或使用
var)。 - Groovy :使用
def声明变量,编译器在编译期将其视为Object,但在运行期是强类型的(动态类型)。
groovy
// Groovy 写法
def name = "Alice" // 运行时为 String
def age = 25 // 运行时为 Integer
def list = [1, 2, 3] // 运行时为 ArrayList
// 你依然可以使用强类型(推荐在大型项目中保留类型声明以利用 IDE 提示)
String strictName = "Bob"
1.2 简化的书写规则
Groovy 允许你省略大量在 Java 中必须存在的"语法噪音":
- 分号(
;):完全可选。 public修饰符 :类、方法、字段默认就是public的。return关键字 :方法的最后一行表达式会自动作为返回值。- 包导入 :Groovy 默认导入了
java.lang.*,java.util.*,java.io.*,java.net.*,groovy.lang.*,groovy.util.*以及java.math.BigInteger/BigDecimal。
groovy
// Java 写法
public int add(int a, int b) {
return a + b;
}
// Groovy 写法
int add(int a, int b) {
a + b // 自动 return
}
1.3 字符串的进化:GString
Java 的 String 是不可变的,且拼接繁琐。Groovy 引入了 GString ,支持字符串插值 和多行文本。
| 语法 | 类型 | 说明 |
|---|---|---|
'Hello' |
java.lang.String |
单引号:纯字符串,不支持插值 |
"Hello $name" |
groovy.lang.GString |
双引号:支持 ${} 或 $ 插值 |
'''Multi\nLine''' |
String |
三个单引号:多行纯字符串 |
"""Multi\nLine""" |
GString |
三个双引号:多行且支持插值 |
/Regex/ |
String |
斜杠:用于正则,避免转义地狱 |
groovy
def user = "Admin"
def greeting = "Welcome, ${user.toLowerCase()}!" // 支持方法调用
def regex = ~/\d{3}-\d{4}/ // 斜杠字符串,无需写成 "\\d{3}-\\d{4}"
1.4 Groovy Truth(更聪明的布尔判断)
在 Java 中,if 语句的条件必须是 boolean 类型。Groovy 扩展了真值判断规则(Groovy Truth),让判空和判空集合变得极其优雅:
groovy
// 以下情况在 Groovy 中全部评估为 FALSE:
if (!null) // null
if (!"") // 空字符串
if (!0) // 数字 0
if (![]) // 空集合
if (![:]) // 空 Map
if (!Matcher.find()) // 正则未匹配
// 实战对比:
// Java: if (list != null && !list.isEmpty()) { ... }
// Groovy:
if (list) { ... }
二、 面向对象特性的"现代化"改造
2.1 POJO 的简单化(属性与 Getter/Setter)
在 Groovy 中,字段(Field)即属性(Property) 。当你声明一个非私有字段时,Groovy 编译器会自动生成对应的 getter 和 setter,并且你可以像访问字段一样直接访问属性。
groovy
class User {
String name // 自动生成 getName() 和 setName()
int age
}
def user = new User()
user.name = "Alice" // 底层实际调用 user.setName("Alice")
println user.name // 底层实际调用 user.getName()
注意 :如果布尔类型字段名为
active,Groovy 生成的 getter 是isActive()而不是getActive(),这与 Java Bean 规范一致。
2.2 方法增强
1. 默认参数
告别 Java 中为了不同参数组合而写的"构造器/方法重载瀑布"。
groovy
def connect(String host, int port = 8080, String protocol = "http") {
println "Connecting to ${protocol}://${host}:${port}"
}
connect("localhost") // Connecting to http://localhost:8080
connect("localhost", 3306) // Connecting to http://localhost:3306
2. 命名参数(伪实现)
Groovy 通过将 Map 作为第一个参数,实现了类似 Python 的命名参数调用,极大提升了 API 的可读性。
groovy
def createUser(Map params) {
println "Name: ${params.name}, Role: ${params.role}"
}
// 调用时,Groovy 会自动将键值对组装成 Map
createUser(name: "Bob", role: "Admin")
2.3 AST 转换注解(内置 Lombok)
Java 开发者离不开 Lombok,而 Groovy 内置 了 AST(抽象语法树)转换注解,在编译期修改字节码。
groovy
import groovy.transform.*
@Canonical // 组合了 @ToString, @EqualsAndHashCode, @TupleConstructor
@Builder // 生成 Builder 模式
class Product {
String id
String name
BigDecimal price
}
// 使用 Builder
def p = Product.builder().id("1").name("MacBook").price(1999.0).build()
println p // 自动调用 toString()
三、 闭包(Closure):Groovy 的灵魂
3.1 闭包的基本语法
闭包使用 { [参数] -> [代码体] } 定义。如果只有一个参数,可以省略参数列表,使用隐式参数 it。
groovy
// 定义闭包
def greet = { String name -> println "Hello, ${name}" }
greet("Alice")
// 使用隐式参数 it
def square = { it * it }
println square(5) // 25
3.2 闭包作为方法参数(尾随闭包)
当方法的最后一个参数 是闭包时,可以将闭包提取到括号外面 ;如果方法只有 一个闭包参数,括号甚至可以省略。这是 Groovy DSL 看起来像配置文件的核心原因。
groovy
// 普通调用
[1, 2, 3].each({ println it })
// 尾随闭包
[1, 2, 3].each { println it }
// 嵌套闭包构建 DSL (类似 Gradle 语法)
dependencies {
implementation 'org.apache.groovy:groovy:4.0.0'
}
3.3 闭包的委托机制(Delegate)
这是 Java 开发者最难理解、也是 Groovy 最强大的特性。
闭包内有三个隐式变量:this(定义闭包的类)、owner(定义闭包的闭包/类)、delegate(委托对象)。
通过修改 delegate 和 resolveStrategy,闭包可以代理执行另一个对象的方法,仿佛那些方法就在当前作用域内。
groovy
class EmailBuilder {
String to
String subject
String body
void to(String to) { this.to = to }
void subject(String s) { this.subject = s }
void body(String b) { this.body = b }
}
def email(Closure config) {
def builder = new EmailBuilder()
config.delegate = builder
config.resolveStrategy = Closure.DELEGATE_FIRST // 优先从 delegate 找方法
config()
return builder
}
// 优雅的 DSL 调用
def myEmail = email {
to "admin@test.com"
subject "Alert"
body "Server is down!"
}
四、 集合与 GDK
Groovy 没有发明新的集合框架,而是通过 GDK (Groovy Development Kit) 对 Java 原生的 Collection、Map、String 等注入了大量扩展方法。
4.1 集合的极简声明
groovy
// List (默认 ArrayList)
def list = [1, 2, 3, 4]
// Map (默认 LinkedHashMap)
// 注意:Map 的 key 默认是 String,不需要加引号!
def map = [name: "Alice", age: 25, "complex-key": true]
// Range (范围)
def range = 1..10 // 包含 10
def range2 = 1..<10 // 不包含 10
4.2 高阶函数(告别 Stream API 的繁琐)
Java 8 的 Stream API 虽然强大,但需要 stream().map().filter().collect() 等冗长的链式调用。Groovy 直接在集合上提供了高阶方法:
| Groovy 方法 | 对应 Java Stream | 作用 |
|---|---|---|
each { } |
forEach() |
遍历 |
collect { } |
map() |
映射转换 |
findAll { } |
filter() |
过滤 |
find { } |
filter().findFirst() |
查找第一个匹配项 |
groupBy { } |
collectors.groupingBy() |
分组 |
inject(init) { } |
reduce() |
折叠/累积 |
any { } / every { } |
anyMatch() / allMatch() |
布尔断言 |
实战对比:
groovy
def users = [
[name: "Alice", age: 28, dept: "IT"],
[name: "Bob", age: 35, dept: "HR"],
[name: "Charlie", age: 22, dept: "IT"]
]
// 需求:找出 IT 部门的人,提取他们的名字,并用逗号拼接
// Java: users.stream().filter(u -> "IT".equals(u.get("dept"))).map(u -> u.get("name")).collect(Collectors.joining(","))
// Groovy:
def result = users
.findAll { it.dept == "IT" }
.collect { it.name }
.join(", ")
println result // Alice, Charlie
4.3 集合的"黑科技"操作
groovy
def list = [1, 2, 3]
// 1. 展开操作符 (Spread Operator)
def numbers = [1, 2, 3]
def moreNumbers = [*numbers, 4, 5] // [1, 2, 3, 4, 5]
// 2. 安全导航调用 (对集合中每个元素调用方法)
def names = ["alice", "bob", null]
def upperNames = names*.toUpperCase() // ["ALICE", "BOB", null] (自动跳过 null)
// 等同于 names.collect { it?.toUpperCase() }
// 3. Map 的解构
def config = [host: "localhost", port: 8080]
def (h, p) = [config.host, config.port]
五、 操作符重载与语法
5.1 常用操作符重载映射
| 操作符 | 对应方法 | 适用场景 |
|---|---|---|
a + b |
a.plus(b) |
字符串、集合、自定义类相加 |
a - b |
a.minus(b) |
集合移除元素 |
a * b |
a.multiply(b) |
字符串重复、数学运算 |
a << b |
a.leftShift(b) |
集合追加元素、IO 流写入 |
a <=> b |
a.compareTo(b) |
太空船操作符,比较大小 |
a in b |
b.contains(a) |
判断元素是否在集合/范围内 |
a =~ b |
Pattern.matcher |
正则匹配(查找) |
a ==~ b |
String.matches |
正则严格匹配 |
5.2 必备语法糖操作符
groovy
// 1. 安全导航操作符 (Safe Navigation) - 避免 NullPointerException
def user = getUser()
def city = user?.address?.city // 如果 user 或 address 为 null,直接返回 null,不抛异常
// 2. Elvis 操作符 (三元运算符的简化)
def name = user.name ?: "Anonymous" // 如果 user.name 为 null 或空(Groovy Truth),则赋值 "Anonymous"
// 3. Elvis 赋值操作符 (Groovy 3.0+)
user.name ?= "Default" // 等同于 user.name = user.name ?: "Default"
// 4. 强制类型转换 (as)
def list = [1, 2, 3] as Set
def str = "123" as Integer
六、 进阶:元编程与 DSL 构建
6.1 MOP (Meta-Object Protocol)
在 Groovy 中,所有的方法调用和属性访问都不再是直接的 JVM 字节码调用,而是经过 MetaClass 拦截和分发。
groovy
String.metaClass.shout = { -> delegate.toUpperCase() + "!!!" }
println "hello groovy".shout() // 输出: HELLO GROOVY!!!
// 你动态地为 Java 核心的 String 类增加了一个方法!
6.2 拦截未知调用:methodMissing
当调用一个不存在的方法时,Groovy 不会立即抛出 MissingMethodException,而是调用 methodMissing。这是构建动态 DSL 的核心。
groovy
class QueryBuilder {
def criteria = []
def methodMissing(String name, args) {
criteria << "${name} = ${args[0]}"
}
def build() { criteria.join(" AND ") }
}
def qb = new QueryBuilder()
qb.age(18)
qb.status("active")
println qb.build() // 输出: age = 18 AND status = active
思考 :Grails 框架的 ORM(GORM)中的
findByAgeAndStatus(18, "active")就是基于methodMissing实现的动态查询方法!
七、 Groovy 4.x 新特性速览
随着 Java 版本的迭代,Groovy 4.x 也引入了许多现代特性(注意:Maven 坐标已从 org.codehaus.groovy 迁移至 org.apache.groovy)。
-
Switch 表达式 (Switch Expressions)
告别break,支持返回值,支持正则、类型、闭包匹配。groovydef result = switch (input) { case Integer -> "It's a number" case ~/^\d+$/ -> "It's a numeric string" case { it.size() > 5 } -> "Long string" default -> "Unknown" } -
Record 类型支持
与 Java 14+ 的 Record 无缝兼容,并可用 Groovy 注解增强。 -
内置类型检查器 (Type Checking)
使用@TypeChecked或@CompileStatic注解,可以在编译期强制进行类似 Java 的严格类型检查,极大提升运行性能并提前暴露 Bug。
八、 实战生态
8.1 Groovy 在 Java 生态中的位置
| 场景 | 工具/框架 | 优势 |
|---|---|---|
| 构建工具 | Gradle | 利用闭包和 DSL 描述构建逻辑,比 Maven XML 清晰百倍 |
| 单元测试 | Spock Framework | BDD 风格,数据驱动测试(where 块),Mock 极其优雅 |
| CI/CD 脚本 | Jenkins Pipeline | Groovy 脚本定义流水线,支持共享库(Shared Library) |
| API Mock/契约 | WireMock / Spring Cloud Contract | 使用 Groovy DSL 定义 HTTP 契约 |
| 快速脚本/运维 | Groovy Shell / CliBuilder | 替代 Shell/Python,直接调用项目内的 Java 业务类 |
8.2 Spock 测试框架示例
groovy
import spock.lang.Specification
import spock.lang.Unroll
class MathSpec extends Specification {
@Unroll // 数据驱动测试展开
def "计算 #a 和 #b 的最大公约数应为 #expected"() {
expect: "执行 GCD 算法"
MathService.gcd(a, b) == expected
where: "测试数据矩阵"
a | b || expected
10 | 2 || 2
15 | 5 || 5
7 | 3 || 1
}
}
8.3 建议
- 核心业务逻辑用 Java,边缘/测试/脚本用 Groovy:Java 的强类型和重构支持在复杂业务中依然无可替代;Groovy 适合做"胶水"和测试。
- 谨慎使用动态元编程 :
MetaClass修改全局行为会导致难以追踪的 Bug,尽量使用 AST 转换(@CompileStatic)或 Category。 - 使用
@CompileStatic:在不需要动态特性的 Groovy 类上加上此注解,编译器会生成与 Java 性能一致的字节码,并开启 IDE 的严格类型提示。 - 注意
==的含义 :在 Groovy 中,==默认调用.equals()(对比 Java 的==是对比引用)。如果要对比引用地址,请使用===。