【语言学习】Groovy - hippowc/hippowc.github.io GitHub Wiki

Groovy 简介

Groovy是一门基于Jvm的动态语言,预发和python以及ruby很相似,Groovy脚本最终也会编译成为字节码运行在Jvm之上。

Groovy是使用java编写的,同样具有编译器和解释器,所以它是一门语言。通过下载GDK来进行Groovy开发,Groovy想JDK中添加了各种便捷方法,将JDK扩展为GDK

开发工具与环境

Groovy 2.4 需要 Java 6+ ,并对直到 Java 8 的版本都提供了全面支持。目前在使用 Java 9 快照时在某些方面上出现了一些问题。groovy-nio 模块需要 Java 7+,可以直接到Groovy官网下载GDK,不仅仅是bin文件

  • groovysh 这将创建一个交互式的 Groovy shell,在这里可以输入任何 Groovy 语句。
  • 运行一个特定的 Groovy 脚本 groovy SomeScript

关于GDK

Groovy 提供了很多辅助方法,尽管可以使用 Java 来解决,但 Groovy 还是更多方便的方法来处理文件、流、阅读器

Groovy为Java的一些类添加了特殊处理方式,是的类的操作会更加方便,主要有一下几个:

  • 处理IO操作
  • 集合操作
  • 其他的一些实用工具:配置文件读取工具,动态可扩展对象Expando,可扩展的集合

具体的需要看Groovy支持的语法

与Java的不同

默认导入

  • java.io.*
  • java.lang.*
  • java.math.BigDecimal
  • java.math.BigInteger
  • java.net.*
  • java.util.*
  • groovy.lang.*
  • groovy.util.*

多重方法

在 Groovy 中,调用的方法将在运行时被选择。这种机制被称为运行时分派或多重方法(multi-methods),是根据运行时实参(argument)的类型来选择方法。Java 采用的是相反的策略:编译时根据声明的类型来选择方法。

数组初始化表达式

在 Groovy 中,{...} 语句块是留给闭包(Closure)使用的

// java
int[] array = { 1, 2, 3}
// groovy
int[] array = [1,2,3]

包范围可见性

在 Groovy 中,如果某个字段缺失了修饰符,并不会导致像在 Java 中那样形成包的私有字段,相反,它会用来创建一个属性(property),也就是一个私有字段(private field),以及一个关联的 getter 和一个关联的 setter。

在 Groovy 中创建包私有字段,可以通过标注 @PackageScope 来实现。

class Person {
    @PackageScope String name
}

ARM 语句块

ARM(Automatic Resource Management,自动资源管理)语句块从 Java 7 开始引入,简言之就是,将资源类(InputStream,OutputStream)在try()括号中声明,执行完try语句会自动关闭资源文件,这些资源文件都实现了AutoClosable接口

但 Groovy 并不支持。相反,Groovy 提供多种基于闭包的方法,效果相同但却合乎习惯。

内部类

对于非静态内部类,Groovy 不支持 y.new X(),需要使用 new X(y),其中X为Y的非静态内部类

Lambda 表达式

Java 8 的 lambda 几乎可以认为是匿名内部函数。Groovy 不支持这种格式,而采用闭包来实现。

GString

由于双引号所包括起来的字符串字面量会被解释为 GString 值(即 “Groovy 字符串”的简称),所以如果当某个类中的 String 字面量含有美元字符($)时,那么利用 Groovy 和 Java 编译器进行编译时,Groovy 很可能就会编译失败,或者产生与 Java 编译所不同的结果。

在 Groovy 中,由单引号所创建的字面量属于 String 类型对象,而双引号创建的字面量则可能是 String 或 GString 对象,具体分类由字面量中是否有插值来决定。当把一个包含单个字符的 String 对象赋予一个 char 类型变量时,Groovy 会自动将该对象转换为 char 类型。在调用带有 char 类型实参的方法时,我们需要显式地转换参数值,或者确认参数值已经预先转换过了

== 的行为差异

在 Java 中,== 代表基本数据类型的相同,或对象引用的等价性。在 Groovy 中,== 的含义变成了 a.compareTo(b)==0,不过这要当且仅当 == 两边的对象都实现了 Comparable 接口时才能实现,否则 == 就等同于 a.equals(b)。而要想在 Groovy 中检查对象间的引用等价性,则需使用 is,比如:a.is(b)

额外的关键字

Groovy 的关键字要比 Java 中的多一些。不要使用以下这些关键字来定义变量名等名称:

  • as
  • def
  • in
  • trait

default 必须位于 swith / case 结构的结尾

元编程

Groovy 支持两种元编程:运行时元编程和编译时元编程。第一种方式允许在运行时改变类模式和程序行为,第二种方式则只发生在编译时。

运行时元编程

运行时元编程,可以将一些决策(诸如解析、注入甚至合成类和接口的方法)推迟到运行时来完成。Groovy 的元编程支持所有类型的对象,但是它们采用的方式却各不相同。

  • POJO —— 普通的 Java 对象,它的类可以用 Java 或其他任何 JVM 上的语言来编写。
  • POGO —— Groovy 对象,它的类使用 Groovy 编写而成,继承自 java.lang.Object 且默认实现了 groovy.lang.GroovyObject 接口。
  • Groovy 拦截器 —— 实现了 groovy.lang.GroovyInterceptable 接口的 Groovy 对象,并具有方法拦截功能。

编译时元编程

