Contents

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) {...}

关于此转换,需要记住一些简单的规则:

  • 非空Collectionsarraysmaps 评估为true
  • 至少有一个匹配的Matcher 评估为true
  • 具有更多元素的Iterators Enumerations 被强制为true
  • 非空StringsGStringsCharSequences被强制为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()方法。如果我们为特定字段定义settergetter ,编译器将不会创建公共方法。

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

在上面的示例中,如果personperson.organizationorganization.parentnull,则返回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. CollectionsMaps

我们来看看一些基本的数据结构是如何处理的。

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。经常使用的可迭代对象是范围、集合、映射、数组、迭代器和枚举。事实上,任何对象都可以是可迭代的。

如果主体仅包含一个语句,则主体周围的大括号是可选的。下面是迭代rangelistarraymapstrings的示例:

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 成为一个复杂的控制结构。它是使用通过闭包迭代对象的方法的有效对应物,例如使用Collectioneach方法。

主要区别在于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 语句。