Groovy是一种与Java非常相似的脚本语言,编译器会将该脚本语言编译成class字节码文件,最终运行于Java虚拟机之上。
前提是配置好JDK
Groovy环境在类Unix上配置,只需以下的几行命令即可:
第一步下载sdkman,这是管理sdk的工具,命令如下:
curl -s get.sdkman.io | bash
读取并执行sdkman的初始化脚本,如下命令:
source "$HOME/.sdkman/bin/sdkman-init.sh"
接着安装groovy的sdk,命令如下:
sdk install groovy
最后,检验是否安装成功:
groovy -version
安装完Groovy环境之后,输入groovy命令时,会有如下的工具:
groovyc:groovy编译器,类似于javac,将groovy脚本编译成class字节码文件
groovy:用于运行groovy脚本
groovysh:groovy命令交互式的shell,类似python中的交互式环境。
另外,很多出名的IDE均已支持Groovy,本人使用的是IntelliJ IDEA。
注意,Groovy的语法与Java十分相似,这里只重点介绍与Java有区别的语法,相同的就不再赘述。
引用标识符:Groovy中对变量的引用方式是多样化的,Test.groovy源码如下:
void testQuotedIdentifiers() {
def map = [:]
map.no_quote = 1
map.'single quote' = 2
map."double quote" = 3
map.'''triple single quote''' = 4
map."""triple double quote""" = 5
map./slashy string/ = 6
map.$/dollar slashy string/$ = 7
def closureQuote = "value"
map."closure quote ${closureQuote}" = 8
println(map)
}
testQuotedIdentifiers()
运行结果如下:
[no_quote:1, single quote:2, double quote:3, triple single quote:4, triple double quote:5, slashy string:6, dollar slashy string:7, closure quote value:8]
上述代码先定义一个静态方法,然后在方法内定义了一个Map(本质上是java中的LinkedHashMap实例)。接着使用不同的引用操作符的方式往这个map中添加键值对,最后打印这个map实例,如下图:
紧接着来看Test.groovy编译之后的class代码:
public class Test extends Script {
public Test() {
CallSite[] var1 = $getCallSiteArray();
}
public Test(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].call(InvokerHelper.class, Test.class, args);
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
if (!__$stMC && !BytecodeInterface8.disabledStandardMetaClass()) {
this.testQuotedIdentifiers();
return null;
} else {
return var1[1].callCurrent(this);
}
}
public void testQuotedIdentifiers() {
CallSite[] var1 = $getCallSiteArray();
Object map = ScriptBytecodeAdapter.createMap(new Object[0]);
byte var3 = 1;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var3), (Class)null, map, (String)"no_quote");
byte var4 = 2;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var4), (Class)null, map, (String)"single quote");
byte var5 = 3;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var5), (Class)null, map, (String)"double quote");
byte var6 = 4;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var6), (Class)null, map, (String)"triple single quote");
byte var7 = 5;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var7), (Class)null, map, (String)"triple double quote");
byte var8 = 6;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var8), (Class)null, map, (String)"slashy string");
byte var9 = 7;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var9), (Class)null, map, (String)"dollar slashy string");
Object closureQuote = "value";
byte var11 = 8;
ScriptBytecodeAdapter.setProperty(Integer.valueOf(var11), (Class)null, map, (String)ShortTypeHandling.castToString(new GStringImpl(new Object[]{closureQuote}, new String[]{"closure quote ", ""})));
var1[2].callCurrent(this, map);
}
}
由上可知,Test.groovy编译成Test.class之后,会继承Script类,然后定义了两个构造方法、main方法,run方法,最后才是testQuotedIdentifiers方法。
在testQuotedIdentifiers方法中调用了ScriptBytecodeAdapter.createMap方法创建了map实例,代码如下:
public static Map createMap(Object[] values) {
return InvokerHelper.createMap(values);
}
上述的方法只是一个封装,具体实现在InvokerHelper中:
public static Map createMap(Object[] values) {
Map answer = new LinkedHashMap(values.length / 2);
int i = 0;
while (i < values.length - 1) {
if ((values[i] instanceof SpreadMap) && (values[i + 1] instanceof Map)) {
Map smap = (Map) values[i + 1];
Iterator iter = smap.keySet().iterator();
for (; iter.hasNext();) {
Object key = iter.next();
answer.put(key, smap.get(key));
}
i += 2;
} else {
answer.put(values[i++], values[i++]);
}
}
return answer;
}
Groovy字符串分为好种:单引号字符串、双引号字符串、三引号字符串等,这些字符串均对应不同的应用场景。
单引号字符串:本质上是java的String,与java的String的使用方法也是一样。如下代码:
void testSingleQuotedString() {
def ss = 'SingleQuotedString'
println(ss)
}
testSingleQuotedString()
上述定义了一个单引号字符串然后打印到控制台。再来看看编译过后的class代码:
public void testSingleQuotedString() {
CallSite[] var1 = $getCallSiteArray();
Object ss = "SingleQuotedString";
var1[3].callCurrent(this, ss);
}
三引号字符串:分为三单引号字符串和三双引号字符串,目的是为了解决单行限制,如下三单引号字符串代码:
void testTripleSingleQuotedString() {
def tss = '''line one
line two
line three'''
println(tss)
}
testTripleSingleQuotedString()
再看编译过后的class代码,原来Groovy中多行字符串的实现原理是加了\n换行符,其实现也是java的String对象:
public void testTripleSingleQuotedString() {
CallSite[] var1 = $getCallSiteArray();
Object tss = "line one \nline two\nline three";
var1[6].callCurrent(this, tss);
}
双引号字符串:如果字符串中没有插值符,其底层实现是java的String,否则是groovy.lang.GString,如下实例:
void testDoubleQuotedString() {
def person = [:]
person.'name' = 'Jack'
def one = "person name:" + person.get("name")
def two = "person name:$person.name "
print(one + ' and ' + two)
}
testDoubleQuotedString()
上述代码定义了一个Map,在第一句println中是双引号字符串的普通使用,下一句是使用插值符用法。再来看看,编译后的class源码:
public void testDoubleQuotedString() {
CallSite[] var1 = $getCallSiteArray();
Object person = ScriptBytecodeAdapter.createMap(new Object[0]);
String var3 = "Jack";
ScriptBytecodeAdapter.setProperty(var3, (Class)null, person, (String)"name");
//双引号字符串的普通用法
Object one = var1[2].call("person name:", var1[3].call(person, "name"));
//双引号字符串的插值符用法
Object two = new GStringImpl(new Object[]{var1[4].callGetProperty(person)}, new String[]{"person name:", " "});
var1[5].callCurrent(this, var1[6].call(var1[7].call(one, " and "), two));
}
由上可知双引号字符串普通用法实现是Java的String,而插值符用法则使用了GStringImpl。
双引号字符串的插值符合+闭包表达式,展现了Groovy语言强大的一面,如下代码:
void testInterpolatingClosureExpressions() {
def number = 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${-> number}"
println(eagerGString)
println(lazyGString)
number = 2
println(eagerGString)
println(lazyGString)
}
testInterpolatingClosureExpressions()
上述的编译后的class如下:
public void testInterpolatingClosureExpressions() {
CallSite[] var1 = $getCallSiteArray();
Reference number = new Reference(Integer.valueOf(1));
Object eagerGString = new GStringImpl(new Object[]{number.get()}, new String[]{"value == ", ""});
class _testInterpolatingClosureExpressions_closure1 extends Closure implements GeneratedClosure {
public _testInterpolatingClosureExpressions_closure1(Object _thisObject, Reference number) {
CallSite[] var4 = $getCallSiteArray();
super(Test.this, _thisObject);
this.number = number;
}
public Object doCall() {
CallSite[] var1 = $getCallSiteArray();
return this.number.get();
}
public Object getNumber() {
CallSite[] var1 = $getCallSiteArray();
return this.number.get();
}
}
Object lazyGString = new GStringImpl(new Object[]{new _testInterpolatingClosureExpressions_closure1(this, number)}, new String[]{"value == ", ""});
var1[2].callCurrent(this, eagerGString);
var1[3].callCurrent(this, lazyGString);
byte var5 = 2;
((Reference)number).set(Integer.valueOf(var5));
var1[4].callCurrent(this, eagerGString);
var1[5].callCurrent(this, lazyGString);
}
区别是构造GStringImpl的参数不同,eagerGString使用的是Reference类型,而lazyGString使用的是_testInterpolatingClosureExpressions_closure1。
Slashy字符串:即斜杠字符串,是为正则表达式设计的,原因是除了正斜杠需要转义,其它的均不再需要转义。它还支持多行、插值的特性,如下代码:
void testSlashyString() {
def fooPattern = /.*foo.*/
println(fooPattern)
def escapeSlash = /The character \/ is a forward slash/
println(escapeSlash)
def multilineSlashy = /one
two
three/
println(multilineSlashy)
def color = 'blue'
def interpolatedSlashy = /a ${color} car/
println(interpolatedSlashy)
}
testSlashyString()
再看编译过后的class代码,会发现与双引号字符串本质是一样的:
public void testSlashyString() {
CallSite[] var1 = $getCallSiteArray();
Object fooPattern = ".*foo.*";
var1[2].callCurrent(this, fooPattern);
Object escapeSlash = "The character / is a forward slash";
var1[3].callCurrent(this, escapeSlash);
Object multilineSlashy = "one\n two\n three";
var1[4].callCurrent(this, multilineSlashy);
Object color = "blue";
Object interpolatedSlashy = new GStringImpl(new Object[]{color}, new String[]{"a ", " car"});
var1[5].callCurrent(this, interpolatedSlashy);
}
Groovy中字符类型要借助于字符串来辅助完成创建,方法如下:
void testCharacters() {
char c1 = 'A'
assert c1 instanceof Character
def c2 = 'B' as char
assert c2 instanceof Character
def c3 = (char) 'C'
assert c3 instanceof Character
}
testCharacters()
整型:Groovy中的byte、char、short、int、long和java.lang.BigInteger均跟Java一样,如下示例:
void testIntegralLiterals() {
// primitive types
byte b = 1
char c = 2
short s = 3
int i = 4
long l = 5
// infinite precision
BigInteger bi = 6
}
使用def来定义来整型数据,编译器会自动适配数据的类型,原则是在不丢失数据的前提下选取小容量类型,如下例子:
void testAdaptIntegralType() {
def a = 1
assert a instanceof Integer
// Integer.MAX_VALUE
def b = 2147483647
assert b instanceof Integer
// Integer.MAX_VALUE + 1
def c = 2147483648
assert c instanceof Long
// Long.MAX_VALUE
def d = 9223372036854775807
assert d instanceof Long
// Long.MAX_VALUE + 1
def e = 9223372036854775808
assert e instanceof BigInteger
}
除了十进制,Groovy同样支持二进制、八进制与十六进制,使用如下:
void test() {
//二进制,以0b开头
int xInt = 0b10101111
assert xInt == 175
//八进制,以0开头
int xInt = 077
assert xInt == 63
//十六进制,以0x开头
int xInt = 0x77
assert xInt == 119
}
Lists:Groovy使用方括号来定义列表,如下示例:
void testLists() {
def numbers = [1, 2, 3]
println('number:' + numbers.size())
}
上述代码编译过的字节码如下:
public void testLists() {
CallSite[] var1 = $getCallSiteArray();
Object numbers = ScriptBytecodeAdapter.createList(new Object[]{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)});
var1[2].callCurrent(this, var1[3].call("number:", var1[4].call(numbers)));
}
通过ScriptBytecodeAdapter的createList创建了这个list,再看其源码:
public static List createList(Object[] values) {
return InvokerHelper.createList(values);
}
InvokerHelper中的createList方法
public static List createList(Object[] values) {
List answer = new ArrayList(values.length);
answer.addAll(Arrays.asList(values));
return answer;
}
由上可知,Groovy的list类型就是java的list类型,而且默认使用的ArrayList类型。
一个list可存放多种类型数据,:
void testDiffType() {
def list = [1,'abc',true]
println(list.size())
}
上述的list默认都是ArrayList类型,可通过as标识符或明确指定类型来修改:
void test() {
def linkedList = [2, 3, 4] as LinkedList
assert linkedList instanceof java.util.LinkedList
LinkedList otherLinked = [3, 4, 5]
assert otherLinked instanceof java.util.LinkedList
}
list元素:与一般的脚本语言类似,可使用正或负下标来访问元素,通过<<符合往列表尾部添加元素,具体如下:
void testElementsOperation() {
def letters = ['a', 'b', 'c', 'd']
assert letters[0] == 'a'
assert letters[1] == 'b'
assert letters[-1] == 'd'
assert letters[-2] == 'c'
letters[2] = 'C'
assert letters[2] == 'C'
letters << 'e'
assert letters[4] == 'e'
assert letters[-1] == 'e'
assert letters[1, 3] == ['b', 'd']
assert letters[2..4] == ['C', 'd', 'e']
}
Groovy的数组用法除了需要显式定义,其余用法与list一样,如下示例:
void testArrays() {
// 直接定义为数组
String[] arrStr = ['one', 'second', '3']
println('arrStr len:' + arrStr.length)
// 使用as标识符指定为数组
def numArr = [1, 2, 3] as int[]
println('numArr len:' + numArr.length)
}
上述代码的字节码如下:
public void testArrays() {
CallSite[] var1 = $getCallSiteArray();
String[] arrStr = new String[]{"one", "second", "3"};
var1[2].callCurrent(this, var1[3].call("arrStr len:", var1[4].callGetProperty(arrStr)));
Object numArr = (int[])ScriptBytecodeAdapter.asType(ScriptBytecodeAdapter.createList(new Object[]{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3)}), int[].class);
var1[5].callCurrent(this, var1[6].call("numArr len:", var1[7].callGetProperty(numArr)));
}
上面as标识符代码:将ScriptBytecodeAdapter.createList创建出来的ArrayList与int[].class作为ScriptBytecodeAdapter.asType的参数,最终返回了int数组。
Groovy中Map的key是不限类型,基本用法如下:
void testMap() {
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
colors['pink'] = '#FF00FF'
colors.yellow = '#FFFF00'
println('size:' + colors.size() + ",blue:" + colors.blue)
}
当要获取Map的key时,需定义key这个变量,且定义map时添加圆括号,如下:
void testMapKey() {
def key = 'name'
def person = [key: 'Guillaume']
println("key:" + person.name)
person = [(key): 'Guillaume']
println("key:" + person.name)
}
结果:
key:null
key:Guillaume
上述key不加圆括号为null的原因是:person直接将key这字符串当作了map的key而不是将key的内容name作为key。