刷题几乎不会用到 Groovy,本文目的仅在于快速熟悉 Groovy 语言,并了解其与 Java 语言的区别。

Groovy 简介

Groovy 是一门基于 JVM 的脚本语言。它在兼容 Java 语法的同时,借鉴了 Ruby、Python 等语言的特性,有自己一套简洁而灵活的语法。同时,运行在 JVM 上也意味着它也可以使用 Java 语言编写的库。这两点结合,让 Groovy 极其适合编写 Java 代码的测试脚本。

Groovy 兼容 Java 的语法。有关 Java 语法详见:站内文章【语言热身】Java 基础(热身索引)

Groovy 提供了大量的语法糖来方便我们编写脚本。

在 IDEA 中,可以通过 Tools->Groovy Console 随时随地进行简单 Groovy 的编写与调试。也可以另外创建 Groovy 项目。

缺点:

  • 效率问题。Groovy 作为运行在 JVM 上的动态语言,运行效率是低于 Java 的。虽然可以用 @CompileStatic 注解来静态编译一些类以提高效率,但这样又会失去 Groovy 的一些动态语言的特性。
  • 语法过于灵活,运用不当会降低可读性与可维护性。Groovy 支持元编程特性,可以在运行时动态添加方法。这一点自然可以简化代码,但也有很大的可能会降低可维护性。函数式编程与大量的语法糖会让不熟悉 Groovy 的人读起来一头雾水,反而降低了可读性。

main 方法

🍬 优化了 Java 的 main 方法。类似 Python。

main 方法的写法可以和 Java 相同。

1
2
3
4
5
6
// Java 写法
public class HelloWorld {
public static void main(String []args) {
System.out.println("Hello, World!");
}
}

Groovy 中 main 方法不一定要写在类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 执行 main 方法可以选其一执行
class Student {
int StudentID;
String StudentName;

static void main(String[] args) {
println "inside"
}
}

static void main(String[] args) {
println "outside"
}

甚至,main 方法根本就不需要:

1
2
3
// 分号 ; 是可选的
def x = 5; // 可选类型。def 可以省略
println "The value of x is " + x ; // 方法调用可以用空格代替括号

变量的定义和交换

🍬 简化 Java 多变量定义即交换方法。类似 Python。

1
2
3
4
5
def (var1, var2) = [1, 2]
(var1, var2) = [1, 2] // def 是可以省略的

int a = 1, b = 2
(a, b) = [b, a]

数据类型

字符串

🍬 弥补了 Java 字符串的重复操作。类似 Python。

字符串操作:

  • +:字符串链接
  • *:字符串重复
  • str.length():字符串长度

可选类型

🍬 优化了 Java 的类型创建。类似 Python。

Groovy 是一种 " 可选 " 类型的语言,在理解该语言的基础知识时,这种区别很重要。 与 Java 相比,Java 是一种 " 强 " 类型语言,编译器知道每个变量的所有类型,并且可以在编译时理解和遵守合同。 这意味着可以在编译时确定方法调用。

在 Groovy 中编写代码时,开发人员可以灵活地提供或不提供类型。 这可以在实现中提供一些简单性,并且如果使用得当,可以以健壮和动态的方式为您的应用程序提供服务。

范围 Range

范围是指定值序列的简写。 Range 由序列中的第一个值和最后一个值表示,Range 可以是包含的,也可以是排除的。 包含范围包括从第一个到最后一个的所有值,而排他范围包括除最后一个之外的所有值。

1
2
3
4
5
1..10 // 包含范围的示例
1..<10 // 独占范围的示例,定义半开区间
'a'..'x' // 范围也可以由字符组成
10..1 // 范围也可以按降序排列
'x'..'a' // 范围也可以由字符组成并按降序排列。
1
def range = 0..5

常用方法:

  • range.contains(e) 检查范围是否包含特定值 e
  • range.get(i) 返回此 Range 中指定位置 i 的元素。
  • range.getFrom() 获取此 Range 的下限值。
  • range.getTo() 获取此 Range 的上限值。
  • range.isReverse() 是否为反转的 Range
  • range.size() 返回此 Range 中的元素数。
  • range.subList(fromIndex,toIndex):返回此 Range 在指定 fromIndex(包括)和 toIndex(不包括)之间部分的视图