在 Groovy 中,编译时元编程能够容许编译时生成代码。这种转换会影响程序的抽象语法树(AST,Abstract Syntax Tree),与运行时元编程相比,在类文件自身中(也就是说,在字节码内)就可以看到变化。这一点是非常重要的,比如说当你想让转换成为类抽象一部分时(实现接口,继承抽象类,等等),或者甚至当需要让类可从 Java (或其他的 JVM 语言)中调用时。例如,AST 转换可以为一个类添加一些方法。如果用运行时元编程来实现的话,新方法只能可见于 Groovy;而用编译时元编程来实现,新方法也可以在 Java 中显现出来。最后一点也同样重要,编译时元编程的性能要好过运行时元编程

领域专用语言

命令链

Groovy 可以使你省略顶级语句方法调用中参数外面的括号。“命令链”功能则将这种特性继续扩展,它可以将不需要括号的方法调用串接成链,既不需要参数周围的括号,链接的调用之间也不需要点号。举例来说,a b c d 实际上就等同于 a(b).c(d)。

// 等同于:take(2.pills).of(chloroquinine).after(6.hours)
take 2.pills of chloroquinine after 6.hours
// 等同于:check(that: margarita).tastes(good)
check that: margarita tastes good
// 等同于:given({}).when({}).then({})
given { } when { } then { }

不带参数的链中也可以使用方法,但在这种情况下需要用括号。

// 等同于:select(all).unique().from(names)
select all unique() from names

如果命令链包含奇数个元素,链会由方法和参数组成,最终由一个最终属性访问

// 等同于:take(3).cookies
// 同样也等于:take(3).getCookies()
take 3 cookies

DSL 创建策略

map 映射与闭包

show = { println it }
square_root = { Math.sqrt(it) }

def please(action) {
  [the: { what ->
    [of: { n -> action(what(n)) }]
  }]
}

// 等同于:please(show).the(square_root).of(100)
please show the square_root of 100

操作符重载

Groovy 的多种操作符都可以被映射到对象的正则方法调用上。

脚本基类

@DelegatesTo

构建者模式

编译自定义器

无论你使用 groovyc 还是采用 GroovyShell 来编译类,要想执行脚本,实际上都会使用到编译器配置(compiler configuration)信息。这种配置信息保存了源编码或类路径这样的信息,而且还用于执行更多的操作,比如默认添加导入,显式使用 AST 转换,或者禁止全局 AST 转换,

Grape依赖管理器

Grape 是一个内嵌在 Groovy 中的 JAR 依赖项管理器。它能使你在类路径上快速添加 Maven 库依赖项,更易于编写脚本。最简单的用法是在脚本上添加注释(annotation),如下所示

@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

Groovy 与应用的集成

Eval

  • groovy.util.Eval 类是最简单的用来在运行时动态执行 Groovy 代码的类,调用 me 方法即可。
  • GroovyShell groovy.lang.GroovyShell 类是建议采用的脚本计算方式,因为它具有缓存结果脚本实例的能力。虽然 Eval 类能够返回编译脚本的执行结果,但 GroovyShell 类却能提供更多选项
  • GroovyClassLoader 通过利用 GroovyClassLoader,而不是 GroovyShell,可以加载类,而不是脚本实例.roovyClassLoader 持有一个它所创建的所有类的引用,因此很容易造成内存泄露,尤其当你两次执行同一脚本时,比如一个字符串,那么你将获得两个不同的类:GroovyClassLoader 并不跟踪源文本。如果想要同一实例,源必须是一个文件
  • GroovyScriptEngine groovy.util.GroovyScriptEngine 类能够为那些依赖脚本重载及依赖的应用程序提供一种灵活的基础。尽管 GroovyShell 聚焦单独的脚本,GroovyClassLoader 能够处理任何 Groovy 类的动态编译与加载,然而 GroovyScriptEngine 能够为 GroovyClassLoader 其上再增添一个能够处理脚本依赖及重新加载的功能层。

语法风格

不用分号

分号是可选择采用的,你可以忽略不用它们,而且往往这种方法才是地道的用法

可选择性使用的 return 关键字

对于短方法和闭包而言,忽略这个关键字会显得更简洁。

def 和类型

很多开发者往往会同时使用 def 和类型,但这里的 def 是多余的。因此,要么使用 def,要么使用类型。

默认采用 public

默认情况下,Groovy 会将类及方法认为是 public 型,所以不必使用 public 修饰符了,只有当非公开时,才需要加上。

省略括号(重点特性)

对于顶级表达式,Groovy 允许省去括号,比如 println 命令

println "Hello"
method a, b

当闭包成为方法调用的最后一个参数时,比如在使用 Groovy 的 each{} 迭代机制时,你可以将闭包放到括号对外面,甚至将括号对去除。

list.each( { println it } )
list.each(){ println it }
list.each  { println it }

对于内嵌的方法调用或在赋值语句的右侧,则是不允许忽略括号的。

作为一等公民存在的类

Groovy 中并不需要 .class 后缀

Getter 与 Setter

可以舍弃 Java 式的调用方法,而采用字段样式的访问标记

利用命名参数及默认构造函数初始化 bean

class Server {
    String name
    Cluster cluster
}
def server = new Server(name: "Obelix", cluster: aCluster) 

相等与 ==

Java 的 == 实际相当于 Groovy 的 is() 方法,而 Groovy 的 == 则是一个更巧妙的 equals()。要想比较对象的引用,不能用 ==,而应该用 a.is(b)

数据结构的原生语法

Groovy 开发工具包

导入别名

使用不同包而同名的两个类时(比如 java.util.List 和 java.awt.List 这两个包),你可以导入其中一个类,而对另一个类使用完整限定名

Groovy Truth

任何对象都可以被强制转换为布尔值:任何为 null、void的对象,等同于 0 或空的值,都会解析为 false,凡之则为 true。这一原则也可以用于集合等对象。

异常捕捉

try 语句块中所要抛出的异常类型,可以只捕捉异常而忽略它们的类型。

try {
    // ...
} catch (any) {
    // 一些糟糕的事情  
}