0%

Groovy 3之新特性预览

背景介绍

Groovy面世已有10多年光阴,在不断创新的同时还汲取了不少其他语言的优秀特性。目前Groovy核心团队已开始将重心逐渐转移到Groovy 3的研发上,而Groovy 3的主要亮点之一便是其全新的解析器以及更加丰富的语言特性。由于前期为Groovy研发了这一全新的解析器并添加了不少语言特性,有幸受邀成为Groovy核心团队的一员。最初我将该全新的解析器命名为“Parrot”,其含义相对比较谦卑,即“鹦鹉学舌”,学着如何说Groovy以及Java8,通过数月陆陆续续地完善,已达到一定的成熟度并已被纳入master分支

接下来的内容将主要介绍一下这些新特性。可在Groovy官方网站下载Groovy 3尝鲜。

注: 在Apache Groovy 3.0.0的Release Note中对该解析器有较为全面地介绍:http://groovy-lang.org/releasenotes/groovy-3.0.html

新特性预览

1. 完善循环语句

在Groovy 3之前,Groovy既不支持do-while语句,也不支持真正符合Java语法规范的for语句(比如:多个初始化以及多个更新表达式)。在Groovy 3中,对循环语句做了完整的支持,令Java背景的开发人员可以更加平缓地过度到Groovy开发。举些例子:

do-while语句示例

int i = 0;

do {
i++
} while (i < 5)

assert i == 5

符合Java语法规范的for语句示例

def result = 0
for (int i = 0, j = 0; i < 5 && j < 5; i = i + 2, j++) {
result += i;
result += j;
}
assert 9 == result

2. 支持Lambda表达式

Java 8引入了Lambda表达式,为了更好地兼容Java的语法,Groovy 3也新增了对该语法特性的支持。举些例子:

Lambda表达式示例

assert 9 == [1, 2, 3].stream().map(e -> e + 1).reduce(0, (r, e) -> r + e)

3. 支持方法引用(method reference)和构造器引用(constructor reference)

Java 8除了引入了Lambda表达式,还引入了方法引用(method reference)和构造器引用(constructor reference),基于同样的原因,Groovy 3也新增了对该语法特性的支持。举些例子:

方法引用(method reference)示例

assert ['A', 'B', 'C'] == ['a', 'b', 'c'].stream().map(String::toUpperCase).collect(Collectors.toList())

构造器引用(constructor reference)示例

assert [1, 2, 3] as String[] == [1, 2, 3].stream().map(String::valueOf).toArray(String[]::new)

4. 支持try-with-resources语句

Java 7引入了自动资源管理机制,开发人员可以通过try-with-resources语句很方便地完成资源的管理。在Groovy 3中也对其进行了支持。举些例子:

try-with-resources语句示例

class Resource implements Closeable {
int resourceId;
static closedResourceIds = [];

public Resource(int resourceId) {
this.resourceId = resourceId;
}

public void close() {
closedResourceIds << resourceId
}
}

def a = 1;
try (Resource r1 = new Resource(1)) {
a = 2;
}
assert Resource.closedResourceIds == [1]
assert 2 == a

5. 支持代码块

在Java中有一种不常用但比较有用的语法可以方便地隔离变量的作用范围,即代码块。Groovy 3也对其进行了支持。举些例子:

代码块示例

{
def a = 1
a++
assert 2 == a
}
{
def a = 1
assert 1 == a
}

6. 支持Java风格的数组初始化

为了更好地兼容Java语法,Groovy 3新增了对Java风格的数组初始化的支持。

数组初始化示例

def a = new int[] {1, 2}
assert a[0] == 1
assert a[1] == 2
assert a as List == [1, 2]

7. 支持interface的默认方法(default method)

在Java 8引入的众多新特性中,interface的默认方法(default method)也是十分有用的一个特性。Groovy 3自然也不会遗漏对其进行支持。举些例子:

默认方法(default method)示例

