Groovy

1. Groovy概述

Groovy概述

Apache Groovy 编程语言 (groovy-lang.org)

image-20220704100412771

​ Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。

​ Groovy是从Java衍生出来的,并且运行在Java虚拟机上的语言.其目标是不管作为脚本语言,还是编程语言,都可以简单、直接使用。Groovy 也并不会替代 Java,而是相辅相成、互补的关系,具体使用哪门语言这取决于要解决的问题和使用的场景。

为什么要学习Groovy

  • Groovy基于JVM,这使我能够调用产品的Java代码,也能够调用Java标准库里的代码。除些之外,还可以通过Maven或Gradle使用大量的第三方Java库。
  • Groovy是动态语言,扩展了Java的容器类,提供了完善的函数式编程和元编程支持。这让我们可以写出简洁而富有表现力的代码。
  • Groovy提供了大量的语法糖。与Java自身繁冗的代码相比,这些语法糖大大节约了我们编写脚本的时间,减少了我的脚本的代码量。

2. Groovy环境

官网下载地址:https://groovy.apache.org/download.html

下载

image-20220704102135636

解压

image-20220704102521552

配置环境变量

  • Path 环境变量中添加 Groovy 的bin 目录路径

image-20220704102946430

image-20220712165812449

验证

启动命令窗口,输入groovy -version指令,打印如下图,即为安装成功。

image-20220704103212343

集成IDEA

创建项目

新建Project,选择Groovy,注意要选择Project SDK和Groovy library,默认是没有Groovy library的,选择右边的 create 按钮 ,之后选择自己安装groovy的目录即可,然后点击next

image-20220704103908208

新建测试:
1
2
3
4
5
6
public class Groovy_H {

public static void main(String[] args) {
System.out.println("hello groovy");
}
}

3. Groovy语法

3.1 Hello World

image-20220704105528154

