重要要点
- Groovy 3试图弥补Java最新版本中出现的某些功能缺陷
- 新的控制流程功能包括do-while和增强的for循环
- 添加了Java样式的Lambda表达式,使其与Groovy闭包并排放置
- 自动资源管理(尝试资源)也到了
- 纠正了一些遗漏(例如集合索引中的?),从而增强了Groovy的运算符集
Apache Groovy是一种在Java虚拟机(JVM)上运行的开源,面向对象的语言。 Groovy与Java语法兼容,并且在某种程度上比Java更强大,因为它是动态和静态类型的(使用def关键字)。 Groovy既是编程语言又是脚本语言。 Java中没有的Groovy功能包括对领域特定语言(DSL)和元编程的支持。
问题
即使Groovy 2.x与Java语法兼容并且可以编译为JVM字节码,它也不支持Java所具有的某些功能。 Java代码不能直接完全集成到Groovy 2.x代码中。 例如,不支持方法引用和lambda表达式,需要将其转换为Groovy闭包。
解
Groovy 3增添了Java的一些新功能,使Groovy与Java更具可集成性和互操作性。
受Java样式影响的新Groovy 3功能包括:控制流do-while循环,增强的for循环,支持逗号分隔的表达式和多赋值语句,使用大括号{}进行数组初始化,lambda表达式,方法引用, try-with-resources语句,匿名代码块,非静态内部类实例化和接口方法的默认实现。 在本文中,我们将讨论Groovy 3.0.0中的这些和其他新功能。
搭建环境
下载并安装以下软件:
- Groovy 3.0.0
- JDK 8(支持的最低版本)
同时执行控制流语句
Java中的do-while语句在表达式为true且具有以下语法的情况下评估do块中的语句。
do {
statement/s
} while (an_expression);
do块中的语句至少运行一次,并且每次运行后都会对表达式an_expression进行求值。
Groovy 3.0.0增加了对do-while语句的支持。 在Groovy中使用do-while语句的示例如下,其中输出x的值,并使用do块中的后缀增量运算符对x的值进行递增。 while中的表达式评估x是否小于或等于10 。
创建一个Groovy脚本(hello.groovy),然后将以下列表复制到该脚本中。
def x = 5;
do{
println("x is " + x);
x++;
} while (x <= 10);
使用groovy hello.groovy命令运行脚本,并输出与Java相同的输出,如下所示:
x is 5
x is 6
x is 7
x is 8
x is 9
x is 10
改进的循环
Groovy 2.x中已经存在for语句,但是它不支持与Java中完全相同的语法。 为了解决这个问题,Groovy 3.0.0在for语句的初始化和增量表达式中增加了对多个逗号分隔的表达式的支持。 例如:
for(int i = 1,j = 5; i < 6; i++,j--){
println("i is: " + i);
println("j is: " + j);
}
运行前面的Groovy脚本,并生成与Java中相同的输出。
i is: 1
j is: 5
i is: 2
j is: 4
i is: 3
j is: 3
i is: 4
j is: 2
i is: 5
j is: 1
从1.6版开始,Groovy就支持多分配。 Groovy 3.0.0扩展了此支持,并在for循环中添加了多分配语句,如以下脚本所示:
def xy = []
for (def (String x, int y) = ['groovw', 1]; y < 4; x++, y++) {
xy << "$x $y"
}
println xy
如果运行前面的脚本,则会生成以下输出。
[groovw 1, groovx 2, groovy 3]
用{}进行数组初始化
Groovy一直在数组初始化中省略对Java样式大括号{}的支持,因为它可能导致与闭包语法混淆,闭包语法也使用了大括号。
但是,闭包语法确实与数组初始化语法不同,在数组初始化语法中,花括号遵循数组类型声明。 考虑下面的Java数组初始化表达式。
String[] strArray=new String[] {"A","B","C"};
Groovy 3.0.0增加了对数组初始化语法的支持,其中花括号遵循数组类型声明,如以下数组初始化示例所示。
def strArray=new String[] {"A","B","C"}
println strArray.size()
println strArray[0]
运行前面的Groovy脚本以生成以下输出:
3
A
Lambda表达式
Java在JDK 8中添加了对lambda表达式的支持,以替代匿名类,以实现声明单个方法的接口,例如java.lang.Runnable接口,该接口声明单个方法run() 。 lambda表达式的语法由一个参数列表,一个箭头,一个语句块或表达式组成,例如:
Runnable r2 = () -> System.out.println("Hello from Lambda Expression");
要么:
Runnable r2 = () -> {}
可以按以下方式调用run()方法:
r2.run();
在Groovy 2.x中,Java样式的lambda表达式转换为闭包。 闭包缺少相同级别的类型推断和类型检查,因此缺少lambda表达式提供的相同级别的性能。 Groovy 3.0.0增加了对lambda表达式的支持。 作为示例,请考虑以下Groovy脚本:
def r2 = () -> System.out.println("Hello from Lambda Expression");
r2.run();
运行Groovy脚本以生成与Java相同的输出,如下所示:
Hello from Lambda Expression
考虑在Groovy中使用闭包的以下语句。
def multiply = { x, y ->
return x*y
}
println multiply(2, 3)
运行Groovy脚本时,输出为6 。
在Groovy 3.0.0中,闭包可以用lambda表达式代替,如以下示例所示:
def multiply = (int x, int y) -> { return x*y }
println multiply(2, 3)
如果未指定大括号(如下所示),则将计算箭头->后的表达式并从lambda表达式返回,如下所示:
def multiply = (int x, int y) -> x*y
参数类型在参数列表中是可选的,如下所示:
def multiply = (x,y) -> x*y
如果在->大括号{}之后使用了一个或多个语句,则如下所示:
def乘法=(x,y)-> {z = x * y; println z}
Groovy语法允许在参数列表中使用默认值,在Java中没有默认值,例如:
def multiply = (x,y=1) -> x*y
与Java中一样,没有类型的单个参数不需要括号。
def hello = x -> "hello "+x
方法参考
Java中的方法参考提供了一种命名方法的简化语法,替代了使用lambda表达式创建匿名方法的方法。 方法引用使用::运算符。 例如,考虑以下参数化List的声明。
List<String> list = Arrays.asList("Hello", " Deepak", " and"," Hello"," John");
以下语句利用lambda表达式遍历列表并输出String值。
list.forEach((String s)->System.out.println(s));
前面语句的输出如下:
Hello
Deepak
and
Hello
John
方法参考可以用于替换lambda表达式以简化语法。
list.forEach(System.out::println);
在Groovy 2.xa中,方法参考转换为方法闭包。 Groovy 3.0.0添加了对方法引用的支持。 相同的方法参考示例可以作为Groovy脚本运行以生成相同的输出。
在前面的示例中,实例方法是通过方法引用来调用的。 作为调用实例方法的方法引用的另一个示例,将String值列表转换为大写。 以下Groovy片段使用方法引用来调用String类中的toUpperCase实例方法。
import java.util.stream.Collectors;
def list = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
def upperCaseList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
println upperCaseList
运行前面的Groovy脚本以生成以下输出:
[A, B, C, D, E, F, G, H, I, J]
作为使用方法引用来调用静态方法的示例,请考虑以下Groovy脚本,其中使用Integer类中的静态方法valueOf(String)将字符串值列表转换为整数列表。 过滤器用于仅包含奇数整数。
import java.util.stream.Collectors;
def list = Arrays.asList("1","2","3","4","5","6","7","8","9","10");
def subList = list.stream().map(Integer::valueOf).filter(number -> number % 2 == 1).collect(Collectors.toList());
println subList
运行Groovy脚本以输出奇数整数值:
[1, 3, 5, 7, 9]
匿名代码块
Java中的匿名代码块是括在花括号{}中的一个语句或一组语句。 例如,以下是一个匿名代码块。
{
int x = 5;
x--;
System.out.println(x);
}
如果在JShell中运行,则代码块输出4 。 Java中的匿名代码块通常用于限制变量范围。 例如,在前面的代码块之后运行以下try-catch语句。
try {
x++;
} catch(Exception ex) {
System.out.println(ex.getMessage());
}
生成一条错误消息,指示未定义x 。
cannot find symbol
| symbol: variable x
| x++;
| ^
Groovy 3.0.0增加了对匿名代码块的支持。 以下Groovy脚本定义了一个输出4的匿名代码块。
{
def x = 5
x--
println(x)
}
x的范围仅在代码块内。 为了演示,请在前面的代码块之后运行以下try-catch语句。
try {
x++
} catch(Exception ex) {
println ex.message
}
将显示一条错误消息,指示未定义x 。
No such property: x for class: hello
嵌套的匿名代码块定义了另一个作用域,如以下Groovy脚本中所述,该脚本声明了两个称为“ a”的变量,每个变量都位于不同的块中。
{
{
def a = "Groovy"
def size= a.length()
println size
}
int a = 1
println a
}
运行脚本时,将输出两个块中的“ a”值。
6
1
如果在Groovy中的方法调用之后定义了代码块,则将其视为将闭包作为方法调用的最后一个参数传递。 例如,定义一个方法,该方法定义Object类型的vararg参数并输出传递给该方法的args数。 调用该方法,然后定义一个匿名代码块。
def hello(Object... args) {println args.length }
hello(1,2)
{5}
前面的Groovy脚本的输出可能是2,但实际上是3 。 要不将匿名代码块作为最后一个参数传递给方法调用,请添加“;” 在方法调用之后,如下所示:
def hello(Object... args) {println args.length }
hello(1,2);
{5}
非静态内部类实例化
Java在外部类的实例上使用new运算符支持非静态内部类实例化。 例如,声明一个类Hello并声明一个内部类Groovy 。
public class Hello{
public class Groovy{
int x;
public Groovy(int x){
this.x=x;
}
}
}
接下来,创建外部类Hello的实例,并实例化内部类,如下所示。
int y=new Hello().new Groovy(5).x;
输出y的值。
System.out.println(y);
在JShell中运行前面的内部类实例化示例,并输出值5 。
Groovy 3.0.0增加了对非静态内部类实例化的支持。 例如,创建一个Groovy脚本,该脚本在外部类Hello中声明一个内部类Hello2 。 内部类声明一个实例方法,该方法输出一条消息“ Hello Groovy”。 外部类声明一个静态方法,该方法将外部类的实例作为参数并返回内部类的实例。 内部类实例化使用new运算符演示。 main方法使用外部类的实例调用外部类中的静态方法,而内部类方法则在返回的内部类实例上被调用。 列出了Groovy脚本( hello.groovy ):
public class Hello {
public class Hello2 {
public void hello() {
println "Hello Groovy"
}
}
public static Hello2 createHello2(Hello y) {
return y.new Hello2();
}
static void main(String... args) {
Hello.createHello2(new Hello()).hello()
}
}
使用以下输出运行Groovy脚本。
Hello Groovy
接口中的默认方法实现
在JDK 8中添加了接口方法功能的默认方法实现,从而可以在不破坏与旧版本接口二进制兼容性的情况下向接口添加新功能。 如果在接口中没有默认方法实现,则如果将新功能添加到接口中,则会破坏与实现接口旧版本的类的二进制兼容性。 Groovy 2.x将接口默认方法实现为特征。 Groovy 3.0.0添加了对接口中默认方法实现的实验性支持。 作为一个示例,声明一个接口,其中包括方法的默认实现,并声明一个实现该接口的类HelloImpl 。 将以下清单复制到Groovy脚本( hello.groovy )。
class HelloImpl implements Hello {
static void main(String... args) {
def hello=new HelloImpl()
hello.hello("Deepak")
}
}
interface Hello {
default void hello(String name){
println "Hello " +name
}
}
运行Groovy脚本以生成输出:
Hello Deepak
资源试一试
Java支持try-with-resources语句,作为try语句的一种变体,用于声明一个或多个资源,并且该资源是在程序运行后需要关闭的对象。 资源的一个示例是JDBC 语句对象。 Groovy 3.0增加了对try-with-resouces的支持 。 例如,以下Groovy脚本使用try-with-resources语句来声明Statement资源。
import java.sql.*
public class JDBC {
static void main(String[] args) {
try {
def connection
= DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql?" + "user=root&password=mysql")
query(connection)
} catch (SQLException e) {
}
}
public static void query(Connection connection) throws SQLException {
def query = "select * from Catalog"
try (def stmt = connection.createStatement()) {
def rs = stmt.executeQuery(query)
while (rs.next()) {
}
} catch (SQLException e) {
println e.getMessage()
}
}
}
运算符的!in和!instance
要在Groovy 2.x中使用否定形式,必须将包含instanceof和in运算符的表达式放在括号中。 举个例子:
def x=5;
assert !(x instanceof List)
assert !(x in [1,2,3,4,5,6])
Groovy 3.0.0增加了对!instanceof和!in运算符的支持,而无需使用方括号。 前面的脚本可以按如下方式使用。
def x=5;
assert x !instanceof List
assert x !in [1,2,3,4,5,6]
这两个脚本生成相同的输出:
Caught: Assertion failed:
assert x !in [1,2,3,4,5,6]
| |
5 false
Assertion failed:
assert x !in [1,2,3,4,5,6]
| |
5 false
at Hello.run(Hello.groovy:3)
身份比较运算符
Groovy 3.0.0添加了一个新的标识运算符=== ,它与is()方法相同。 以下脚本说明了===的用法,还使用了等效的is()方法调用。
def a = ['A','B','C']
def b=a
assert a === b
assert a.is(b)
对于is()的等效否定形式,添加了新的运算符!== 。 以下Groovy脚本说明了!==运算符以及否定的is() 。
def c= ['A','B','C']
assert a !== c
assert !a.is(c)
赋值运算符的新解析器和缩写形式
Groovy 3.0.0添加了一个新的解析器,称为Parrot解析器,它比旧的解析器更加灵活和可维护,并支持其他语法选项和功能。 新的解析器默认情况下处于启用状态。
Groovy 2.x已经支持简短形式的三元运算符。 如果表达式返回false,则三元运算符将返回默认值。 Groovy 3.0.0为赋值运算符添加了一个简短形式。 例如,在以下Groovy脚本名称变量中分配了一个默认值。
import groovy.transform.ToString
@ToString
class Hello {
String name
}
def hello = new Hello(name: 'Deepak')
hello.with {
name ?= 'John'
}
assert hello.toString() == 'Hello(Deepak)'
安全索引
可以使用索引来访问Groovy集合,例如数组,列表和映射。 安全索引是指使用安全延迟运算符?访问集合索引以获取/设置值。 例如,声明一个数组为null 。
def anArray = null
下面的语句利用了? 运算符可以正常运行,因为所有索引值都返回null 。
assert null==anArray?[0]
如果? 未使用运算符时,将生成以下错误消息。
Caught: java.lang.NullPointerException: Cannot invoke method getAt() on null object
下面的语句利用了? 操作员访问空对象的索引以分配值,并且操作员将其忽略而不是生成错误消息。
anArray?[0]='a'//ignores
同样,以下语句运行良好,因为所有索引值都返回null 。
assert null==anArray?['a']
下面的语句利用了? 访问空对象索引以设置值的操作符将被忽略,而不会生成错误消息。
anArray?['a']='a'//ignored
Groovy 3.0.0的另一个新功能是可嵌入的Groovydoc注释。
摘要
在本文中,我们讨论了Groovy 3.0.0中的新功能。 大多数新功能均取自Java,包括对do-while语句的支持,Java样式的for语句,该语句在初始化和增量表达式,lambda表达式,方法引用,匿名代码块,非静态内部类实例化,接口中的默认方法,以及try-with-resources语句。 Groovy特定的新功能包括一些新的运算符和安全索引。