interface Greetable {
String name();
default String hello() {
return 'hello'
}
default public String sayHello() {
return this.hello() + ', ' + this.name()
}
}

class Person implements Greetable {
@Override
public String name() {
return 'Daniel'
}
}

def p = new Person()
assert 'hello, Daniel' == "${p.hello()}, ${p.name()}"
assert 'hello, Daniel' == p.sayHello()

8. 新增操作符:一致性操作符(===!==)、Elvis赋值(?=)、!in!instanceof

为了令Groovy程序更加简洁,Groovy 3引入了这些新的操作符。举些例子:

一致性操作符(===!==)示例

assert 'abc' === 'abc'
assert 'abc' !== new String('abc')

Elvis赋值(?=)示例

def a = 2
a ?= 1
assert a == 2

a = null
a ?= 1
assert a == 1

!in示例

assert 1 !in [2, 3, 4]

!instanceof示例

assert 1 !instanceof String

9. 支持安全检索

Groovy对null有一种安全的操作方式,即通过?.进行引用,这样可以避免NullPointerException的发生,但缺少对集合和数组的安全检索的支持,在Groovy 3弥补了这点缺憾。举些例子:

安全检索示例

String[] array = ['a', 'b'];
assert 'b' == array?[1];

array?[1] = 'c';
assert 'c' == array?[1];

array = null;
assert null == array?[1];

array?[1] = 'c';
assert null == array?[1];

10. 支持运行时Groovydoc以及将Groovydoc作为元数据保存于AST节点中

Groovy 3新增了运行时Groovydoc,即可在运行时访问该Groovydoc内容,与传统Groovydoc的差别在于运行时Groovydoc以/**@开头(详见以下示例)。如果说Javadoc伴随Java源码是对文档重视的一种体现,那么运行时Groovydoc则更进一步,它保存于bytecode中,与程序做到了真正的结合。

注:该特性需将groovy.attach.runtime.groovydoc设置为true,即-Dgroovy.attach.runtime.groovydoc=true,方可启用。

运行时Groovydoc示例

/**@
* class AA
*/
class AA {
/**@
* field SOME_FIELD
*/
public static final int SOME_FIELD = 1;

/**@
* constructor AA
*/
public AA() {
}

/**@
* method m
*/
public void m() {
}

/**@
* class InnerClass
*/
class InnerClass {
}
}

/**@
* annotation BB
*/
@interface BB {
}

assert AA.class.getAnnotation(groovy.lang.Groovydoc).value().contains('class AA')
assert AA.class.getMethod('m', new Class[0]).getAnnotation(groovy.lang.Groovydoc).value().contains('method m')
assert AA.class.getConstructor().getAnnotation(groovy.lang.Groovydoc).value().contains('constructor AA')
assert AA.class.getField('SOME_FIELD').getAnnotation(groovy.lang.Groovydoc).value().contains('field SOME_FIELD')
assert AA.class.getDeclaredClasses().find {it.simpleName.contains('InnerClass')}.getAnnotation(groovy.lang.Groovydoc).value().contains('class InnerClass')
assert BB.class.getAnnotation(groovy.lang.Groovydoc).value().contains('annotation BB')

将Groovydoc作为元数据保存于AST节点中的示例

注:该特性需将groovy.attach.groovydoc设置为true,即-Dgroovy.attach.groovydoc=true,方可启用。

import org.codehaus.groovy.control.*
import static org.apache.groovy.parser.antlr4.GroovydocManager.DOC_COMMENT

def code = '''
/**
* Groovydoc for hello method
* @author Daniel Sun
*/
void hello(String name) {}
'''
def ast = new CompilationUnit().tap {
addSource 'hello.groovy', code
compile Phases.SEMANTIC_ANALYSIS
}.ast

assert ast.classes[0].methods.grep(e -> e.name == 'hello')[0].nodeMetaData[DOC_COMMENT].contains('Groovydoc for hello method')

结束语

仿照Donald Trump的”Make America Great Again”,我希望Groovy 3可以”Make Groovy Great Again”