您的位置:首页 > 其它

Groovy语法基础

2017-08-09 16:09 218 查看

Groovy与Java

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环境之后,输入groovy命令时,会有如下的工具:



groovyc:groovy编译器,类似于javac,将groovy脚本编译成class字节码文件

groovy:用于运行groovy脚本

groovyConsole:Groovy官方提供的一个简易IDE,如下图



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

119f1
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;
}


上述代码因为参数传递过来的是new Object[0],所以是直接new了一个LinkedHashMap实例就返回了。回到testQuotedIdentifiers中可以看到其实前面7种引用方式本质是一样,只有闭包引用方式有区别,但是最终都是ScriptBytecodeAdapter的setProperty方法完成map的添加操作。

字符串

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);
}


单引号字符串(包括双引号字符串)的限制是只能单行来使用,这与Groovy的语法特性有关。

三引号字符串:分为三单引号字符串和三双引号字符串,目的是为了解决单行限制,如下三单引号字符串代码:

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类型

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']
}


Arrays类型

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数组。

Map类型

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: