Gradle and Groovy
视频教程
文档
groovy和java语法对比
Groovy 是一门运行在 JVM 上、与 Java 兼容的动态脚本语言;与在编译期进行类型检查的 Kotlin 不同,Groovy 更依赖运行时检查,因此新项目通常推荐优先使用 Kotlin,但在编写脚本或 Gradle 构建时学习和使用 Groovy 仍然很有价值。
1. 基本语法差异
1.1 分号与括号
- Java: 必须以分号结尾,括号和花括号是必需的
- Groovy: 分号是可选的,许多括号可以省略
// Groovy
println "Hello"
if (true)
println "Test"// Java
System.out.println("Hello");
if (true) {
System.out.println("Test");
}1.2 字符串
Groovy支持多种字符串定义方式:
// 单引号字符串(同Java,不支持插值)
String a = 'Hello'
// 双引号字符串(支持GString插值)
String name = "World"
String b = "Hello ${name}" // Hello World
String c = "2 + 3 = ${2 + 3}" // 2 + 3 = 5
// 斜杠字符串(避免转义)
String regex = /c:\path\to\file/
// 三引号字符串(多行字符串)
String multiLine = """
This is a
multi-line string
"""2. 类型系统差异
2.1 动态类型 vs 静态类型
// Groovy - 类型可选(动态类型) def即可定义变量也可定义方法
def x = 10
def s = "test"
x = "现在是字符串" // 允许// Java - 需要声明类型
int x = 10;
String s = "test";2.2 类型推断
// Groovy
def x = 10 // 运行时确定类型// Java 10+
var x = 10; // 编译时确定类型3. 方法定义
3.1 返回值与参数
// Groovy - 返回类型可选,最后一个表达式自动返回
def greet(name) {
"Hello $name"
}
// Groovy - 参数类型也可省略
String greet2(String name) "Hello $name"// Java
public String greet(String name) {
return "Hello " + name;
}3.2 默认参数
// Groovy - 直接支持默认参数
def test(name = "default") {
println name
}// Java - 不支持,需要使用重载
public void test(String name) { }
public void test() { test("default"); }4. 集合与映射
4.1 List
// Groovy
def list = [1, 2, 3] // 更简洁
list << 4 // 使用<<操作符添加元素// Java
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);4.2 Map
// Groovy
def map = [a: 1, b: 2]
map.c = 3 // 可以直接设置属性
def value = map.a // 可以直接访问// Java
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);5. 闭包(Closure)
Groovy 的闭包是一个可传递、可存储、可执行的代码块,底层类型是 groovy.lang.Closure。闭包可以像普通对象一样赋值给变量、作为方法参数传递、作为返回值返回。
- Groovy 闭包可以直接定义为
{ 参数 -> 代码 } - 如果只有一个参数,参数名可以省略,默认使用
it - 如果某个方法的最后一个参数是闭包,闭包可以移到外面
method(arg1, arg2) { ... }效果等同于method(arg1, arg2, { ... });如果方法参数只有这一个闭包,可直接写成method { ... } - 闭包的最后一个表达式会作为返回值返回
- 可以通过
closure.call(args)或closure(args)调用
Java 8+ 引入 Lambda 表达式,语法更轻量,但本质上是函数式接口实例;Groovy 闭包则是更灵活的对象,可以访问外层作用域并支持更丰富的调用形式。
// Groovy 闭包:定义、调用、隐式参数
def closure = { x -> x * 2 }
println closure(5) // 10
println closure.call(6) // 12
// 单参数时可以省略参数名,使用 it
def square = { it * it }
println square(4) // 16
// 无参数闭包
def hello = { println 'Hello Groovy' }
hello()
// 闭包作为方法参数
def eachItem(list, action) {
list.each(action)
}
eachItem([1, 2, 3]) { item ->
println "item = $item"
}
// 闭包应用于集合操作
def list = [1, 2, 3]
def result = list.collect { it * 2 } // [2, 4, 6]
def filtered = list.findAll { it > 1 } // [2, 3]
// Closure 也可以使用显式类型
Closure<Integer> add = { a, b -> a + b }
println add(2, 3) // 5// Java Lambda
Function<Integer, Integer> lambda = x -> x * 2;
System.out.println(lambda.apply(5)); // 10
List<Integer> list = Arrays.asList(1, 2, 3);
list.forEach(item -> System.out.println(item));
List<Integer> result = list.stream()
.map(x -> x * 2)
.collect(Collectors.toList());5.1 Groovy 闭包更多特性
Groovy 闭包可以访问定义时外层作用域的变量,类似于 Java 中的闭包/匿名内部类:
int factor = 3
def multiply = { x -> x * factor }
println multiply(5) // 15Groovy 还支持闭包柯里化(currying):
def concat = { a, b, c -> "$a$b$c" }
def hello = concat.curry('Hello, ')
println hello('world', '!') // Hello, world!当闭包作为方法最后一个参数时,可使用“尾随闭包”语法:
[1, 2, 3].each {
println it
}对于更复杂的 DSL 和 Gradle 脚本,闭包的可委托机制(delegate / owner / resolveStrategy)也是 Groovy 的重要特性,但在普通对比中,重点是:
- Groovy 闭包是一个真正对象,可在运行时传递和组合
- Java Lambda 是函数式接口的实例,语法更有限但与 Java 类型系统更契合
- Groovy 在处理集合、DSL 和脚本时,闭包风格更自然
6. 取值与赋值
6.1 Getter/Setter
// Groovy - 自动生成getter/setter 默认public
class Person {
String name
}
// 使用时
def p = new Person()
p.name = "Alice" // 直接赋值,自动调用setter
println p.name // 直接取值,自动调用getter// Java
class Person {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}6.2 属性访问操作符
Groovy 支持安全导航操作符,java不支持
def person = null
println person?.name // 返回null,不抛出异常
println person?.name ?: "默认名字" //Elvis运算符(?:)是标准三目运算符的简写形式,用于快速处理 null 值。它的语法是 x ?: y,如果左侧表达式 x 不为 null(且布尔值为真),则返回 x;否则返回右侧的默认值 y// Java 需要显式null检查
if (person != null) {
System.out.println(person.getName());
}// Groovy - 支持更多类型,无需break
def result = switch (x) {
case 1 -> "one"
case 2 -> "two"
default -> "other"
}// Java
switch (x) {
case 1:
System.out.println("one");
break;
case 2:
System.out.println("two");
break;
}7.2 For循环
// Groovy
for (i in 0..9) {
println i
}
// 范围操作符
def range = 1..5 // [1,2,3,4,5]
def exclusiveRange = 1..<5 // [1,2,3,4]// Java
for (int i = 0; i < 10; i++) {
System.out.println(i);
}8. 异常处理
// Groovy - 异常捕获是可选的
try {
risky()
} catch (e) { // 可选类型
println e
}// Java
try {
risky();
} catch (IOException e) {
e.printStackTrace();
} finally {
cleanup();
}9. 类与对象
9.1 声明与初始化
// Groovy
class Person {
String name
int age
}
def p = new Person(name: "Alice", age: 30) // 命名参数
def p2 = new Person("Alice", 30) // 位置参数// Java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Person p = new Person("Alice", 30);9.2 GroovyBean
// Groovy 自动生成equals, hashCode, toString
class Person {
String name
int age
}
def p1 = new Person(name: "Alice", age: 30)
def p2 = new Person(name: "Alice", age: 30)
println p1 == p2 // 自动deep equals,输出true
println p1 // 自动toString10. 元编程特性
Groovy支持运行时元编程,这是Java所没有的:
// 动态添加方法
String.metaClass.shout = { -> delegate.toUpperCase() + "!!!" }
println "hello".shout() // HELLO!!!
// 动态添加属性
class Dynamic { }
def obj = new Dynamic()
obj.dynamicProp = "value"
println obj.dynamicProp
// 方法缺失处理
class MyClass {
def methodMissing(String name, args) {
"You called $name with args: $args"
}
}
def obj = new MyClass()
println obj.anyMethod(1, 2) // 动态处理任何方法调用11. 常用操作符对比
| 操作 | Java | Groovy |
|---|---|---|
| 字符串拼接 | "a" + "b" | "a" + "b" 或 "${a}${b}" |
| 范围 | 不支持 | 1..10, 1..<10 |
| Elvis操作符 | 三元运算符 | a ?: b |
| 安全导航 | obj != null ? obj.prop : null | obj?.prop |
| 传播操作符 | stream+lambda | list*.property |
| 正则匹配 | Pattern/Matcher | "test" =~ /pattern/ |
| 模式匹配 | switch(Java 17+) | switch/case |
groovy spock测试框架
Spock 是基于 Groovy 的测试框架,常用于单元测试、集成测试和行为驱动测试 (BDD)。它与 JUnit 兼容,但语法更简洁、可读性更高,并内置 Mock/Stub 支持。相当于Junit+Mockito
1. Gradle 依赖配置
dependencies {
testImplementation 'org.spockframework:spock-core:2.4-groovy-4'
testImplementation 'org.codehaus.groovy:groovy' // 如果项目未使用 Groovy 插件,需要显式引入
}
test {
useJUnitPlatform()
}如果是 Spring Boot 项目,可额外引入:
testImplementation 'org.spockframework:spock-spring:2.4-groovy-4'2. 基本写法
Spock 测试类继承 Specification,每个测试方法直接写成 def "描述"(),并使用 given: / when: / then: / expect: 等块结构。
import spock.lang.Specification
class CalculatorSpec extends Specification {
def "两个数相加"() {
given:
def calculator = new Calculator()
when:
def result = calculator.add(2, 3)
then:
result == 5
}
}3. 常见块说明
given::测试前置条件或初始化数据when::触发测试行为then::断言结果expect::适用于直接断言,不需要明确when:的场景where::用于数据驱动测试setup:/cleanup::每个测试方法执行前后处理setupSpec:/cleanupSpec::整个规格类执行前后处理
class ExampleSpec extends Specification {
def setup() {
// 每个测试方法前执行
}
def cleanup() {
// 每个测试方法后执行
}
def setupSpec() {
// 仅执行一次,类级别初始化
}
def cleanupSpec() {
// 仅执行一次,类级别清理
}
}4. 断言与异常验证
Spock 使用 Groovy Truth,then: 里的表达式会自动作为断言。
then:
result == 5
list.size() == 3
name ==~ /hello.*/验证异常:
when:
calculator.divide(1, 0)
then:
thrown(ArithmeticException)如果需要获取异常对象:
when:
calculator.divide(1, 0)
then:
def ex = thrown(ArithmeticException)
ex.message == "/ by zero"5. Mock / Stub / Spy
Spock 内置了方便的模拟机制。
class OrderServiceSpec extends Specification {
def paymentGateway = Mock(PaymentGateway)
def service = new OrderService(paymentGateway)
def "创建订单时调用支付网关"() {
given:
def order = new Order(amount: 100)
when:
service.placeOrder(order)
then:
1 * paymentGateway.pay(100) //这个1表示预期调用一次
}
}常用类型:
Mock():可以验证交互Stub():只返回预设值,不关注交互Spy():部分真实对象,部分模拟行为
def userRepository = Stub(UserRepository) {
findById(1) >> new User(id: 1, name: 'Alice')
}6. 数据驱动测试
Spock 的数据驱动测试非常强大,使用 where: 块可以写成清晰的表格形式。
class MathSpec extends Specification {
def "最大值计算"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 2 || 2
5 | 3 || 5
0 | 0 || 0
}
}也可以使用列表形式:
where:
[a, b, c] << [[1, 2, 2], [5, 3, 5], [0, 0, 0]]7. @Unroll 与可读性输出
当使用数据驱动测试时,@Unroll 可以让每一组数据成为单独的执行条目,并在报告中显示更直观的描述。
import spock.lang.Unroll
class MathSpec extends Specification {
@Unroll
def "max(#a, #b) == #c"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 2 || 2
5 | 3 || 5
0 | 0 || 0
}
}8. Spring 集成
如果是 Spring Boot 项目,使用 @SpringBootTest 与 spock-spring 结合:
import org.springframework.boot.test.context.SpringBootTest
import spock.lang.Specification
@SpringBootTest
class UserServiceSpec extends Specification {
def userService
def "用户可以被成功保存"() {
when:
def user = userService.save(new User(name: 'Alice'))
then:
user.id != null
}
}9. 运行测试
在 Gradle 项目中执行:
./gradlew test如果只运行 Spock 规范:
./gradlew test --tests *Spec