列表 List

🍬 优化了 Java 中的 List 的创建。类似 Python。

Groovy 可以很轻松的定义 Java 中的 List

在 Groovy 中,List 列表包含一系列对象引用。 List 列表中的对象引用在序列中占据一个位置,并通过整数索引进行区分。 List 列表字面量表示为一系列用逗号分隔并用方括号括起来的对象。

1
2
3
4
5
[11, 12, 13, 14] // 整数值列表
[‘Angular’, ‘Groovy’, ‘Java’] // 字符串列表
[1, 2, [3, 4], 5] // 嵌套列表
[‘Groovy’, 21, 2.11] // 对象引用的异构列表
[ ] // 一个空列表
方法 描述 Java 中类似方法
add(e) 将新值附加到此列表的末尾。 Collection 的「增」
remove(i) 删除此列表中指定位置的元素。 ArrayList 的「删」
get(i) 返回此列表中指定位置的元素。 Collection 的「查」
contains(e)
如果此 List 包含指定的值,则返回 true。 Collection 的的判断
isEmpty() 如果此 List 不包含任何元素,则返回 true
minus() 创建一个由原始元素组成的新列表,而不是集合中指定的元素。
plus() 创建一个由原始元素和集合中指定的元素组成的新列表。
pop()
reverse() 创建一个新的列表,它与原始列表的元素相反
size() 获取此 List 中的元素个数。 Collection 获取长度
sort() 返回原始列表的排序副本。

Java 集合相关内容详见:站内文章【语言热身】Java 集合的使用

通过范围可以快速定义 List

1
2
3
4
5
6
7
def arrayList = 'A'..'D' as ArrayList
def linkedList = 1..<5 as LinkedList
println arrayList
println linkedList
// assert可以检查表达式是否为true。当表达式为false时,assert会打印出表达式里对象或方法的值
assert arrayList instanceof ArrayList
assert linkedList instanceof LinkedList

List 的运算:

1
2
3
4
5
6
7
8
def list1 = []
def list2 = list1 + 'Hello' // 加号返回新的list
println "$list1 $list2" // 输出:[] [Hello]
def list3 = list2 << 'World' // list2 会修改原有list
println "$list2 $list3" // 输出:[Hello, World] [Hello, World]
list2 << 'Yes!' // 注意,list3 持有 list2 的引用
println "$list2 $list3" // 输出:[Hello, World, Yes!] [Hello, World, Yes!]
println([1,2]+[3,4]) // 输出:[1, 2, 3, 4]

使用 Range 访问 List

1
2
3
list = [1, 2, 3, 4, 5]
assert list[0..2] == [1, 2, 3]
assert list[0..-2] == [1, 2, 3, 4]

Map

🍬 优化了 Java 中的 Map 的创建。

Groovy 可以很轻松的定义 Java 中的 Map

Map(也称为关联数组、字典、表和散列)是对象引用的无序集合。 Map 集合中的元素通过键值访问。 Map 中使用的键可以是任何类。 当我们插入 Map 集合时,需要键和值。

1
2
3
['TopicName' : 'Lists', 'Author' : 'Raghav'] // 以 TopicName 为键及其各自值的键值对集合。

[ : ] // 空 Map 集合。
方法 描述 Java Map 存在类似方法
containsKey(key)
get(key) 如果此 Map 中没有该键的条目,则返回 null
keySet()
put(key,newValue)
size()
values() 返回此 Map 中包含的值的集合视图。

Java Map 集合相关内容详见:站内文章【语言热身】Java 集合的使用

正则表达式

🍬 优化了 Java 中正则包 java.util.regex 的使用。

1
2
3
4
5
def regex = ~'Groovy'
def pattern1 = ~/match/

def word = "text"
def pattern2 = ~"$word"

条件判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def text = "some text to match"
def pattern = /some/
if (text =~ /some/){
println "Matched"
}
def matcher = (text =~ pattern)
if (matcher){
println "Matched"
}

// 以下式子在条件判断中,可以得到布尔值
'Groovy' =~ 'Groovy'
'Groovy' =~ 'oo'
'Groovy' ==~ 'Groovy'
'Groovy' ==~ 'oo'
'Groovy' =~ '∧G'
'Groovy' =~ 'G$'
'Groovy' =~ 'Gro*vy'
'Groovy' =~ 'Gro{2}vy'

