1. Groovy安装

<dependency>
    <groupId>org.apache.groovy</groupId>
    <artifactId>groovy-bom</artifactId>
    <version>4.0.17</version>
    <scope>import</scope>
    <type>pom</type>
</dependency>

2. Default Import

  • java.io.*;

  • java.lang.*;

  • java.math.BigDecimal;

  • java.math.BigInteger;

  • java.net.*;

  • java.util.*;

  • groovy.lang.*;

  • groovy.util.*;

3. Multi Method

  • groovy中,被调用的方法是在运行时选择,称为runtime dispatch
    或multi-method,即将在运行时根据参数类型来选择方法;

  • 而在Java正好想法,方法是编译时根据声明的类型选择,
    如下所示,用Java编写,用Java和Groovy编译:

int method(String arg) {
    return 1;
}
int method(Object arg) {
    return 2;
}

Object o = "Object";
int result = method(o);

# Java
assertEquals(2, result);

# Groovy:
assertEquals(1, result);

# 因Java将使用静态信息类型,即o被声明为Object,
# 而Groovy在运行时选择,用String调用故选择String版本;

4. Array Initializer

  • Java中,数组初始化有如下两种形式:

int[] shorthand = {1, 2, 3};

int[] longhand = new int[] {4, 5, 6};
  • 而Groovy中,{……}块是闭包(closure)保留的,故不能使用shorthand方式创建;

int[] groovy = [1, 2, 3]

def groovy3Plus = new int[] {1, 2, 3}

5. Package Scope Visibility

  • groovy中,声明属性可省略修饰符,这是私有字段,有对应的getter和setter;

class Person {
    String name
}
  • 可使用@PackageScope来声明包字段,

class Person {
    @PackageScope String name
}

6. ARM Block

  • groovy 3+ 提供java的arm twr语法,
    也提供各种依赖于闭包的方法,相同效果更实用;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

public class Arm{

