Groovy 简介
1. 概述
Groovy 是一种用于 JVM 的动态脚本语言。它编译为字节码并与 Java 代码和库无缝融合。
在本文中,我们将了解Groovy 的一些基本特性,包括基本语法、控制结构和集合。
然后我们将看看使它成为一种有吸引力的语言的一些主要特性,包括空安全性、隐含真理、运算符和字符串。
2. 环境
如果我们想在 Maven 项目中使用 Groovy,我们需要在pom.xml 中添加以下内容:
<build>
<plugins>
// ...
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.5</version>
</plugin>
</plugins>
</build>
<dependencies>
// ...
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.10</version>
</dependency>
</dependencies>
最新的 Maven 插件可以在这里 找到,最新版本的groovy-all可以在这里 找到。
3. 基本特点
Groovy 中有许多有用的特性。现在,让我们看看该语言的基本构建块以及它与 Java 的区别。
现在,让我们看看该语言的基本构建块以及它与 Java 的区别。
3.1. 动态类型
Groovy 最重要的特性之一是它对动态类型的支持。
类型定义是可选的,实际类型在运行时确定。我们来看看这两个类:
class Duck {
String getName() {
'Duck'
}
}
class Cat {
String getName() {
'Cat'
}
}
这两个类定义了相同的getName方法,但它没有在合同中明确定义。
现在,假设我们有一个对象列表,其中包含具有getName方法的鸭子和猫。使用 Groovy,我们可以执行以下操作:
Duck duck = new Duck()
Cat cat = new Cat()
def list = [duck, cat]
list.each { obj ->
println obj.getName()
}
代码将编译,上面代码的输出将是:
Duck
Cat
3.2. 隐式真实转换
就像在 JavaScript 中一样,如果需要,Groovy 会将每个对象评估为布尔值,例如在if语句中使用它或取反值时:
if("hello") {...}
if(15) {...}
if(someObject) {...}
关于此转换,需要记住一些简单的规则:
- 非空Collections、arrays、maps 评估为true
- 至少有一个匹配的Matcher 评估为true
- 具有更多元素的Iterators 和Enumerations 被强制为true
- 非空Strings、GStrings和CharSequences被强制为true
- 非零数字被评估为true
- 非空对象引用被强制为true
*如果我们想自定义隐式真值转换,我们可以定义我们的*asBoolean()方法。
3.3. 导入
有些包是默认导入的,我们不需要显式导入它们:
import java.lang.*
import java.util.*
import java.io.*
import java.net.*
import groovy.lang.*
import groovy.util.*
import java.math.BigInteger
import java.math.BigDecimal
4. AST 转换
AST(抽象语法树)转换允许我们挂钩到 Groovy 编译过程并对其进行自定义以满足我们的需求。这是在编译时完成的,因此在运行应用程序时没有性能损失。我们可以创建我们的 AST 转换,但我们也可以使用内置的。
我们可以创建我们的转换,或者我们可以从内置的转换中受益。
我们来看看一些值得了解的注解。
4.1. TypeChecked注解
此注释用于强制编译器对带注释的代码片段进行严格的类型检查。类型检查机制是可扩展的,因此我们甚至可以在需要时提供比 Java 中更严格的类型检查。
让我们看一下下面的例子:
class Universe {
@TypeChecked
int answer() { "forty two" }
}
如果我们尝试编译此代码,我们将观察到以下错误:
[Static type checking] - Cannot return value of type java.lang.String on method returning type int
@TypeChecked注解可以应用于类和方法。
4.2. CompileStatic注解
此注解允许编译器执行编译时检查,就像它对 Java 代码所做的那样。之后,编译器执行静态编译,从而绕过 Groovy 元对象协议。
当一个类被注解时,被注解的类的所有方法、属性、文件、内部类等都会进行类型检查。当一个方法被注解时,静态编译只应用于那些被该方法包围的项目(闭包和匿名内部类)。
5. 属性
在 Groovy 中,我们可以创建 POGO(Plain Old Groovy Objects),其工作方式与 Java 中的 POJO 相同,尽管它们更紧凑,因为在编译期间会为公共属性自动生成 getter 和 setter。重要的是要记住,只有在尚未定义它们时才会生成它们。
这使我们可以灵活地将属性定义为开放字段,同时保留在设置或获取值时覆盖行为的能力。
考虑这个对象:
class Person {
String name
String lastName
}
由于类、字段和方法的默认范围是public——这是一个公共类,两个字段都是公共的。
编译器会将这些转换为私有字段并添加getName()、setName()、getLastName()和setLasfName()方法。如果我们为特定字段定义setter和getter ,编译器将不会创建公共方法。
5.1. 快捷符号
Groovy 为获取和设置属性提供了一种快捷表示法。我们可以使用类似字段的访问表示法,而不是调用 getter 和 setter 的 Java 方式:
resourceGroup.getResourcePrototype().getName() == SERVER_TYPE_NAME
resourceGroup.resourcePrototype.name == SERVER_TYPE_NAME
resourcePrototype.setName("something")
resourcePrototype.name = "something"
6. 运算符
现在让我们看看在普通 Java 已知的运算符之上添加的新运算符。
6.1. 空安全取消引用
最流行的是空安全解引用运算符*“?”* 这允许我们在调用方法或访问null 对象的属性时避免NullPointerException。它在链中的某个点可能出现空值的链调用中特别有用。
例如,我们可以安全地调用:
String name = person?.organization?.parent?.name
在上面的示例中,如果person、person.organization或organization.parent为null,则返回null。
6.2. Elvis 运算符
Elvis 运算符*“?:* ”让我们可以压缩三元表达式。这两个是等价的:
String name = person.name ?: defaultName
和
String name = person.name ? person.name : defaultName
如果它是Groovy true(在这种情况下,不为null并且具有non-zero长度),则它们都将person.name的值分配给 name 变量。
6.3. 比较运算符
比较运算符*“<=>”是一个关系运算符,其执行类似于 Java 的compareTo()*,它比较两个对象并根据两个参数的值返回 -1、0 或 +1。
如果左侧参数大于右侧,则运算符返回 1。如果左侧参数小于右侧,则运算符返回 -1。如果参数相等,则返回 0。
使用比较运算符的最大优点是可以平滑处理空值,这样x <=> y永远不会抛出NullPointerException:
println 5 <=> null
上面的示例将打印 1 作为结果。
7. Strings
有多种表示字符串文字的方法。支持 Java 中使用的方法(双引号字符串),但也允许在首选时使用单引号。
使用三引号(单引号或双引号)也支持多行字符串,有时在其他语言中称为 heredocs。
使用三引号(单引号或双引号)也支持多行字符串,有时在其他语言中称为 heredocs。
用双引号定义的字符串支持使用*${}*语法进行插值:
def name = "Bill Gates"
def greeting = "Hello, ${name}"
事实上,任何表达式都可以放在*${}*内:
def name = "Bill Gates"
def greeting = "Hello, ${name.toUpperCase()}"
如果包含表达式*${}的带双引号的 String 称为 GString ,否则它是一个普通的String*对象。
下面的代码将在不通过测试的情况下运行:
def a = "hello"
assert a.class.name == 'java.lang.String'
def b = 'hello'
assert b.class.name == 'java.lang.String'
def c = "${b}"
assert c.class.name == 'org.codehaus.groovy.runtime.GStringImpl'
8. Collections 和 Maps
我们来看看一些基本的数据结构是如何处理的。
8.1. Lists
下面是一些代码,用于在 Java中将一些元素添加到ArrayList的新实例中:
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
这是 Groovy 中的相同操作:
List list = ['Hello', 'World']
列表默认为java.util.ArrayList类型,也可以通过调用相应的构造函数显式声明。
Set没有单独的语法,但我们可以使用类型强制。要么使用:
Set greeting = ['Hello', 'World']
或者:
def greeting = ['Hello', 'World'] as Set
8.2. Map
Map的语法是相似的,虽然有点冗长,因为我们需要能够指定用冒号分隔的键和值:
def key = 'Key3'
def aMap = [
'Key1': 'Value 1',
Key2: 'Value 2',
(key): 'Another value'
]
在这个初始化之后,我们将得到一个新的LinkedHashMap条目:Key1 -> Value1, Key2 -> Value 2, Key3 -> Another Value。
我们可以通过多种方式访问Map中的条目:
println aMap['Key1']
println aMap[key]
println aMap.Key1
9. 控制结构
9.1. 条件:if-else
Groovy 按预期支持条件if/else语法:
if (...) {
// ...
} else if (...) {
// ...
} else {
// ...
}
9.2. 条件:switch-case
switch语句向后兼容 Java 代码,因此我们可以遇到多个匹配项共享相同代码的情况。
最重要的区别是一个switch可以针对多种不同的值类型执行匹配:
def x = 1.23
def result = ""
switch ( x ) {
case "foo":
result = "found foo"
break
case "bar":
result += "bar"
break
case [4, 5, 6, 'inList']:
result = "list"
break
case 12..30:
result = "range"
break
case Number:
result = "number"
break
case ~/fo*/:
result = "foo regex"
break
case { it < 0 }: // or { x < 0 }
result = "negative"
break
default:
result = "default"
}
println(result)
上面的示例将打印number。
9.3. 循环:while
Groovy 支持常见的while循环,就像 Java 一样:
def x = 0
def y = 5
while ( y-- > 0 ) {
x++
}
9.4. 循环:for
Groovy 拥抱这种简单性,并强烈鼓励遵循这种结构的 for循环:
for (variable in iterable) { body }
for循环遍历iterable。经常使用的可迭代对象是范围、集合、映射、数组、迭代器和枚举。事实上,任何对象都可以是可迭代的。
如果主体仅包含一个语句,则主体周围的大括号是可选的。下面是迭代range、list、array、map和strings的示例:
def x = 0
for ( i in 0..9 ) {
x += i
}
x = 0
for ( i in [0, 1, 2, 3, 4] ) {
x += i
}
def array = (0..4).toArray()
x = 0
for ( i in array ) {
x += i
}
def map = ['abc':1, 'def':2, 'xyz':3]
x = 0
for ( e in map ) {
x += e.value
}
x = 0
for ( v in map.values() ) {
x += v
}
def text = "abc"
def list = []
for (c in text) {
list.add(c)
}
对象迭代使 Groovy for-loop 成为一个复杂的控制结构。它是使用通过闭包迭代对象的方法的有效对应物,例如使用Collection 的 each方法。
主要区别在于for循环的主体不是闭包,这意味着该主体是一个块:
for (x in 0..9) { println x }
而这个主体是一个闭包:
(0..9).each { println it }
尽管它们看起来很相似,但它们的构造却大不相同。
闭包是它自己的对象,具有不同的特性。它可以在不同的地方构造并传递给each方法。但是,for循环的主体在其出现时直接生成为*bytecode *。没有特殊的范围规则适用。
10. 异常处理
最大的区别是不强制执行检查异常处理。
为了处理一般异常,我们可以将可能导致异常的代码放在try/catch块中:
try {
someActionThatWillThrowAnException()
} catch (e)
// log the error message, and/or handle in some way
}
通过不声明我们捕获的异常类型,任何异常都会在此处捕获。
11. 闭包
简单地说,闭包是一个匿名的可执行代码块,它可以传递给变量,并且可以访问定义它的上下文中的数据。
它们也类似于匿名内部类,尽管它们不实现接口或扩展基类。它们类似于 Java 中的 lambda。
有趣的是,Groovy 可以充分利用为支持 lambda 而引入的 JDK 新增功能,尤其是流式 API。我们总是可以在需要 lambda 表达式的地方使用闭包。
让我们考虑下面的例子:
def helloWorld = {
println "Hello World"
}
变量helloWorld现在持有对闭包的引用,我们可以通过调用它的call方法来执行它:
helloWorld.call()
Groovy 让我们使用更自然的方法调用语法——它为我们调用call方法:
helloWorld()
11.1. 参数
像方法一样,闭包也可以有参数。有三种变体。
在后一个示例中,因为没有任何 declpersistence_startared,所以只有一个默认名称为it的参数。打印发送内容的修改后的闭包将是:
def printTheParam = { println it }
我们可以这样称呼它:
printTheParam('hello')
printTheParam 'hello'
我们还可以期望闭包中的参数并在调用时传递它们:
def power = { int x, int y ->
return Math.pow(x, y)
}
println power(2, 3)
参数的类型定义与变量相同。如果我们定义一个类型,我们只能使用这个类型,但也可以它并传入我们想要的任何东西:
def say = { what ->
println what
}
say "Hello World"
11.2. 可选返回
闭包的最后一条语句可以隐式返回,而无需编写返回语句。这可用于将样板代码减少到最低限度。因此,计算数字平方的闭包可以缩短如下:
def square = { it * it }
println square(4)
这个闭包使用了隐式参数it和可选的 return 语句。