捕获组的获得:

1
2
3
4
def pattern = ~/A is (\d+), B is (\d+)/
def matcher = ("A is 10, B is 15. A is 20, B is 25" =~ pattern)
assert matcher[0][1] == "10" && matcher[0][2] == "15"
assert matcher[1][1] == "20" && matcher[1][2] == "25"

流程控制语句

Groovy 的 switch case 语法和 Java 类似,但是支持 ListRange、整数、数字、正则、闭包的判断。

可以使用 for in 语法:

1
2
3
4
for (i in 1..10)
{
println "$i"
}

方法

🍬 优化了 Java 中的方法声明,类似 Python。

方法可以接收任意数量的参数。 在定义参数时不需要显式定义类型。 可以添加诸如 publicprivateprotected 等修饰符。 默认情况下,如果没有提供可见性修饰符,则该方法是公共的。

1
2
3
4
5
6
7
8
9
10
11
12
def methodName() { 
// Method code
}

def methodName(parameter1, parameter2, parameter3) {
// Method code goes here
}

// 带默认参数
def someMethod(parameter1, parameter2 = 0, parameter3 = 0) {
// Method code goes here
}

Groovy 的方法定义中可以省略 return 关键字。

特殊方法

🍬 对常用方法的使用进行简化。

与 Ruby 类似,groovy 提供了 times 方法,让我们能写出更简洁的循环。

1
2
3
4
3.times { i -> print "$i " }
def list = [1,2,3]
list.size().times { i -> print "$i "}
// 输出:0 1 2 0 1 2

在使用 Java 的时候,常常会遇到需要在同一个对象上多次调用方法。Groovy 也提供了相应的语法糖,这就是 with 方法。通过 with 方法,我们在调用对象方法时可以省略对象名。

1
2
3
4
5
6
7
StringBuilder stringBuilder = new StringBuilder()
stringBuilder.with {
append "W"
append "o"
append "w"
}
println stringBuilder.toString()

调试(标准输出与字符串格式化)

🍬 优化了 Java 中的字符串格式化

字符串可以使用模板引擎。

各种类型直接 println 也能很好的展现出信息来。

1
2
3
4
5
6
7
8
9
def num = 1
def str = 'Hello'
def list = [1,2,3,4,5]
def map = [ one : 1, two : 2, three : 3]
// 花括号可以省略
println "number is ${num}, string is ${str}, list is ${list}, map is ${map}"

// 输出:
// number is 1, string is Hello, list is [1, 2, 3, 4, 5], map is [one:1, two:2, three:3]

字符串的两端用单引号的话则不会进行格式化。

闭包与函数式编程

🍬 优化了 Java 中的 Stream 编程。

闭包是一个简短的匿名代码块。 它通常只跨越几行代码。 方法甚至可以将代码块作为参数。 它们本质上是匿名的。

与方法不同,闭包在被调用时总是返回一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 形式
{ [closureParameters -> ] statements }
// closureParameters 是一个逗号分隔的可选参数列表
// statements 语句是0条或更多Groovy语句
// 当指定一个参数列表时,`->`字符是必需的,用于将实参从闭包体中分离出来。语句部分由0、1或许多Groovy语句组成。

//一个引用名为item的变量的闭包
{ item++ }

//可以通过添加箭头 (->) 显式地将闭包参数从代码中分离出来。
{ -> item++ }

//使用隐式参数(it)的闭包
{ println it }

//它是一个显式参数的替代版本
{ it -> println it }

//接受两个类型化参数的闭包
{ String x, int y ->
println "hey ${x} the value is ${y}"
}

闭包的使用

闭包作为匿名代码块,可以像调用任何其他方法一样调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义一个不带参数的闭包
def code = { 'uuanqin.top' }
println code() // 隐式调用
println code.call() // 显式调用
//输出: uuanqin.top

// 创建一个闭包对象,并添加到isOdd引用。该闭包对象接受int的入参。
def isOdd = { int i -> i%2 != 0 }
println isOdd(3) // 输出: true
println isOdd.call(2) //输出 :false

//创建一个闭包对象,采用隐式声明,
def isEven = { it%2 == 0 }
println isEven(3) // 输出:false
println isEven.call(2) // 输出:true

