算术运算
Groovy的算术运算操作相比Java多了对幂运算的支持,用法如下:
void testPowerOperators() { def power = 2 ** 5 println(power) }
上述编译之后的字节码如下:
public void testPowerOperators() { CallSite[] var1 = $getCallSiteArray(); Object power = var1[2].call(Integer.valueOf(2), Integer.valueOf(5)); var1[3].callCurrent(this, power); }
因无法从字节码文件上确定CallSite类型,无法给出上述幂运算实现细节的结论。
对象运算
相比Java,Groovy支持安全运算符(?.),可有效避免空指针异常,使用方法如下:
void testSafeNavigationOperator() { Person person = null def name = person?.name // def name1 = person.name println('person:' + person + ',name:' + name) }
安全运算符保证了上述代码正常运行,而注释起来的代码在运行时会直接抛异常,看编译过后的class代码来比较它们的区别:
public void testSafeNavigationOperator() { CallSite[] var1 = $getCallSiteArray(); Person person = null; Object name = var1[2].callGroovyObjectGetPropertySafe(person); Object name1 = var1[3].callGroovyObjectGetProperty(person); var1[4].callCurrent(this, var1[5].call(var1[6].call(var1[7].call("person:", person), ",name:"), name)); }
从源码来看person?.name与person.name区别就在于前者调用callGroovyObjectGetPropertySafe方法,后者调用callGroovyObjectGetProperty方法。它们实现如下:
public final Object callGroovyObjectGetPropertySafe(Object receiver) throws Throwable { if (receiver == null) return null; else return callGroovyObjectGetProperty(receiver); }
public Object callGroovyObjectGetProperty(Object receiver) throws Throwable { if (receiver == null) { try { return InvokerHelper.getProperty(NullObject.getNullObject(), name); } catch (GroovyRuntimeException gre) { throw ScriptBytecodeAdapter.unwrap(gre); } } else { return acceptGroovyObjectGetProperty(receiver).getProperty(receiver); } }
上述两者区别就是callGroovyObjectGetPropertySafe比callGroovyObjectGetProperty多了判空操作。
Groovy支持直接访问属性,相比java的get/set方法,代码会更加简洁,如下例子:
class User { public final String name User(String name) { this.name = name } String getName() { "Name: $name" } } def user = new User('Bob') println(user.name) // Name: Bob println(user.@name) // Bob
注意,上述代码user.name实际调用的是user.getName方法,而user.@name才是调用name这个属性。
Groovy支持方法指针操作符(.&),原理是通过变量来存放某个方法的指针,即可使用这个变量来调用这个方法,如下示例:
void testMethodPointerOperator() { def str = 'example of method reference' def fun = str.&toUpperCase println(fun()) // EXAMPLE OF METHOD REFERENCE }
上述代码的字节码如下:
public void testMethodPointerOperator() { CallSite[] var1 = $getCallSiteArray(); Object str = "example of method reference"; Object fun = ScriptBytecodeAdapter.getMethodPointer(str, "toUpperCase"); var1[2].callCurrent(this, var1[3].call(fun)); }
继续追踪getMethodPointer方法:
public static Closure getMethodPointer(Object object, String methodName) { return InvokerHelper.getMethodPointer(object, methodName); }
再看InvokerHelper的getMethodPointer,发现这个方法指针返回的其实是MethodClosure这个类:
public static Closure getMethodPointer(Object object, String methodName) { if (object == null) { throw new NullPointerException("Cannot access method pointer for '" + methodName + "' on null object"); } return new MethodClosure(object, methodName); }
上述MethodClosure涉及到了Groovy中的闭包概念,简单理解是将一个方法作为另一个方法的参数传递,具体用法如下:
def transform(List elements, Closure action) { def result = [] elements.each { result << action(it) } result } String describe(Person p) { "$p.name is $p.age" } // 定义了方法指针,本质是Closure的子类 def action = this.&describe def list = [new Person(name: 'Bob', age: 42), new Person(name: 'Julia', age: 35)] //action作为参数传递 assert transform(list, action) == ['Bob is 42', 'Julia is 35']
正则表达式
相比java,groovy提供模式运算符(~)可方便创建java.util.regex.Pattern的示例,如下代码:
void testPatternOperator() { def p = ~/test/ println(p) }
对应的字节码如下:
public void testPatternOperator() { CallSite[] var1 = $getCallSiteArray(); Object p = ScriptBytecodeAdapter.bitwiseNegate("test"); var1[2].callCurrent(this, p); }
来看bitwiseNegate的实现:
public static Object bitwiseNegate(Object value) throws Throwable { try { return InvokerHelper.bitwiseNegate(value); } catch (GroovyRuntimeException gre) { throw unwrap(gre); } }
实现是在InvokerHelper的bitwiseNegate方法中:
public static Object bitwiseNegate(Object value) { if (value instanceof Integer) { Integer number = (Integer) value; return ~number; } if (value instanceof Long) { Long number = (Long) value; return ~number; } if (value instanceof BigInteger) { return ((BigInteger) value).not(); } if (value instanceof String) { // value is a regular expression. return StringGroovyMethods.bitwiseNegate(value.toString()); } if (value instanceof GString) { // value is a regular expression. return StringGroovyMethods.bitwiseNegate(value.toString()); } if (value instanceof ArrayList) { // value is a list. List newlist = new ArrayList(); Iterator it = ((ArrayList) value).iterator(); for (; it.hasNext();) { newlist.add(bitwiseNegate(it.next())); } return newlist; } return invokeMethod(value, "bitwiseNegate", EMPTY_ARGS); }
从语法基础篇可知这是一个String类型,所以最终会调用StringGroovyMethods.bitwiseNegate方法,具体实现如下:
public static Pattern bitwiseNegate(String self) { return bitwiseNegate((CharSequence) self); } public static Pattern bitwiseNegate(CharSequence self) { return Pattern.compile(self.toString()); }
由上可得出结论:groovy的模式运算符最终会被翻译成Pattern来实现。
Groovy提供查找运算符(=~),对应的是java.util.regex.Matcher,最终是通过Pattern的matcher来实现,此处不再展开,例子如下:
void testFindOperator() { def text = "some text to match" def m = text =~ /match/ if (!m.find()) { throw new RuntimeException("Oops, text not found!") } println(m.getClass())//class java.util.regex.Matcher }
匹配运算符(==~)与查找运算符的区别:对应的匹配结果是Boolean类型,使用如下:
void testMatchOperator() { def text = "some text to match" def m = text ==~ /match/ if (m) { throw new RuntimeException("Should not reach that point!") } println(m.getClass()) }
展开运算符
展开运算符(*.):继承Iterable接口的类都可使用展开运算符,作用是扩展列表的操作,底层是通过迭代来完成展开。如下例子:
void testSpreadOperator() { def persons = [new Person('one', 1), new Person('ten', 10)] def nameList = persons*.name println('persons:' + persons + ',nameList:' + nameList) // persons:[name=one, age=1, name=ten, age=10],nameList:[one, ten] }
上述展开运算符可以方便的获取到person中的属性列表,看字节码实现:
public void testSpreadOperator() { CallSite[] var1 = $getCallSiteArray(); Object persons = ScriptBytecodeAdapter.createList(new Object[]{var1[2].callConstructor(Person.class, "one", Integer.valueOf(1)), var1[3].callConstructor(Person.class, "ten", Integer.valueOf(10))}); Object nameList = ScriptBytecodeAdapter.getPropertySpreadSafe(Operators.class, persons, (String)"name"); var1[4].callCurrent(this, var1[5].call(var1[6].call(var1[7].call("persons:", persons), ",nameList:"), nameList)); }
再看ScriptBytecodeAdapter.getPropertySpreadSafe实现,最终也是借助于Iterator来实现:
public static Object getPropertySpreadSafe(Class senderClass, Object receiver, String messageName) throws Throwable { if (receiver == null) return null; List answer = new ArrayList(); for (Iterator it = InvokerHelper.asIterator(receiver); it.hasNext();) { answer.add(getPropertySafe(senderClass, it.next(), messageName)); } return answer; }
展开运算符不但能对列表中的属性进行展开,还能应用在方法参数、列表构造或者是map构造中,如下例子:
void testSpreadOperator() { def rawAges = [12, 13] println(getTotalAge(*rawAges)) def newAges = [10, 11, *rawAges] println(getTotalAge(*newAges)) def m1 = [c:3, d:4] def map = [a:1, b:2, *:m1] println(map) } static int getTotalAge(int ... args) { int total = 0 for (int i = 0; i < args.length; i++) { total += args[i] } return total }
区间运算符
区间运算符(..):用来表示两个操作数之间的范围集合,分为闭区间运算符与半闭区间运算符,使用区别如下:
void testRangeOperator() { //闭区间运算符 def range = 0..5 println(range) // [0, 1, 2, 3, 4, 5] //半闭区间运算符 def rangeExclusive = 0..<5 println(rangeExclusive) // [0, 1, 2, 3, 4] }
上述编译之后的字节码如下:
public void testRangeOperator() { CallSite[] var1 = $getCallSiteArray(); Object range = ScriptBytecodeAdapter.createRange(Integer.valueOf(0), Integer.valueOf(5), (boolean)1); var1[2].callCurrent(this, range); Object rangeExclusive = ScriptBytecodeAdapter.createRange(Integer.valueOf(0), Integer.valueOf(5), (boolean)0); var1[3].callCurrent(this, rangeExclusive); }
由上可知,闭区间运算符与半闭区间运算符的区别在于ScriptBytecodeAdapter.createRange方法的最后一个参数值不同,如下代码:
public static List createRange(Object from, Object to, boolean inclusive) throws Throwable { if (from instanceof Integer && to instanceof Integer) { int ifrom = (Integer) from; int ito = (Integer) to; if (inclusive || ifrom != ito) { return new IntRange(inclusive, ifrom, ito); } // else fall through for EmptyRange } if (!inclusive) { if (compareEqual(from, to)) { return new EmptyRange((Comparable) from); } if (compareGreaterThan(from, to)) { to = invokeMethod0(ScriptBytecodeAdapter.class, to, "next"); } else { to = invokeMethod0(ScriptBytecodeAdapter.class, to, "previous"); } } return new ObjectRange((Comparable) from, (Comparable) to); }
上述创建的是一个IntRange列表,它其实是AbstractList的子类,并实现了Range接口,而Range继承了List接口。
比较运算符
比较运算符:可以方便的比较变量的大小关系,如下例子:
void testSpaceshipOperator() { println(123 <=> 123) // 0 println('a' <=> 'e') // -1 }
实现是compareTo,如下字节码:
public void testSpaceshipOperator() { CallSite[] var1 = $getCallSiteArray(); if (BytecodeInterface8.isOrigInt() && !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()) { var1[3].callCurrent(this, ScriptBytecodeAdapter.compareTo(Integer.valueOf(123), Integer.valueOf(123))); } else { var1[2].callCurrent(this, ScriptBytecodeAdapter.compareTo(Integer.valueOf(123), Integer.valueOf(123))); } var1[4].callCurrent(this, ScriptBytecodeAdapter.compareTo("a", "e")); }
in操作符
in操作符:与isCase 方法等效;而对于list,则与list.contains或 list.isCase等效,使用如下:
void testInOperator() { def list = [1,2,3,4] println(1 in list) println(list.isCase(1)) println(list.contains(1)) }
从下面的字节码可以看出:isCase与contains实现是一样,而in操作符会被转成isCase方法,所以三者实现是一致的:
public void testInOperator() { CallSite[] var1 = $getCallSiteArray(); Object list = ScriptBytecodeAdapter.createList(new Object[]{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4)}); var1[2].callCurrent(this, ScriptBytecodeAdapter.isCase(Integer.valueOf(1), list)); var1[3].callCurrent(this, var1[4].call(list, Integer.valueOf(1))); var1[5].callCurrent(this, var1[6].call(list, Integer.valueOf(1))); }
as操作符
as操作符:即强转操作符,相比java的强转,会更加安全,如下:
void testAsOperator() { Integer x = 123 String castStr = (String) x println(castStr) String asStr = x as String println(asStr) }
两种转换的区别如下:
public void testAsOperator() { CallSite[] var1 = $getCallSiteArray(); Integer x = Integer.valueOf(123); String castStr = (String)ShortTypeHandling.castToString(x); var1[2].callCurrent(this, castStr); String asStr = (String)ScriptBytecodeAdapter.asType(x, String.class); var1[3].callCurrent(this, asStr); }