简介
groovy可以当成java的脚本化改良版,同样运行于JVM之上,可以很好地和java代码及相关库进行交互,既可以面向对象编程,也可以用作纯粹的脚本语言。Groovy支持动态类型转换、闭包、元编程、函数式编程、默认作用域为public
(不支持default
)、基本类型为对象(可以直接调用对象的方法)、支持领域特定语言DSL和其他简洁语法,并且完全兼容java语法。
官方文档,下载地址,下载好压缩包后,解压、将bin目录的路径加入到path环境变量中即可,而后在命令行中验证:
C:\Users\songzeceng>groovy -v
Groovy Version: 4.0.2 JVM: 1.8.0_231 Vendor: Oracle Corporation OS: Windows 10
在Idea中创建groovy项目
创建新项目时,选择Groovy,然后在Groovy library中选择groovy解压目录(已有Groovy library则不用),再点击Next:
而后给项目起个名字,点击Finish:
最后,会得到如下所示的groovy项目:
Groovy基本语法
可参见官方文档
类的定义
class Test {
int a = 1
String name
boolean flag
}
Groovy脚本:
def username = 'szc'
println(username)
编译后,Groovy类产生的字节码,反编译成java代码后,内容如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import groovy.transform.Internal;
import java.beans.Transient;
public class Test implements GroovyObject {
private int a;
private String name;
private boolean flag;
@Generated
public Test() {
byte var1 = 1;
this.a = var1;
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
@Generated
@Internal
@Transient
public MetaClass getMetaClass() {
MetaClass var10000 = this.metaClass;
if (var10000 != null) {
return var10000;
} else {
this.metaClass = this.$getStaticMetaClass();
return this.metaClass;
}
}
@Generated
@Internal
public void setMetaClass(MetaClass var1) {
this.metaClass = var1;
}
@Generated
public int getA() {
return this.a;
}
@Generated
public void setA(int var1) {
this.a = var1;
}
@Generated
public String getName() {
return this.name;
}
@Generated
public void setName(String var1) {
this.name = var1;
}
@Generated
public boolean getFlag() {
return this.flag;
}
@Generated
public boolean isFlag() {
return this.flag;
}
@Generated
public void setFlag(boolean var1) {
this.flag = var1;
}
}
反编译分析
Groovy脚本得到的反编译java代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
public class Demo01 extends Script {
public Demo01() {
}
public Demo01(Binding context) {
super(context);
}
public static void main(String... args) {
InvokerHelper.class.invoke<invokedynamic>(InvokerHelper.class, Demo01.class, args);
}
public Object run() {
Object username = "szc";
return this.invoke<invokedynamic>(this, username);
}
}
可见两者的父类是不同的。我们可以在Groovy脚本中定义类,但类名不能和文件名一致:
def username = 'szc'
println(username)
class Person {
int age
String name
}
//class Demo01 { // 类名不能和文件名一致
//
//}
实际上,更推荐用def定义变量、字段或方法:
def count = 1
class Person {
def age
def name
public def getName() {
return name
}
}
类的使用
def p = new Person(hometown: "Anyang") // 字段赋值方式1:具名构造器
p.age = 25 // 字段赋值方式2:对象.字段名 = 字段值
p["name"] = "szc" // 字段赋值方式3:对象["字段名"] = 字段值
p.setGender("male") // 字段赋值方式4:对象.set字段名(字段值)
println p.metaClass.class.name // 不引起歧义的情况下,方法调用的()可以省略
println p.toString()
println p.getName()
println p.say()
class Person {
def age
def name
def gender
def hometown
public def getName() {
return name
}
public def say() {
"${name} said, 'Oh my Jesus!'" // 方法体最后一句,即为方法返回值
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name=" + name +
", gender=" + gender +
", hometown=" + hometown +
'}';
}
}
引号和字符串:
def s = "test"
def s1 = '单引号字符串,不支持换行操作和$引用,$(s)'
def s2 = "双引号字符串,不支持换行操作,但支持\$引用,${s}"
def s3 = """
三双引号字符串,支持换行操作,支持$引用:
${s}
"""
def s4 = '''
三但引号字符串,支持换行操作,但不支持\\$引用:
${s}
'''
println s1
println s2
println s3
println s4
/*
单引号字符串,不支持换行操作和$引用,$(s)
双引号字符串,不支持换行操作,但支持$引用,test
三双引号字符串,支持换行操作,支持$引用:
test
三但引号字符串,支持换行操作,但不支持\$引用:
${s}
*/
输出一下四种字符串的类型:
println s1.class.name // java.lang.String
println s2.class.name // org.codehaus.groovy.runtime.GStringImpl
println s3.class.name // org.codehaus.groovy.runtime.GStringImpl
println s4.class.name // java.lang.String
可见,单引号字符串类型就是String,双引号字符串的类型则是GStringImpl
列表
Groovy中的列表就是ArrayList,但是支持+、-、+=、-=这些操作符,以便向列表里添加或删除新的列表,遍历时,支持以闭包的形式遍历:
def list1 = [1, 2, 3, 4, 5]
println(list1) // [1, 2, 3, 4, 5]
list1.add(6)
println(list1) // [1, 2, 3, 4, 5, 6]
list1 = list1.plus([7, 8, 9, 10])
list1 += [11, 12, 13, 14]
println(list1) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
list1.remove(list1.size() - 1)
list1 = list1 - [11, 12]
list1.removeAll([11, 10])
println(list1) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 13]
println(list1.pop()) // 弹出第一个元素,输出为1
println(list1) // [2, 3, 4, 5, 6, 7, 8, 9, 13]
list1.putAt(15, 20) // 给指定位置设置元素值,不必和已有数据连续
list1[18] = 92 // 给指定位置设置元素值的另一种方式
println(list1) // [2, 3, 4, 5, 6, 7, 8, 9, 13, null, null, null, null, null, null, 20, null, null, 92]
// 遍历列表
list1.each {
if (it instanceof Integer && it != null) { // it即当前元素
def x = it * 2
println(x)
} else {
println(0)
}
}
// 输出:
/*
4
6
8
10
12
14
16
18
26
0
0
0
0
0
0
40
0
0
184
*/
映射
Groovy中的映射是LinkedHashMap,同样支持+、-、+=、-=这些操作符,遍历时,支持以闭包的形式遍历:
def map = ["Name": "szc", "Age": 25] // 映射初始化
map.put("Gender", "male") // 往映射里添加键值对,键已存在则覆盖
map += ["Hometown": "Anyang", "School": "UESTC"] // 往映射里添加新的映射,通过操作符的方式
println(map) // [Name:szc, Age:25, Gender:male, Hometown:Anyang, School:UESTC]
map.remove("School") // 从映射里删除键对应的键值对,无此键则跳过
map.remove("Hometown", "Anyang") // // 从映射里删除键和值对应的键值对,无此键值对则跳过
map = map - ["Gender": "male"] // 从映射里删除子集映射,通过操作符的方式
println(map) // [Name:szc, Age:25]
// 遍历映射,以键值对的方式
map.each {key, value -> {
println("key: $key, value: $value")
}}
// 输出:
/*
key: Name, value: szc
key: Age, value: 25
*/
// 遍历映射,以entry的方式
map.each {
println("key: ${it.key}, value: ${it.value}")
}
// 输出:
/*
key: Name, value: szc
key: Age, value: 25
*/
类导入和异常处理
跟java中类似:
// 类导入
import groovy.xml.MarkupBuilder
def builder = new MarkupBuilder()
// 异常捕获方式1:和java里完全一样
try {
def x = 1, y = 0
def t = x / y
} catch (e) {
e.printStackTrace()
} finally {
println ("here we are at finally")
}
// 异常捕获方式2:try-catch中嵌套try-finally,和方式一等效
try {
try {
def x = 1, y = 2
def t = x / y
println(t)
} finally {
println ("here we are at finally")
}
} catch (e) {
e.printStackTrace()
}
闭包
定义
闭包是一个开放的匿名代码块,可以有参数(默认为it)和返回值,可以引用周围作用域中声明的变量,语法:
{
[params ->] statements
}
调用
先将闭包赋值给一个变量,再通过变量名()或变量名.call()调用:
def name = "test"
def f1 = {
Integer x, Integer y, Integer z -> {
println("$name, x = $x, y = $y, z = $z")
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2))
}}
def ret = f1(1, 2, 5)
println(ret)
println f1.call(2, 3, 4)
// 输出:
/*
test, x = 1, y = 2, z = 5
5.477225575051661
test, x = 2, y = 3, z = 4
5.385164807134504
*/
实际开发中,经常把闭包当成一个对象,传入给方法:
// 无参闭包做参数
def run(Closure closure) {
println("Start running.......")
closure() // 执行闭包
println("Stop running.......")
}
run { // 传入无参闭包
println("Running....")
// -> println("Running....") 也可以这样指定无参闭包,上面的方式带有隐式参数it
}
// 有参闭包做参数
def distance(Closure closure, int x1, int x2, int y1, int y2) {
closure(x1, x2, y1, y2)
}
// 传入有参闭包和参数,得到返回结果并输出
def ret1 = distance({ int x1, int x2, int y1, int y2 ->
{
Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2))
}
}, 8, -2, 4, 9)
println(ret1)
// 输出:
/*
Start running.......
Running....
Stop running.......
11.180339887498949
*/
如果方法中的有参闭包参数是参数列表的最后一个,那么调用该方法时,可以将有参闭包写在方法调用外面:
def distance2(int x1, int x2, int y1, int y2, Closure closure) { // 有参闭包为方法的最后一个参数
closure(x1, x2, y1, y2)
}
println(distance2(8, -2, 4, 9) { // 将有参闭包体写在方法调用外面
int x1, int x2, int y1, int y2 ->
{
Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2))
}
})