1
2
3
4
5
6
7
8
class Groovy_T {

// Groovy注释标记和Java一样,支持 //或者/**/
static void main(String[] args) {
// 使用 println 就可打印输出,并且类和方法默认就是public,可以不用写 (分号也可省略)
println("hello groovy")

}
  • 使用 println 就可打印输出,并且类和方法默认就是public,可以不用写
  • Groovy注释标记和Java一样,支持 //或者/**/
  • Groovy语句可以不用分号结尾

3.2 数据类型

Groovy 的内置数据类型和 Java 一样有8种。byte、short、int、long、float、double、char、boolean并且都有其对应的封装类

此外,以下类可用于支持高精度计算 -

名称 描述 例如
java.math.BigInteger 不可变的任意精度的有符号整数数字 30克
java.math.BigDecimal 不可变的任意精度的有符号十进制数 3.5克

3.3 Groovy 变量

Groovy中的变量可以通过两种方式定义 - 使用数据类型的本地语法,或者使用def关键字。对于变量定义,必须明确提供类型名称或在替换中使用”def”。这是Groovy解析器需要的。

Groovy 提供了def关键字供使用,它可以省略变量类型的定义,根据变量的值进行类型推导。

变量声明

变量声明告诉编译器为变量创建存储的位置和大小。

下面是一个变量声明的例子 -

1
2
3
4
5
6
7
8
9
10
11
class Example { 
static void main(String[] args) {
// x is defined as a variable
String x = "Hello";
def var = "测试变量"

// The value of the variable is printed to the console
println(x);
println(var)
}
}

3.4 Groovy 字符串

Groovy提供了多种表示String字面量的方法。 Groovy中的字符串可以用单引号('),双引号(“)或三引号(”“”)括起来。

  • 单引号''–>所见即所得,’不支持’变量内插,有’特殊字符’同样的通过’反斜杠转义’
  • 双引号""–>可以通过${var}进行”变量内插(GString)” –>’常用
  • 三引号"""–>可以改变输出格式

GStrings是groovy.lang.GString的实例,并且允许文本中包含占位符,GStrings并不是String的子类,因为String类是最终类(final class)不能被继承。然而,GString与一般的字符串一样,因为Groovy能将GStrings转型为Java strings。

​ GString 适用于编写模板代码,因为必须动态构建字符串,上面的字符串拼接可以优化为:

1
2
3
4
5
6
@Test
void test99(){
String price = '999'
String abc2 = "价格是: ${price}";
println(abc2)
}

3.4 Groovy 运算符

运算符是一个符号,通知编译器执行特定的数学或逻辑操作。

大部分运算符和 Java 一样,如:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符

与 Java 不同点在于,新增了 范围运算符

Groovy支持范围的概念,并在..符号的帮助下提供范围运算符的符号。下面给出了范围运算符的一个简单示例。

1
def range = 0..5 

这只是定义了一个简单的整数范围,存储到一个局部变量称为范围内的下限为0和上限为5。

以下代码段显示了如何使用各种运算符。

1
2
3
4
5
6
7
class Example { 
static void main(String[] args) {
def range = 5..10;
println(range);
println(range.get(2));
}
}

当我们运行上面的程序,我们会得到以下结果 -

从println语句中,可以看到显示在range语句中定义的整个数字范围。

get语句用于从定义的范围中获取一个对象,它将索引值作为参数。

1
2
[5, 6, 7, 8, 9, 10] 
7

3.5 Groovy 循环

while循环

while语句首先通过计算条件表达式(布尔值)来执行,如果结果为真,则执行while循环中的语句。

1
2
3
4
5
6
7
8
9
10
class Example {
static void main(String[] args) {
int count = 0;

while(count<5) {
println(count);
count++;
}
}
}

通过首先计算条件表达式(布尔值)来执行 while 语句,如果结果为true,则执行while循环中的语句。从while语句中的条件的评估开始重复该过程 此循环继续,直到条件计算为false。当条件变为假时,循环终止。 然后程序逻辑继续紧跟在while语句之后的语句

for循环

for语句用于遍历一组值。

1
2
3
4
5
6
7
8
class Example { 
static void main(String[] args) {

for(int i = 0;i<5;i++) {
println(i);
}
}
}
for-in循环
1
2
3
4
5
6
7
8
9
class Example { 
static void main(String[] args) {
int[] array = [0,1,2,3];

for(int i in array) {
println(i);
}
}
}

在上面的例子中,我们首先初始化一个具有0,1,2和3的4个值的整数数组。然后我们使用for循环语句首先定义一个变量i,然后遍历数组中的所有整数 并相应地打印值。

for-in 语句也可用于循环范围。以下示例说明如何完成此操作:

1
2
3
4
5
6
7
8
9
class Example {
static void main(String[] args) {

for(int i in 1..5) {
println(i);
}

}
}

for-in 语句也可用于循环访问Map

1
2
3
4
5
6
7
8
9
class Example {
static void main(String[] args) {
def employee = ["Ken" : 21, "John" : 25, "Sally" : 22];

for(emp in employee) {
println(emp);
}
}
}

3.6 Groovy 方法

Groovy 中的方法是使用返回类型或使用 def 关键字定义的。方法可以接收任意数量的参数。定义参数时,不必显式定义类型。可以添加修饰符,如 public,private 和 protected。默认情况下,如果未提供可见性修饰符,则该方法为 public。

最简单的方法是没有参数的方法,如下所示:

1
2
3
def methodName() { 
//Method code
}

下面是一个简单方法的例子

1
2
3
4
5
6
7
8
9
10
class Example {
static def DisplayName() {
println("This is how methods work in groovy");
println("This is an example of a simple method");
}

static void main(String[] args) {
DisplayName();
}
}

在上面的例子中,DisplayName 是一个简单的方法,它由两个 println 语句组成,用于向控制台输出一些文本。在我们的静态 main 方法中,我们只是调用 DisplayName 方法。上述方法的输出将是 -

1
2
This is how methods work in groovy 
This is an example of a simple method

方法参数

如果一个方法的行为由一个或多个参数的值确定,则它通常是有用的。我们可以使用方法参数将值传递给被调用的方法。请注意,参数名称必须彼此不同。

使用参数的最简单的方法类型,如下所示 −

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

以下是使用参数的简单方法的示例

1
2
3
4
5
6
7
8
9
10
class Example {
static void sum(int a,int b) {
int c = a+b;
println(c);
}

static void main(String[] args) {
sum(10,5);
}
}

在这个例子中,我们创建一个带有 2 个参数 a 和 b 的 sum 方法。两个参数都是 int 类型。然后我们从我们的 main 方法中调用 sum 方法,并将值传递给变量 a 和 b。

然后我们从我们的 main 方法中调用 sum 方法,并将值传递给变量 a 和 b。

上述方法的输出将是值 15。

默认参数

Groovy 中还有一个规定来指定方法中的参数的默认值。 如果没有值传递给参数的方法,则使用缺省值。 如果使用非默认和默认参数,则必须注意,默认参数应在参数列表的末尾定义。

以下是使用参数的简单方法的示例 -

1
2
3
def someMethod(parameter1, parameter2 = 0, parameter3 = 0) { 
// Method code goes here
}

让我们看看我们之前看到的添加两个数字的相同示例,并创建一个具有一个默认和另一个非默认参数的方法 -

1
2
3
4
5
6
7
8
9
10
class Example { 
static void sum(int a,int b = 5) {
int c = a+b;
println(c);
}

static void main(String[] args) {
sum(6);
}
}

在这个例子中,我们创建一个具有两个参数 a 和 b 的 sum 方法。两个参数都是 int 类型。此示例和上一个示例的区别在于,在这种情况下,我们将 b 的默认值指定为5。 因此,当我们从 main 方法中调用 sum 方法时,我们可以选择只传递一个值为6的值,并将其分配给 sum 方法中的参数 a。

上述方法的输出将为值 11。

1
2
3
4
5
6
7
8
9
10
class Example {
static void sum(int a,int b = 5) {
int c = a+b;
println(c);
}

static void main(String[] args) {
sum(6,6);
}
}

我们也可以通过传递 2 个值来调用 sum 方法,在上面的例子中,我们传递 2 个值 6 第二个值 6 实际上将替换分配给参数 b 的默认值。

上述方法的输出将是值 12。

方法返回值

方法也可以将值返回到调用程序。 这在现在编程语言中是必需的,其中方法执行某种计算,然后将所需值返回到调用方法。

下面是一个带有返回值的简单方法的例子。

1
2
3
4
5
6
7
8
9
10
class Example {
static int sum(int a,int b = 5) {
int c = a+b;
return c;
}

static void main(String[] args) {
println(sum(6));
}
}

在我们上面的例子中,注意这次我们为我们的方法 sum 指定一个类型为 int 的返回类型。 在方法中,我们使用 return 语句将 sum 值发送到调用主程序。 由于方法的值现在可用于 main 方法,因此我们使用 println 函数在控制台中显示该值。

在前面的例子中,我们将我们的方法定义为静态方法,这意味着我们可以直接从类中访问这些方法。方法的下一个示例是实例方法,其中通过创建类的对象来访问方法。我们将在后面的章节中看到类,现在我们将演示如何使用方法。

上述方法的输出将为值 11。

实例方法

方法通常在 Groovy 中的类中实现,就像 Java 语言一样。类只是一个蓝图或模板,用于创建定义其属性和行为的不同对象。类对象显示由其类定义的属性和行为。因此,通过在类中创建方法来定义行为。

以下是如何实现方法的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Example { 
int x;

public int getX() {
return x;
}

public void setX(int pX) {
x = pX;
}

static void main(String[] args) {
Example ex = new Example();
ex.setX(100);
println(ex.getX());
}
}

在我们上面的例子中,这次我们没有为类方法指定静态属性。在我们的 main 函数中,我们实际上创建了一个 Example 类的实例,然后调用 ‘ex’ 对象的方法。

上述方法的输出将是值 100。

本地和外部参数名称

Groovy 提供的设施就像java一样具有本地和全局参数。在下面的示例中,lx 是一个局部参数,它只具有 getX() 函数内的作用域,x 是一个全局属性,可以在整个 Example 类中访问。如果我们尝试访问 getX() 函数之外的变量 lx,我们将得到一个错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Example { 
static int x = 100;

public static int getX() {
int lx = 200;
println(lx);
return x;
}

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

当我们运行上面的程序,我们会得到以下结果。

1
2
200 
100

3.7 Groovy 列表

集合定义

定义一个列表集合的方式有点像 Java 中的定义数组一样,默认的类型就是 ArrayList

1
2
3
4
5
6
//1. 在 Groovy 中定义的集合默认就是对应于 Java 中 ArrayList 集合
def list = [1,2,3,4,5]
println list
//2. 在集合中可以介绍任意类型的数据,例如当前传入的是数字,字符串,boolean值
list = ['张三',true,5]
println list

获取指定角标下的元素

可以通过角标访问集合指定位置的元素,正数角标是从0位置左往右算起,负数角标是从0位置往反方向算。

​ 下面的代码片段中出现的负数角标,就有别于 JAVA ,因为在 JAVA 中出现负数角标,基本就会报异常了,0 就是第一个位置的元素,-1就是最后一个位置的元素,一次类推即可

1
2
3
4
5
def list = [1,2,3,4,5]
//从头开始获取到第二个元素
println list[1]
//从末尾开始获取倒数第一个元素
println list[-1]

获取指定范围的元素

list[index1..index2]取出指定范围的元素

1
2
3
def list = [1,2,3,4,5]
//打印第二个元素到第四个元素
println list[2..4]

添加元素

在列表集合中添加元素的方式有以下三种

1
2
3
4
5
6
7
8
9
def list = [1, 2, 3, 4, 5]
//add 添加元素
list.add 6
//leftShift 添加元素
list.leftShift 7
//<< 添加元素
list << 8

print list

移除元素

1
2
3
4
5
6
7
8
def list = [1, 2, 3, 4, 5]
//通过下标移除元素
list.remove 2
//移除元素为2的对象
list.remove((Object)2)
//删除最后一个元素
list.removeLast()
println list

元素遍历

Groovy 中使用 each 来遍历集合,在遍历时,可以选择是否带有角标来选择不同的遍历方法

1
2
3
4
5
6
7
def list = [1, 2, 3, 4, 5]
//不带有角标的遍历,类似于 java 中的 foreach
list.each { println "元素的结果是${it}" }
//带有角标的遍历,类似于普通的for循环
list.eachWithIndex { int value, int index ->
println "value is ${value} and index is ${index}"
}

3.8 Groovy 映射

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

Map定义

Map 集合的定义有别于 Java 的定义方式,格式如下def map = [key1:value1,key2:value2,...]

Groovy 中定义的 Map 默认类型是 java.util.LinkedHashMap

1
2
def map = [name: "张三", age: 18]
println map.getClass()

image-20220712170800854

获取元素值

Map 集合中指定 key 下的值有有两种方式

1
2
3
4
5
6
7
8
9
def map = [name: "张三", age: 25]
//通过java的方式获取value
println map.get("name")
//通过中括号方式获取value
println map["name"]
//通过"."的方式获取value
println map.name
//还可以通过GString的方式进行访问
println "姓名是:${map['name']},年龄是:${map.age}"

添加元素

有两种方式可以添加map的值

1
2
3
4
5
6
def map = [name: "张三", age: 25]
//通过中括号进行设置值
map['sex'] = "男"
//通过”.“的方式来设置值
map.level = 10
println map

Map 遍历

List 集合一样,Map 集合的遍历也是使用 each 方法来实现。

1
2
3
4
5
6
7
8
9
def map = [name: "张三", age: 25]
//不带角标的遍历
map.each { println "key:${it.key},value:${it.value}" }
//带角标的方式遍历
map.eachWithIndex { Map.Entry entry, int i ->
{
println entry.key + "-" + entry.value + " index = " + i
}
}

3.9 Groovy 面向对象

在Groovy中,如在任何其他面向对象语言中一样,存在类和对象的概念以表示编程语言的对象定向性质。Groovy类是数据的集合和对该数据进行操作的方法。在一起,类的数据和方法用于表示问题域中的一些现实世界对象。

Groovy中的类声明了该类定义的对象的状态(数据)和行为。因此,Groovy类描述了该类的实例字段和方法。

以下是Groovy中的一个类的示例。类的名称是Student,它有两个字段 - StudentIDStudentName。在main函数中,我们创建一个这个类的对象,并将值分配给对象的StudentIDStudentName

1
2
3
4
5
6
7
8
9
10
class Student {
int StudentID;
String StudentName;

static void main(String[] args) {
Student st = new Student();
st.StudentID = 1;
st.StudentName = "Joe"
}
}

getter和setter方法

在任何编程语言中,总是使用private关键字隐藏实例成员,而是提供getter和setter方法来相应地设置和获取实例变量的值。以下示例显示如何完成此操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Student {
private int StudentID;
private String StudentName;

void setStudentID(int pID) {
StudentID = pID;
}

void setStudentName(String pName) {
StudentName = pName;
}

int getStudentID() {
return this.StudentID;
}

String getStudentName() {
return this.StudentName;
}

static void main(String[] args) {
Student st = new Student();
st.setStudentID(1);
st.setStudentName("Joe");

println(st.getStudentID());
println(st.getStudentName());
}
}

当我们运行上面的程序,我们将得到以下结果 -

1
2
1 
Joe

请注意以下关于上述程序的要点 -

  • 在类中,studentID和studentName都标记为private,这意味着无法从类外部访问它们。
  • 每个实例成员都有自己的getter和setter方法。getter方法返回实例变量的值,例如方法int getStudentID()和setter方法设置实例ID的值,例如method - void setStudentName(String pName)

实例方法

在类中包含更多的方法通常是一个很自然的事情,它实际上为类实现了一些功能。在我们的学生示例中,让我们添加Marks1,Marks2和Marks3的实例成员,以表示学生在3个科目中的标记。然后我们将添加一个新的实例方法,计算学生的总分。以下是代码的外观。

在下面的示例中,Total方法是一个额外的Instance方法,它内置了一些逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Student {
int StudentID;
String StudentName;

int Marks1;
int Marks2;
int Marks3;

int Total() {
return Marks1+Marks2+Marks3;
}

static void main(String[] args) {
Student st = new Student();
st.StudentID = 1;
st.StudentName="Joe";

st.Marks1 = 10;
st.Marks2 = 20;
st.Marks3 = 30;

println(st.Total());
}
}

当我们运行上面的程序,我们将得到以下结果 -

1
60

继承

继承可以定义为一个类获取另一个类的属性(方法和字段)的过程。通过使用继承,信息以分级顺序可管理。

继承其他属性的类称为子类(派生类,子类),属性继承的类称为超类(基类,父类)。

extends是用于继承类的属性的关键字。下面给出了extends关键字的语法。在下面的例子中,我们做了以下事情 -

  • 创建一个名为Person的类。这个类有一个名为name的实例成员。
  • 创建一个名为Student的类,它从Person类继承。请注意,在Person类中定义的名称实例成员在Student类中继承。
  • 在Student类构造函数中,我们调用了基类构造函数。
  • 在我们的Student类中,我们添加了2个StudentID和Marks1的实例成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Example {
static void main(String[] args) {
Student st = new Student();
st.StudentID = 1;

st.Marks1 = 10;
st.name = "Joe";

println(st.name);
}
}

class Person {
public String name;
public Person() {}
}

class Student extends Person {
int StudentID
int Marks1;

public Student() {
super();
}
}

当我们运行上面的程序,我们将得到以下结果 -

1
Joe

内部类

内部类在另一个类中定义。封闭类可以像往常一样使用内部类。另一方面,内部类可以访问其封闭类的成员,即使它们是私有的。不允许除封闭类之外的类访问内部类。

下面是一个外部和内部类的例子。在下面的例子中,我们做了以下事情 -

  • 创建一个名为Outer的类,它将是我们的外部类。
  • 在Outer类中定义名为name的字符串。
  • 在我们的外类中创建一个内部或嵌套类。
  • 请注意,在内部类中,我们可以访问在Outer类中定义的名称实例成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Example { 
static void main(String[] args) {
Outer outobj = new Outer();
outobj.name = "tom";
outobj.callInnerMethod()
}
}

class Outer {
String name;

def callInnerMethod() {
new Inner().methodA()
}

class Inner {
def methodA() {
println(name);
}
}

}

当我们运行上面的程序,我们将得到以下结果 -

1
tom

抽象类

抽象类表示通用概念,因此,它们不能被实例化,被创建为子类化。他们的成员包括字段/属性和抽象或具体方法。抽象方法没有实现,必须通过具体子类来实现。抽象类必须用抽象关键字声明。抽象方法也必须用抽象关键字声明。

在下面的示例中,请注意,Person类现在是一个抽象类,不能被实例化。还要注意,在抽象类中有一个名为DisplayMarks的抽象方法,没有实现细节。在学生类中,必须添加实现细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Example { 
static void main(String[] args) {
Student st = new Student();
st.StudentID = 1;

st.Marks1 = 10;
st.name="Joe";

println(st.name);
println(st.DisplayMarks());
}
}

abstract class Person {
public String name;
public Person() { }
abstract void DisplayMarks();
}

class Student extends Person {
int StudentID
int Marks1;

public Student() {
super();
}

void DisplayMarks() {
println(Marks1);
}
}

当我们运行上面的程序,我们将得到以下结果 -

1
2
Joe 
10

接口

接口定义了类需要遵守的契约。接口仅定义需要实现的方法的列表,但是不定义方法实现。需要使用interface关键字声明接口。接口仅定义方法签名。接口的方法总是公开的。在接口中使用受保护或私有方法是一个错误。

以下是groovy中的接口示例。在下面的例子中,我们做了以下事情 -

  • 创建一个名为Marks的接口并创建一个名为DisplayMarks的接口方法。
  • 在类定义中,我们使用implements关键字来实现接口。 因为我们是实现
  • 因为我们正在实现接口,我们必须为DisplayMarks方法提供实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Example {
static void main(String[] args) {
Student st = new Student();
st.StudentID = 1;
st.Marks1 = 10;
println(st.DisplayMarks());
}
}

interface Marks {
void DisplayMarks();
}

class Student implements Marks {
int StudentID
int Marks1;

void DisplayMarks() {
println(Marks1);
}
}

当我们运行上面的程序,我们将得到以下结果 -

1
10

4. 闭包

闭包是Groovy语言的精髓之一,Groovy的闭包大大简化了容器的遍历,提升了代码的可扩展性,使代码更加简洁优雅,闭包在Groovy编程中几乎无处不在。

什么是闭包

闭包(Closure)是很多编程语言中很重要的概念,那么Groovy中闭包是什么,官方定义是“Groovy中的闭包是一个开放,匿名的代码块,可以接受参数,返回值并分配给变量”,简而言之,他说一个匿名的代码块,可以接受参数,有返回值,那么到底是怎么样的

闭包特点

groovy 中的闭包是一个开放的匿名代码块,可以接受参数,返回值可以赋值给变量。

​ 我们通常学习的 lamb 表达式是有一定封闭空间,无法访问闭包的变量

1
2
3
4
5
int anser = 42;
Supplier supplier = () -> anser;
//这一行注释解开那将会报错,不能进行编译
//anser = 45;
System.out.println(supplier.get());

而在 groovy 的闭包是可以访问到外部变量的,虽然这样更自由但是也打破这个封闭空间

1
2
3
4
int anser = 42;
def supplier = { anser }
anser = 45;
println supplier.call()

如何定义

定义闭的语意 :{ [closureParameters -> ] statements },其中[closureParameters->]代表参数们,多参数用逗号分割,用->隔开参数与内容,没有参数可以不写->,其中[]内是可选的闭包参数,可省略。当闭包带有参数,就需要->来将参数和闭包体相分离

直接定义

直接用大括号定义就是一个最简单的闭包

1
2
def hello = {println "hello world"}
hello()
传递参数

闭包是可以传递参数的,类似于lamda表达式的使用

1
2
def hello = {x -> println "参数值:${x}"}
hello(100)
接受不同类型参数
1
2
def info = { String name,int age -> name + "," + age}
println info("matthew",30)
使用隐含变量 it

有时候我们看到闭包没有传递参数但是可以使用参数it,这是使用到了闭包的隐含参数

1
2
3
{it -> println "she is ${it}" }
//上面的闭包可以利用隐含参数简化为
{println "she is ${it}"}
作为一个对象使用

闭包在groovy中是groovy.lang.Closure类的实例,这使得闭包可以赋值给变量或字段。

1
2
3
4
def  closure  = {println "hello world"}
assert closure instanceof Closure
Closure callback = {println "方法执行成功"}
Closure<Boolean> isOK = {boolean flag-> if(flag) return true}

闭包的调用

直接调用

上面我们已经了解到了如何调用闭包,最简单的方式就是直接调用闭包加上一个括号就可以调用了,括号里面可以传递闭包参数

1
2
def hello = {println "hello world"}
hello();
参数传递

闭包是可以传递参数的,如果有参数是可以通过括号进行传递的,如果多个参数可以在后面加上逗号

1
2
3
4
def hello = {x -> println "参数值:${x}"}
hello(100)
def info = { String name,int age -> name + "," + age}
println info("matthew",30)
闭包返回结果

因为闭包是一个函数,所以可以直接调用就可以获取返回结果

1
2
3
4
5
6
def sqrt = { x -> return x * x }
def result = sqrt(10)
println result
//也可以直接输出
def sqrt = { x -> return x * x }
println sqrt(10)
3.3.4 通过call调用

上面都是通过直接函数调用来进行调用,还可以通过call来进行调用

1
2
def sqrt = { x -> return x * x }
println sqrt.call(10)
3.3.5 闭包的参数
  • 参数是可以有类型也可以不定义类型
  • 有一个隐含参数 it
  • 接受可变参数
1
2
def greeting =  { String ...name ->  "hey ${name.join(" ")}" } 
println greeting("matthew","jerry")

5. Groovy 文件I/O操作

Groovy在使用I / O时提供了许多辅助方法,Groovy提供了更简单的类来为文件提供以下功能。

  • 读取文件
  • 写入文件
  • 遍历文件树
  • 读取和写入数据对象到文件

除此之外,您始终可以使用下面列出的用于文件I / O操作的标准Java类。

  • java.io.File
  • java.io.InputStream
  • java.io.OutputStream
  • java.io.Reader
  • java.io.Writer

使用java代码的基本写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
FileInputStream fin = null;
try {
fin = new FileInputStream("E:/tmp/xxx.log");
BufferedReader br = new BufferedReader(new InputStreamReader(fin));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
// TODO: handle exception
} catch (IOException e) {
// TODO: handle exception
} finally {
try {
if (fin != null) {
fin.close();
}
}
catch (IOException e2) {
// TODO: handle exception
}
}

对一个文件进行读取的时候,基本上都会用到上面的代码,重复的写这些代码让人感觉很枯燥,同时在阅读这样的代码的时候也极易干扰视线。真正要干的事情也就是把文件内容输出而已。

读取文件

以下示例将输出Groovy中的文本文件的所有行。方法eachLine内置在Groovy中的File类中,目的是确保文本文件的每一行都被读取。

1
2
3
4
5
6
7
8
import java.io.File 
class Example {
static void main(String[] args) {
new File("E:/Example.txt").eachLine {
line -> println "line : $line";
}
}
}

File类用于实例化以文件名作为参数的新对象。 然后它接受eachLine的函数,将它放到一个line的变量并相应地打印它。

如果文件包含以下行,它们将被打印。

1
2
line : Example1
line : Example2

读取文件的内容到字符串

如果要将文件的整个内容作为字符串获取,可以使用文件类的text属性。以下示例显示如何完成此操作。

1
2
3
4
5
6
class Example { 
static void main(String[] args) {
File file = new File("E:/Example.txt")
println file.text
}
}

如果该文件包含以下行,它们将被打印出来。

1
2
line : Example1 
line : Example2

写入文件

如果你想写入文件,你需要使用作家类输出文本到一个文件中。下面的例子说明了如何可以做到这一点。

1
2
3
4
5
6
7
8
import java.io.File 
class Example {
static void main(String[] args) {
new File('E:/','Example.txt').withWriter('utf-8') {
writer -> writer.writeLine 'Hello World'
}
}
}

如果你打开文件example.txt文件,您将看到文本中打印了“Hello World”这个词。

测试文件是否是目录

如果要查看路径是文件还是目录,可以使用File类的isFile和isDirectory选项。以下示例显示如何完成此操作。

1
2
3
4
5
6
7
class Example { 
static void main(String[] args) {
def file = new File('E:/')
println "File? ${file.isFile()}"
println "Directory? ${file.isDirectory()}"
}
}

上面的代码将显示以下输出 -

1
2
File? false 
Directory? True

创建目录

如果要创建一个新目录,可以使用File类的mkdir函数。以下示例显示如何完成此操作。

1
2
3
4
5
6
class Example {
static void main(String[] args) {
def file = new File('E:/Directory')
file.mkdir()
}
}

如果目录E:\ Directory不存在,将创建它。

删除文件

如果要删除文件,可以使用File类的delete功能。以下示例显示如何完成此操作。

1
2
3
4
5
6
class Example {
static void main(String[] args) {
def file = new File('E:/Example.txt')
file.delete()
}
}

如果存在该文件将被删除。

复制文件

Groovy还提供将内容从一个文件复制到另一个文件的功能。以下示例显示如何完成此操作。

1
2
3
4
5
6
7
class Example {
static void main(String[] args) {
def src = new File("E:/Example.txt")
def dst = new File("E:/Example1.txt")
dst << src.text
}
}

将创建文件Example1.txt,并将文件Example.txt的所有内容复制到此文件。

获取目录内容

以下示例显示如何使用File类的eachFile函数列出特定目录中的文件。

1
2
3
4
5
6
7
class Example {
static void main(String[] args) {
new File("E:/Temp").eachFile() {
file->println file.getAbsolutePath()
}
}
}

输出将显示目录E:\ Temp中的所有文件

如果要递归显示目录及其子目录中的所有文件,则可以使用File类的eachFileRecurse函数。以下示例显示如何完成此操作。

1
2
3
4
5
6
7
class Example { 
static void main(String[] args) {
new File("E:/temp").eachFileRecurse() {
file -> println file.getAbsolutePath()
}
}
}

输出将显示目录E:\ Temp中的所有文件及其子目录(如果存在)。

6. Java运行Groovy脚本

Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。

​ 可将java代码在Groovy脚本动态编码、代码被修改达到不重启服务的目的(类似于热部署)

核心涉及

  • ClassLoader:就是类的装载器,它使JVM可以动态的载入Java类,JVM并不需要知道从什么地方(本地文件、网络等)载入Java类,这些都由ClassLoader完成。
  • GroovyClassLoader:动态地加载一个脚本并执行它的行为。GroovyClassLoader是一个定制的类装载器,负责解释加载Java类中用到的Groovy类。

环境搭建

添加maven环境
1
2
3
4
5
6
7
8
9
10
11
<!--Groovy脚本依赖-->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.5.14</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
创建Groovy脚本装载类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class GroovyUtils {

private final static ClassLoader classLoader = GroovyUtils.class.getClassLoader();//获取当前类装载器
//ClassLoader:就是类的装载器,它使JVM可以动态的载入Java类,JVM并不需要知道从什么地方(本地文件、网络等)载入Java类,这些都由ClassLoader完成。

public final static GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader);
//GroovyClassLoader:负责在运行时编译groovy源代码为Class的工作,从而使Groovy实现了将groovy源代码动态加载为Class的功能。

/**
* .
* 获取实例化对象
*
* @param script groovy脚本内容
* @param <T>
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static <T> T instanceTaskGroovyScript(String script) throws IllegalAccessException, InstantiationException {
Class taskClz = groovyClassLoader.parseClass(script);
T instance = (T) taskClz.newInstance();
return instance;
}
}

定义代码

定义java接口
1
2
3
4
5
6
7
8
/**
* 文件扫描接口
*/
public interface FileScanFilter {

public List<String> scan(String path);
}

定义Groovy实现

在resources目录下添加groovy的是实现类,并实现java的FileScanFilter接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package groovy

import com.heima.buzz.FileScanFilter

/**
* Groovy目录扫描是是实现
*/
class FileScanFilterImpl implements FileScanFilter {

@Override
List<String> scan(String path) {
File file = new File(path)
List<String> fileList = new ArrayList<>();
file.eachFileRecurse {
fileList << "${it.name}".toString()
}
return fileList
}
}
Java进行调用
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException {
//加载groovy文件
String script = IOUtils.toString(GroovyUtils.class.getResourceAsStream("/groovy/FileScanFilterImpl.groovy"), "UTF-8");
//读取文件并使用groovy的类加载器加载groovy文件
FileScanFilter filter = GroovyUtils.instanceTaskGroovyScript(script);
//扫描路径下的文件
List<String> list = filter.scan("e:/tmp");
//并将结果返回
list.forEach(str-> System.out.println(str));
}

测试

测试代码

运行代码后可以得到以下结果

1
2
3
4
5
6
7
config.xml
data.json
dsl.txt
out.txt
xxx.log
xxx.txt
yiming_oa_back.zip

动态修改

我们可以在不改动java代码的情况下改动groovy代码的情况下改变业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package groovy

import com.heima.buzz.FileScanFilter
import groovy.io.FileType

/**
* Groovy目录扫描是是实现
*/
class FileScanFilterImpl implements FileScanFilter {

@Override
List<String> scan(String path) {
File file = new File(path)
List<String> fileList = new ArrayList<>();
file.eachFileRecurse(FileType.FILES) {
fileList << "文件路径:${it.path}".toString()
}
return fileList
}
}
再次测试
1
2
3
4
5
6
文件路径:e:\tmp\config.xml
文件路径:e:\tmp\data.json
文件路径:e:\tmp\dsl.txt
文件路径:e:\tmp\out.txt
文件路径:e:\tmp\xxx.log
文件路径:e:\tmp\xxx.txt