	public static void arm() {
		Path file = Path.of("/path/to/file");
		Charset charset = Charset.forName("UTF-8");
		try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
			String line;
			while ((line = reader.readLine()) != null) {
				System.out.println(line);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
new File('filePath').eachLine('UTF-8') {
   println it
}

new File('filePath').eachLine('UTF-8',){reader ->
	reader.eachLine {
		println it
	}
}

7. Inner Class

  • 匿名内部类和嵌套内部类类似于Java,但从这些类中访问局部变量不一定是final,
    在生成内部类字节码时,借鉴groovy.lang.Closure的实现细节;

  • 静态内部类最受支持:

class A {
    static class B {}
}

new A.B()
  • 匿名(Anonymous)内部类:

import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

CountDownLatch called = new CountDownLatch(1)

Timer timer = new Timer()
timer.schedule(new TimerTask() {
	void run() {
		called.countDown()
	}
}, 0)

assert called.await(10, TimeUnit.SECONDS)

8. Lambda Method Reference

Runnable run = () -> System.out.println("Run");
list.forEach(System.out::println);
Runnable run = { println 'run' }
list.each { println it }
list.each(this.println())

9. GString

  • 双引号字符串被解释为GString值,若使用Groovy和
    Java编译器编译具有含$字符的Str类,Groovy可能编译失败;

  • 虽通常若Api声明参数类型,Groovy将在GString和String间自动转换,
    但注意接受Object参数,然后检查实际类型的Java Api;

10. String and Character

  • Groovy中单引号用于String,双引号用于String或GString,
    取决于字符串中是否有插值(interpolation);

assert 'c'.class == String
assert "c".class == String
assert "c${1}".class in GString
  • 只有当赋值给char类型变量时,groovy才会自动将单字符String强制转换为char,
    当使用char类型参数调用方法时,需显示强制转换,或确保值已提前强制转换;

char a = 'a'
assert Character.digit(a, 16) == 10: 'But Groovy does boxing'
assert Character.digit((char) 'a', 16) == 10

try {
	assert Character.digit('a', 16) == 10
	assert false: 'Need explicit cast'
} 	catch(MissingMethodException e) {

}
  • groovy支持单char和多char强制转换,两者存在细微差异,
    Groovy样式强制转换更宽松(lenient),
    并采用第一个字符,而C-style强制类型转换则会失败;

// for single char strings, both are the same
assert ((char) "c").class == Character
assert ("c" as char).class == Character

// for multi char strings they are not
try {
  ((char) 'cx') == 'c'
  assert false: 'will fail - not castable'
} catch(GroovyCastException e) {
}
assert ('cx' as char) == 'c'
assert 'cx'.asType(char) == 'c'

11. Behaviour of

  • Java中==表示对象的基本类型(primitive)或标识(identity)相等,
    groovy中==表示所有地方相等(equality);

  • 对非基本数据类型,当计算可比较对象的相等性时(evaluating equality),
    它转换为a.compareTo(b) == 0,否则转换为a.equals(b);

  • 要检查标识(引用相等)(identity (reference equality)),
    请使用is():a.is(b),或使用全等式,a === b,c !== d;

12. Primitive and Wrapper

  • Java中支持基本数据类型和包装类自动装箱和拆箱,

public class Main {

   float f1 = 1.0f;
   Float f2 = 2.0f;

   float add(Float a1, float a2) { return a1 + a2; }

   Float calc() { return add(f1, f2); }

    public static void main(String[] args) {
       Float calcResult = new Main().calc();
       System.out.println(calcResult);
    }
}
class Main {

    float f1 = 1.0f
    Float f2 = 2.0f

    float add(Float a1, float a2) { a1 + a2 }

    Float calc() { add(f1, f2) }
}

assert new Main().calc() == 3.0
  • groovy也支持基本数据类型和对象类型,单在推动OO纯粹性(purity)方面更进一步,
    视一切皆对象,任何基本数据类型变量或属性都可像对象一样处理,并将按需自动包装,

  • 虽基本数据类型可能被覆盖(under the cover),但只要可能,
    基本类型的使用与正常对象无法区分(indistinguishable),按需装箱或拆箱;

public class Main {
    public float z1 = 0.0f;

    public static void main(String[] args){
    	# 编译失败
      	new Main().z1.equals(1.0f);
    }
}
class Main {
    float z1 = 0.0f
}
assert !(new Main().z1.equals(1.0f))
int i
m(i)

void m(long l) {
    println "in m(long)"
}

void m(Integer i) {
    println "in m(Integer)"
}
  • 使用@CompileStatic对数值型基本数据类型优化;

  • 正负零边缘,参考 IEEE754标准;

  • 在groovy中,若希望区分正零和负零,请直接使用equals()或使用
    ==前将基本数据类型转换为包装等效(wrapper equivalent);

  • 若要忽略正零和负零的差异,请使用equalsIgnoreZeroSign(),
    或使用==前将任何非基本数据类型转换为等效基本类型;

# Primitive Type
jshell> float f1 = 0.0f
f1 ==> 0.0

jshell> float f2 = -0.0f
f2 ==> -0.0

jshell> f1 == f2
$3 ==> true

# Wrapper Class
jshell> Float f1 = 0.0f
f1 ==> 0.0

jshell> Float f2 = -0.0f
f2 ==> -0.0

jshell> f1.equals(f2)
$3 ==> false
float f1 = 0.0f
float f2 = -0.0f
Float f3 = 0.0f
Float f4 = -0.0f

assert f1 == f2
assert (Float) f1 != (Float) f2

assert f3 != f4
assert (float) f3 == (float) f4

assert !f1.equals(f2)
assert !f3.equals(f4)

assert f1.equalsIgnoreZeroSign(f2)
assert f3.equalsIgnoreZeroSign(f4)

13. Extra Keyword

  • as,def,in,trait,it(within closure);

  • Groovy没Java严格,但在可能混淆场景,避免用于变量,方法和类名,
    如var - var是糟糕的样式;具体示例如下:

var var = [def:1,as:2,in:3,trait:4]