在 Groovy 中,闭包可以作为对象、参数和返回值,所以 Groovy 对高阶函数也有良好的支持。

1
2
3
4
5
6
7
8
9
10
11
def addX = { int x ->
return { int y ->
println "x is $x, y is $y"
return x + y
}
}
def add1 = addX(1)
println add1(2)
// 输出:
// x is 1, y is 2
// 3

闭包的参数

闭包的参数:

1
2
3
4
5
6
def closureWithOneArg = { str -> str.toUpperCase() }
def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
def closureWithTwoArgs = { a,b -> a+b }
def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }

当闭包没有显式定义形参列表 (使用 ->) 时,闭包总是定义一个隐式形参,命名为 it

1
2
def greeting = { it -> "Hello, $it!" }
def magicNumber = { -> 1024 } //创建一个闭包对象,不允许传参

闭包可以像其他方法一样声明变量参数。如果最后一个形参是可变长度的 (或数组),就可以接受可变数量的实参:

1
2
3
4
5
6
7
def concat1 = { String... args -> args.join('') }
def concat2 = { String[] args -> args.join('') }
println concat1('ab','cd','ef') // 输出:abcdef
println concat2('a','b','cd','ef') // 输出:abcdef

def multiConcat = { int n, String... args -> args.join('')*n }
println multiConcat(3,'a','c','c')

集合的筛选与遍历

一些集合提供了以闭包作为参数的方法,用于遍历集合。

1
2
list.find({ it > 2 }) // 标准写法
list.find { it > 2 } // 省略括号。当方法只有闭包一个参数时

一些方法:

  • each(closure):遍历集合,集合本身会改变,返回集合的引用。
  • find(closure):在集合中找到第一个符合某个条件的值。该闭包必须是一些布尔表达式。
  • findAll(closure):在接收对象中查找所有符合闭包条件的值。该闭包必须是一些布尔表达式。相当于对集合进行筛选。
  • any(closure)every(closure)any 方法遍历集合的每个元素,检查布尔谓词是否对至少一个元素有效。返回一个布尔值。
  • collect(closure):遍历一个集合,使用闭包作为转换器将每个元素转换为新值。
1
2
3
4
5
6
// 示例
numbers = 1..10
sum = numbers.findAll { num -> num % 2 == 0}
.each { num -> print "${num} " }
.sum()
println "sum is ${sum}"

其他

在闭包中递归调用自身:

1
2
3
4
def fibonacci = { int n ->
n == 1 ? 1 : n + call(n - 1)
}
println fibonacci(10)

面向对象

🍬 优化了 Java 中类的操作。

Groovy 会自动为类中的变量加上 getter 与 setter 。(没懂)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
private String name
int age
def greet(String otherPerson) {
"Hello ${otherPerson}, my name is ${name}"
}
}

static main(args) {
Person alice = new Person(age: 20) // 调用构造函数时可以用形如(name: value)的方式给特定参数传参
alice.name = 'Alice' // 实际上调用了set方法
println alice.greet('Bob')
println alice.age
}

Groovy 可以动态地给一个类或对象添加方法。

1
2
3
4
5
6
class Cat {
def name
}
Cat kitty = new Cat(name: 'Kitty')
kitty.metaClass.meow = { println "Meow" }
kitty.meow()

Json 的处理

  • JsonSlurper :将 JSON 文本或阅读器内容解析为 Groovy 数据的类结构,例如映射、列表和原始类型,例如 IntegerDoubleBooleanString
  • JsonOutput:该方法负责将 Groovy 对象序列化为 JSON 字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import groovy.json.JsonOutput

class Student {
String name
int ID;
}

def output = JsonOutput.toJson([
new Student(name: 'John',ID:1),
new Student(name: 'Mark',ID:2)
])
println(output);


/**** 输出 *****
[{"name":"John","ID":1},{"name":"Mark","ID":2}]
****************/

多线程

🍬 简化 Java 线程的创建

1
2
3
4
Thread thr = Thread.start {
println "Hello, world!"
}
thr.join()

文件读写

1
2
3
def file = new File('test.txt')
file.newOutputStream() << "Hello, world!"
assert file.text == "Hello, world!"

本文参考