hello world
传统的类+static main函数
的方式:
class Car {
static main(args){
println 'hello world'
}
}
也可以直接这么写:
println 'hello world1'
基本语法
函数最后一行作为返回值、返回多值
def split(String s) {
s.split(",")
}
def (a, b) = split("julia,coca,python")
println "$a $b"
输出:
julia coca
最后一个值python被丢弃了。
bool求值
groovy行为类似于python而非java,下面的例子给出了值判空、集合判空的例子:
def l = [1,2], l1 = []
if(l){
println("collection NOT empty")
}
if(!l1){
println("collection IS empty")
}
def (c,d) = [1,0]
if(c){
println('value NOT zero')
}
if(!d){
println('value zero')
}
说明:
值为null也会被判定为false
这个类C语言的特性比java方便多了。
字符串
字符串的用法也跟python很像,单引号、双引号括起来皆可,多行字符串用三引号表示:
a = 'julia'
s = """introduce
i am a teacher $a
"""
println s
s = 'tutu $a'
println(s)
s = "tutu $a"
println s
注意:
双引号会执行其中的$表达式(术语叫字符串插值),单引号则不会
当然,python可没有字符串插值的能力,scala倒是有:
val a = "julia"
println(s"tutu $a")
在scala里除了s插值,还有f插值、raw插值,具体可参考相关资料。
集合
列表
l = [1,2]
for(i in l){
println i
}
println(l[1])
println(l[-1])
l.add(3)
l << 4
println l
演示了:
- 列表定义
- 遍历
- 下标访问(注意:负下标也是有效的)
- 尾部添加
行为上跟python的list几乎是一样的
字典
m = ['china':1, 'usa':2]
m.each { k,v ->
println "$k=>$v"
}
println m['china']
m.put('russia',3)
println m
演示了:
- 字典定义,跟python有点区别,使用[],而非{}
- 遍历
- 下标访问
- 添加元素
遍历方法跟scala很像,scala里字典遍历的写法是这样:
val m = Map(1 -> "dudu", 2 -> "haha")
m.foreach{case (k,v) => println(s"$k=>$v")}
这里case语句作为一个偏函数传入foreach。
groovy下each传入的则是一个闭包(或称之为代码块)。
闭包
groovy里的闭包其实是匿名函数,严格的说,只有捕获了外部变量的匿名函数,才算闭包,所以groovy使用闭包这个术语不太严谨。
我们令pickEven函数可以接收一个闭包block:
def pickEven(n, block){
for(i=0; i<=n; i+=2){
block(i)
}
}
在groovy里有两种调用方式:
//普通调用
pickEven(10, {i -> println(i)})
//curry化调用
pickEven(10){
i -> println(i)
}
看一下对应的scala的例子:
def pickEven(n:Int, block: Int => Unit): Unit ={
0.to(n, 2).foreach(block)
}
//普通调用
pickEven(10, x => println(x))
def pickEven1(n:Int)(block: Int => Unit): Unit ={
0.to(n, 2).foreach(block)
}
//curry化调用
pickEven1(10){
(x:Int) => println(x)
}
可见,groovy里的闭包等价于scala或java里的lambda。区别仅在于:
- groovy的闭包必须用{}括起来,而scala则不必
- groovy闭包的curry化调用更方便,无需额外定义函数的curry化版本
当然,不论groovy的闭包还是scala的lambda,都具备访问外部变量的能力。
groovy下闭包访问外部变量total:
total = 0
pickEven(10){
total += it
}
println("total $total")
对应的,scala下lambda访问外部变量total:
var total = 0
pickEven1(10){
x => total += x
}
println(s"total $total")
另外,groovy闭包的curry化调用很适合用作资源清理自动化,在scala里也有类似的技巧。
java调用groovy
java调用groovy有两种方式,一是静态调用,二是动态调用。
静态调用
先定义一个groovy类:
class Car {
def miles = 0
final year
Car(theYear){
year = theYear
}
}
接着在java里直接访问:
private static void testGroovy()
{
Car c = new Car(1980);
System.out.println(c.getMiles());
System.out.println(c.getYear());
}
groovy会为def声明的成员变量自动生成get、set方法,所以对Car.miles的访问变成c.getMiles()。
动态调用
动态调用要通过ScriptEngine:
private static void testScriptEngine() throws ScriptException, IOException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine se = manager.getEngineByName("groovy");
Object res = se.eval("1+1");
System.out.println(res.toString());
String path = "src/main/resources/hello.groovy";
try(FileReader fr = new FileReader(path))
{
se.eval(fr);
}
}
hello.groovy脚本的内容:
println 1+4
println('hello world')
import com.isearch.study.*
def son = new Son()
son.doSth()
执行结果:
2
5
hello world
Son doSth
我们发现,java作为宿主调用groovy脚本后,在groovy脚本中还可以再反向调用java中的类Son。这是因为groovy classloader的父亲就是application classloader,根据双亲委派原则,最终还是会由application classloader来负责加载java类。
双亲委派原则
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
如果类Son不存在,一般会报错:
catch exception:org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
Script2.groovy: 5: unable to resolve class Son1
@ line 5, column 11.
def son = new Son1()
^
1 error
实际中,如果出现“unable to resolve class”的错误,往往是类加载器体系出了问题。
groovy调用java
groovy可以无缝的访问java类