Groovy语法之操作符

时间:2022-04-29 17:50:33

算术运算

  1. Groovy的算术运算操作相比Java多了对幂运算的支持,用法如下:

    void testPowerOperators() {
    def power
    = 2 ** 5
    println(power)
    }
  2. 上述编译之后的字节码如下:

    public void testPowerOperators() {
    CallSite[] var1 = $getCallSiteArray();
    Object power = var1[2].call(Integer.valueOf(2), Integer.valueOf(5));
    var1[3].callCurrent(this, power);
    }
  3. 因无法从字节码文件上确定CallSite类型,无法给出上述幂运算实现细节的结论。

对象运算

  1. 相比Java,Groovy支持安全运算符(?.),可有效避免空指针异常,使用方法如下:

    void testSafeNavigationOperator() {
    Person person = null

    def name = person?.name

    // def name1 = person.name

    println('person:' + person + ',name:' + name)
    }
  2. 安全运算符保证了上述代码正常运行,而注释起来的代码在运行时会直接抛异常,看编译过后的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));
    }
  3. 从源码来看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);
    }
    }
  4. 上述两者区别就是callGroovyObjectGetPropertySafe比callGroovyObjectGetProperty多了判空操作。

  5. 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
  6. 注意,上述代码user.name实际调用的是user.getName方法,而user.@name才是调用name这个属性。

  7. Groovy支持方法指针操作符(.&),原理是通过变量来存放某个方法的指针,即可使用这个变量来调用这个方法,如下示例:

    void testMethodPointerOperator() {
    def str = 'example of method reference'
    def fun = str.&toUpperCase
    println(fun()) // EXAMPLE OF METHOD REFERENCE
    }
  8. 上述代码的字节码如下:

    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));
    }
  9. 继续追踪getMethodPointer方法:

    public static Closure getMethodPointer(Object object, String methodName) {
    return InvokerHelper.getMethodPointer(object, methodName);
    }
  10. 再看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);
    }
  11. 上述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']

正则表达式

  1. 相比java,groovy提供模式运算符(~)可方便创建java.util.regex.Pattern的示例,如下代码:

    void testPatternOperator() {
    def p = ~/test/
    println(p)
    }
  2. 对应的字节码如下:

    public void testPatternOperator() {
    CallSite[] var1 = $getCallSiteArray();
    Object p = ScriptBytecodeAdapter.bitwiseNegate("test");
    var1[2].callCurrent(this, p);
    }
  3. 来看bitwiseNegate的实现:

    public static Object bitwiseNegate(Object value) throws Throwable {
    try {
    return InvokerHelper.bitwiseNegate(value);
    } catch (GroovyRuntimeException gre) {
    throw unwrap(gre);
    }
    }
  4. 实现是在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);
    }
  5. 语法基础篇可知这是一个String类型,所以最终会调用StringGroovyMethods.bitwiseNegate方法,具体实现如下:

    public static Pattern bitwiseNegate(String self) {
    return bitwiseNegate((CharSequence) self);
    }

    public static Pattern bitwiseNegate(CharSequence self) {
    return Pattern.compile(self.toString());
    }
  6. 由上可得出结论:groovy的模式运算符最终会被翻译成Pattern来实现。

  7. 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
    }
  8. 匹配运算符(==~)与查找运算符的区别:对应的匹配结果是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())
    }

展开运算符

  1. 展开运算符(*.):继承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]
    }
  2. 上述展开运算符可以方便的获取到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));
    }
  3. 再看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;
    }
  4. 展开运算符不但能对列表中的属性进行展开,还能应用在方法参数、列表构造或者是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
    }

区间运算符

  1. 区间运算符(..):用来表示两个操作数之间的范围集合,分为闭区间运算符与半闭区间运算符,使用区别如下:

    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]
    }
  2. 上述编译之后的字节码如下:

    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);
    }
  3. 由上可知,闭区间运算符与半闭区间运算符的区别在于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);
    }
  4. 上述创建的是一个IntRange列表,它其实是AbstractList的子类,并实现了Range接口,而Range继承了List接口。

比较运算符

  1. 比较运算符:可以方便的比较变量的大小关系,如下例子:

    void testSpaceshipOperator() {
    println(123 <=> 123) // 0
    println('a' <=> 'e') // -1
    }
  2. 实现是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操作符

  1. 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))
    }
  2. 从下面的字节码可以看出: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操作符

  1. as操作符:即强转操作符,相比java的强转,会更加安全,如下:

    void testAsOperator() {
    Integer x = 123

    String castStr = (String) x
    println(castStr)

    String asStr = x as String
    println(asStr)
    }
  2. 两种转换的区别如下:

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