Groovy 学习笔记
1. 安装
groovy 是一个基于 JVM 的语言,可以解释执行,也可以编译。
它的安装和普通的编程语言一样,下载一个目录包,使用里面 bin
目录中的可执行程序就可以执行,但是 groovy 依赖 JDK ,需要 java
这个可执行程序。
下载页面在:https://groovy.apache.org/download.html
2. 语法
groovy 本身兼容 java 的语法(静态语言语法)。同时,它又有自己独立的另外一套语法(动态语言语法)。 groovy 语法受 Python 和 Ruby 影响,所以一些细节是比较友好的。当然,从另一方面来说,细节就多了很多,也算复杂了。
2.1. 定义和赋值
一方面,总是可以使用 def
或者 var
来定义变量,函数。另一方面,可以像 java 一样,以类型开头:
def a = 1 int b = 1 var c = [a, b] // println [a, b] println c
上面代码中上,直接写注释的那一行是不行的。可以写成 println([a, b])
,这里的括号不能省。嗯, Ruby 的那套吧。
也可以同时赋值多个变量:
def (a, b, c) = [10, 'a', []] println a
2.2. 关键词
groovy 有一些关键词,比如 def
。但是,仍然可以用关键词作为函数名或 map 的 key,加上引号就行:
def 'def' () { def m = ['def': 12345] println m.'def' } "def"()
这个有点意思啊。既然关键词可以,那带空格或者特殊符号的句子也可以:
def 'let me try space here' () { def m = ['def': 12345] println m.'def' } "let me try space here"()
另外, groovy 中的变量,是可以用 $ 开头的:
def $a = 123 println $a
2.3. 字符串
字符串的处理有单引号,双引号,斜线,美元号斜线,表达式。表达式还可以是 闭包 。
单引号中不处理表达式,其它的可以处理表达式。
def n = '123' def s1 = 'hello $n' def s2 = "hello ${n}" println s1 println s2
在可以的情况下,表达式 ${n}
可以不写大括号,直接写成 $n
。
闭包是一种类似“匿名函数”的表达式,它在需要对字符串求值时才运算。
def n = '123' def s = "hello ${-> n}" n = 'world' println s
上面的代码,最后输出是 hello world
。
多行字符串,可以使用 \
处理掉首行的换行:
def s = '''\ 1 2 3''' println s
2.4. 列表和字典
类似动态语言, groovy 中可以直接使用列表和字典:
def l = [1,2,3] def m = [a:1, b:2] println l[0] println m.a
l
的类型,默认是 List ,如果要使用数组,需要显式声明:
int[] a = [1,2,3] println a[0]
字典的 key 可以使用表达式:
def m = [ab: 1, 'a+b': 2, ('a'+'c'): 3] println m.('a' + 'b') println m.('a' + '+b')
groovy 对于 key 不存在,或者下标越界的情况不会报错,会返回 null
:
def m = [ab: 1, 'a+b': 2] def l = [1] println m.a println l[2]
但是,如果 m
或者 l
本身是 null
,则会抛出空指标异常,如果要避免这种情况,可以像在 nodejs 中一样,使用带问号的操作符:
def m = [ab: 1, 'a+b': 2] def l = [1] m = null l = null println m?.a println l?[2]
2.5. 正则表达式
使用 ~
加字符串,直接声明一个正则表达式实例。
def p = ~'a.*b' def m = 'a123b' =~ p println m.size() println m[0]
也可以用 ~/a.*b/
。
def p = ~/a(.)(..)b/ def m = 'a123b a333b' =~ p println m.size() println m[0] println m[0][2] println m[1] println m[1][2]
字符串 =~ 正则
是匹配操作。
2.6. 遍历取值
这个操作符好用, *.
:
class Hello { String name; } def l = [ new Hello(name: '123'), new Hello(name: 'abc'), null ] println l*.name println l*.getName() println null*.getName()
它可以很方便地直接提取列表中的每一个成员的指定属性或者指定方法调用,还不怕 null
。
2.7. 参数和赋值解构
void add(x, y, z) { println x + y + z } add(1,2,3) add(*[1,2,3]) add(1, *[2,3])
函数的参数传递,可以使用 *
,像 Python 中一样。
def a = [1,2] def b = [1,2, *a] println b def m = [a:1, b:2] def n = [c:1, *:m] println n
列表和字典中也可以直接使用 *
。
多重赋值:
def (a, b, c) = [1,2] println([a, b, c])
2.8. 范围 Range
groovy 中可以使用 ..
,还可以在 ..
两侧加大于小于:
def r = 1<..10 println r.size() println r.collect()
不加 <
就是“等于”。
2.9. record 类
record 类是一种带属性的类的简写形式:
record Person(String name, int age) { void say() { println "say $name" } } def p = new Person('nnnn', 12) p.say()
形式比较简洁紧凑。
3. 操作符重载
3.1. call
()
调用是 call()
方法:
class Hello { Hello call() { this } } def n = new Hello() println n()()()
3.2. Getter / Setter
通过 setXxx
和 getXxx
就可以定义这两个行为,也不需要有属性的定义。
class Hello { def setName(String name) { println 'set name' } def getName() { println 'get name' 'return' } } def hello = new Hello() hello.name = 123 println hello.name
3.3. 运算
groovy 用的好像是 Ruby 那套, +
对应 plus
方法:
class Hello { int n int plus(int a) { n + a; } int plus(String a) { a.size() + n } int plus(Hello a) { a.n + n } } println new Hello(n: 1) + 10 println new Hello(n: 1) + '10' println new Hello(n: 1) + new Hello(n: 100)
其它的一些方法有:
Operator | Method | |
---|---|---|
+ | plus | |
- | minus | |
* | multiply | |
/ | div | |
... | ... |
3.4. GetItem / SetItem
[]
取值和赋值对应的方法是 getAt
和 putAt
:
class Hello { void getAt(int i) { println i } void getAt(String s) { println 'String ' + s } void putAt(String s, int a) { println "@${s} = ${a}" } } def n = new Hello() n[1] n['123'] n['xxx'] = 123
3.5. As
as
这种,就是可以随意自定义的一个操作符了。
class Hello { int n String asType(Class cls) { if(cls == String) { if(n == 1){ return '一' } } super.asType(type) } } def n2 = [n:2] as Hello println n2.n def n = new Hello(n:1) println n as String
这里有一个注意点, as
后面不能是任意的标识,必须是一个类型。
4. Trait
4.1. 基本使用
trait 像是一个增强版的接口机制,在 groovy 中它还支持“动态声明使用”。
trait Flying { void fly() { println "${getName()}" } String getName() { 'Flying' } } class Hello implements Flying { String getName() { 'Hello' } } def h = new Hello() h.fly()
也可以多重继承:
trait Flying { void fly() { println "${getName()}" } String getName() { 'Flying' } } trait Going extends Flying { void fly() { println 'GGGG' } } class Hello implements Flying, Going { } def h = new Hello() h.fly()
右侧的优先级更高。
也可以使用属性:
trait Named { String name } class Hello implements Named {} def h = new Hello(name: '123') println h.name
动态声明:
trait Flying { void fly() { println 'flying' } } class Hello { } def h = new Hello() as Flying; h.fly()
动态声明多个 trait 的话,需要用到 withTraits
:
trait Flying { void fly() { println 'flying' } } trait Going { void go() { println 'going' } } class Hello { } def h = new Hello() def g = h.withTraits Flying, Going g.fly() g.go()
trait 还可以使用 super 做到运行时的“注入”:
trait Flying { void fly() { println "flying" super.fly() } } class Hello { void fly() { println 'Hello' } } def h = new Hello() as Flying h.fly()
4.2. SAM 单一抽象方法的语法糖
如果一个 trait 只有一个抽象方法(待实现),那么这个抽象方法可以在 trait 具象化定义时“顺带”实现:
trait Flying { void fly() { println "flying ${name}" } abstract String getName() } Flying f = { 'NAME' } f.fly()
5. 闭包
5.1. Closure
Closure 在 groovy 中,是一个用大括号括起来的匿名函数:
def i = 1 def j = {i++} def k = {n,n2 -> i + n + n2} println j() println i println k(10, 100)
j
和 k
都是在 call 时才会在当前上下文中求值。
不显式声明参数时,总有一个名为 it
的参数可用:
println({"hello $it"}('NAME'))
5.2. Currying
Currying ,柯里化,简单来说就是绑定函数参数,从而得到一个新函数:
def sqr = {it ** 2} println sqr(9) def s = sqr.curry(100) println s() def addN = {a, b -> a + b} println addN(1, 10) def add1 = addN.curry(1) println add1(100)
默认的 curry()
是绑定最左侧的一个参数,也可以使用 rcurry()
来绑定右侧参数:
def power = {a, b -> a ** b} println power(10, 2) def sqrt = power.rcurry(2) println sqrt(9) def three = power.curry(3) println three(9)
6. 其它语句
6.1. Switch
switch
除了常规的用法,还可以直接用于赋值,比较方便:
def n = 1 def x = switch(n) { case 1 -> 'A' case 2 -> 'B' } println x
6.2. for
for
中的赋值也可以使用多变量赋值:
for(def (i, j) = [1, 10]; i < j; i++) { println i }
虽然可以把 def
用在 for
里面有点别扭,把它看成是一个 type 的 placeholder 就好点了(这里的 def
也可以省略)。
也可以把 for
和 range 一起用:
for(i in 0..<5) { println i }
迭代 map :
for(item in [a: 1, b: 2]) { println item.key println item.value }
6.3. try / catch / finally
try { 1 / 0 } catch (e) { println e } finally { println 'finally' }
groovy 中也支持 java 的 try-with-resources ,会自动调用 close()
方法:
class Hello { void close() { println 'close' } } try ( Hello h = new Hello() ) { println h 1 / 0 }
7. 从约束到实例
7.1. interface / trait / abstract class
各种约束 interface , trait , abstract class ,它们中只需要一个方法实现时,都可以用一个“闭包”直接生成实例:
interface Cb<T> { void accept(T obj) } Cb c = {it -> println 'hello ' + it} as Cb c(11)
上面的代码中当 interface 中只有一个方法时(其实这个方法的名字也无所谓了),就可以直接生成实例。
abstract class Hello { abstract int getCount() int getSum() { 0 } } Hello h = {-> 123} println h.getCount() println h.getSum()
抽象类中的抽象方法,也可以被闭包“填充”。如果有多个抽象方法,则它们都会被一个闭包同时填充。
如果把类型写在前面,则 as Type
可以省略。
7.2. map
除了单方法, groovy 还支持用 map 定义多个方法之后,直接按键名填充方法实现后生成实例:
trait Hello { abstract String getName() abstract int getCount() void show() { println 'Hello' } } def h = [ getName: { 'Name' }, getCount: {it -> it + 2} ] as Hello h.show()
7.3. 字符串到枚举
这个特性也是很方便的。
enum State { GOOD, BAD, NORMAL } State s = 'GOOD' State s2 = State.GOOD println s == s2
8. 在 Java 项目中使用
以 maven 为例,使用两个 plugin 就可以了 pom 文件大概如下:
<dependencies> ... <dependency> <groupId>org.apache.groovy</groupId> <artifactId>groovy</artifactId> <version>4.0.15</version> </dependency> </dependencies> <build> <plugins> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> </plugin> <plugin> <groupId>org.codehaus.gmavenplus</groupId> <artifactId>gmavenplus-plugin</artifactId> <version>3.0.2</version> <executions> <execution> <goals> <goal>addSources</goal> <goal>addTestSources</goal> <goal>generateStubs</goal> <goal>compile</goal> <goal>generateTestStubs</goal> <goal>compileTests</goal> <goal>removeStubs</goal> <goal>removeTestStubs</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
更多的配置,可以参考: https://github.com/groovy/GMavenPlus/wiki/Examples
比如 Hello.groovy
:
package zys.me.mix import org.springframework.stereotype.Component @Component class Demo { private final JavaCls javaCls; Demo(JavaCls javaCls) { this.javaCls = javaCls; } void say() { println javaCls.out(); println new JavaCls().out(); } }
它编译出来是一个 Demo
类,里面的 say()
方法是:
public void say() { this.invoke<invokedynamic>(this, this.javaCls.invoke<invokedynamic>(this.javaCls)); this.invoke<invokedynamic>(this, ((Class)JavaCls.class.init<invokedynamic>(JavaCls.class)) .invoke<invokedynamic>(JavaCls.class.init<invokedynamic>(JavaCls.class))); }