概述###

Groovy 是一门基于JVM的动态类型语言,可以与 Java 平台几乎无缝融合(与Java代码一样编译成字节码)。 使用 Groovy ,可以增强 Java 应用的灵活性和可扩展性,提升开发效率。使用 Groovy + Spock 组合也更容易编写简洁易懂的单测。熟练使用 Groovy 编程是很有益处的。

本文针对已有一定java开发经验,想要快速熟悉和使用Groovy语言的童鞋。本文会跳过变量、作用域等基础通用的编程内容,重点给出与Groovy相关的实用性的知识点。

变量定义###

Java 代码几乎就是 Groovy 代码。Java 的变量定义基本可透明视为 Groovy 的变量定义。不加修饰符,通常默认是 public 的。一开始不熟悉 Groovy 语法的话 , 可以先用 Java 的方式来写,再逐渐替换为 Groovy 的。

Groovy 还有一种动态类型的定义符 def ,可以定义动态类型的变量。使用def关键字定义的变量类型都是Object。如下代码所示,chameleon 先后被赋值为一个整型、字符串、闭包。


def chameleon = 0
println "i am a ${chameleon.getClass().name}"
chameleon = 'haha changed'
println "now i am ${chameleon}"
chameleon = { a1, a2, op -> op(a1,a2) }
println "now i have changed to ${chameleon.getClass().name}"
println "power(2,10) = ${chameleon(2,10, {bg,p->Math.pow(bg,p)})}"

函数调用###

默认与可变参数####

Groovy 支持默认与可变参数,语法形式与Java相同。如下代码所示,从一个默认空列表和一个可变参数列表中获取是否有值为false的测试函数:

def static isAllTrue(List<Boolean> bList=[], boolean... bools) {
def anyContainFalse = bList?.contains(false)
if (anyContainFalse) { return false }
for (b in bools) {
if (!b) {
return false
}
}
return true } println "isAllTrue(true,true): ${isAllTrue (true , true)}"
println "isAllTrue(true, false, true): ${isAllTrue(true, false, true)}"
println "isAllTrue([true, false, true]): ${isAllTrue([true, false, true])}"
println "isAllTrue([true, true, true]): ${isAllTrue([true, true, true])}"
println "isAllTrue([true, true, true], true,false): ${isAllTrue([true, true, true], true,false)}"
println "isAllTrue([true, true, false], true,true): ${isAllTrue([true, true, false], true,true)}"
println "isAllTrue([true, true, true], true,true): ${isAllTrue([true, true, true], true,true)}"

动态调用####

Groovy 支持简便的函数定义和动态函数调用,使用 "${funcName}" 来引用函数名进行调用即可。


def anoList = [3,7,2,3,1,4,6,5,9,2,8,12,6,7,1]
['unique', 'sort'].each {
println "${it} list:" + anoList."${it}"()
}

语法精简###

Groovy 做了很多针对Java语法的精简,大幅提升了开发效率。Groovy 的语法很像 Python, 而我也很喜欢 Python。

  • 可以省略语句末尾的分号。 别看一小步, 积少成多,省了很多时间哦。
  • 同时赋值给多个变量。很适合关联变量,比如三维坐标 def (x,y,z) = [1,2,3] ; println "($x,$y,$z)" 。
  • 变量、函数声明默认为 public 的。
  • 函数签名中的参数类型可省略(可能无法自动补全造成不方便)。
  • 如果不存在歧义的话,函数调用可以省略括号。
  • 函数的最后一个参数为闭包时,其括号可省略。
  • 方法的最后一句表达式可作为返回值返回,而不需要return关键字。
  • GroovyBean 默认含有 getter/setter 方法。
  • with 语法创建 Context 。
  • switch 可接受多种类型 。
  • 所有类型都能转成布尔值,null和void相当于0或者相当于false,其他则相当于true 。
  • ?. 操作符可以安全取值,有防 NPE 作用。

字符串###

字符串是编程中最常用的对象。关于字符串,需要掌握如下三点:

  • 创建字符串;

  • 变量引用

  • 正则匹配与捕获

Groovy字符串的诸多方法,可以在网上搜索,这里不再赘述。

创建字符串####

可以使用单引号、双引号或三引号创建字符串。 单引号可以创建普通字符串,消除转义; 双引号可以创建含变量引用的字符串;三引号可以创建多行字符串。

注意: 字符串是不可变的。如果要创建可变的字符序列,使用 StringBuilder 类。


def name = 'qin'
def intro = "i am $name"
def intro2 = 'i am $name'
println "intro=$intro, intro2=$intro2"
def multilines = '''
There will be one day when
i am proud of who i am.
Because i believe my heart.
'''
println multilines

变量引用####

变量引用是Groovy对字符串模板的重要支持,可以方便地格式化输出文本。变量引用必须围在一对双引号里(双引号可以是两个或三个')。变量引用也可以作用于方法调用。比如:


println "intro=$intro, size = ${intro.length()} intro2=$intro2 size=${intro2.length()}"

多行字符串加上变量引用特别适合做报告模板。比如下面的测试报告:

               def caseInfo = """
[CaseFailed -> DataCheckFailed]
SearchTestCases: ${searchTestCases}
Expect: ${expect}
SearchParam: ${JSON.toJSONString(param)}
Data: ${JSON.toJSONString(data)}
"""

正则表达式####

Groovy 使用 ~ 创建正则表达式,其类型是 java.util.regex.Pattern 。使用 =~ 或 ==~作为匹配操作符,其中, =~ 是部分匹配, ==~ 是全匹配。=~ 操作符的结果是一个 Matcher 类,含有被匹配的所有字符串列表。 正则匹配示例如下:


def multilines = '''There will be one day when
i am proud of who i am.
Because i believe my heart.'''
println multilines
println multilines =~ "\\w+\\s+" ? 'matched' : 'not matched'
println multilines ==~ "\\w+\\s+" ? 'matched' : 'not matched'
def matched = multilines =~ /\w+\s+/
matched.each {
println "matched: $it"
}
def isAllMatched = multilines ==~ /(?m)^(([a-zA-z]+\s+)+([a-zA-z]+[.\n]+)\s*)+$/
println isAllMatched ? 'All matched' : 'not matched'
def matchedCaptured = multilines =~ /(?:[a-zA-z]+\s+)+(?:[a-zA-z]+[.\n]+)/
matchedCaptured.each {
println "matchedCaptured: $it"
}

有几点说明下:

  • 正则表达式可以使用单引号、双引号或双斜线//围起来。使用双斜线时,可以对反斜杠转义,双引号下 "\w+" 可以写成 /\w+/ ;

  • 多行匹配使用 ?m ,忽略捕获分组使用 ?:

  • 编写正确的正则表达式,我没有固定经验可循,只有不断尝试,从最简单的字符串匹配,依次编写子表达式匹配字符串里每个子部分,然后将子表达式组合起来即可。组合的方式有顺序、+,*,?, [] 等。

  • 匹配正确后,可以使用 each 遍历匹配的字符串。

关于正则表达式的基础知识,可参阅:“正则表达式基础知识”

闭包###

闭包和元编程是Groovy语言的两大精髓。Groovy的闭包大大简化了容器的遍历,提升了代码的可扩展性,使代码更加简洁优雅。闭包在Groovy编程中几乎无处不在。

可参阅: “谈谈Groovy闭包”

容器###

任何一门编程语言中,容器的使用是重中之重。灵活使用Map,List 可以让代码实现更简洁。

Map####

Map 的创建非常简单,中括号 [] 里面包含一系列的 key:value 即可。跟Python的语法一样简洁。通过 def map = [key:value] 创建的Map类型是 java.util.LinkedHashMap 。 访问Map中的元素,可以用 map[key] , map.key (key含特殊字符不可以), map.'key' , 或 map.get(key), map.getOrDefault(key) 。

Map 的基本方法有 each, collect, find, findAll ,分别用来做最通用的遍历、收集器、查询过滤操作。


println map['me']
println map.lover
println map.getOrDefault("nonexist", [:])
println map.collect { it.value['name'] }
println map.findAll { it.value.age <=25 }

List####

List 的创建非常简单,中括号里包含逗号分隔的一系列元素。跟大多数语言里的数组创建一样。通过def list = [1,2,3] 创建的List 类型是 java.util.ArrayList。

下标访问

使用下标访问List中的元素很灵活。可以使用 list[i], list.get(i), 可以使用区间下标,负数下标。如下所示:


println "first: " + alist.get(0)
println "second: " + alist[1]
println alist[2..4]
println alist[-1]

常用方法

list 也有非常丰富的方法来操作其中的元素。each, eachWithIndex, find, findAll, collect, groupBy, join, flatten, inject 等,这些参数都是一个闭包。

eachWithIndex可以按照下标遍历列表元素; find, findAll 在列表中查询满足条件的第一个元素或所有元素列表;collect 可以将列表元素转换成另一个列表;groupBy可以根据某种条件对列表元素分组;join可以连接列表元素成字符串;flatten 可以将嵌套列表打平成一维列表;inject 类似 reduce 方法。


alist.eachWithIndex {
int entry, int i ->
println "index=$i, item=$entry"
}
println alist.findAll {
it % 3 == 0
}
println alist.collect { it * it }
println '[' + alist.join(",") + ']'
println alist.inject(1) { n1, n2 -> n1 * n2 }
println ([[1,2,4], [1,3,9], [1,4,16]].flatten())
println ([[[1,5,10], [1,6,12]], []].flatten())

对象###

定义一个Groovy类很简单,几乎只要指定属性名即可。def 默认为属性添加 getter/setter 方法。


class Person {
def name
def age
def address
}
class Address {
def detail
}

然后可以基于Map来创建对象。一般使用 List 来存储多个对象,然后通过 List 方法来操作对象列表。访问对象的属性用 object.attrName 即可。如下代码所示:


def persons = [new Person(["name": 'qin', "age": 28, "address": new Address(["detail": "Hangzhou"])]), new Person(["name": 'ni', "age": 25])]
println persons.collect { "${it.name} live in ${it.address?.getDetail()}" }
println persons.find { it.age >=28 }.name

这里使用了 ?. 操作符对对象做非空判断。一般用于访问嵌套对象中某个子对象的属性而该子对象可能不存在的情况。 ?. 必须放置在可能不存在的子对象的后面,表示对这个对象的存在性“表示怀疑”。这样就免除了写 if-else 判断的繁琐,尤其在多层嵌套对象的时候更为方便。另外也可以看到,变量引用也可以作用于对象的属性和方法。

Groovy对象也支持动态方法调用。只要使用 objectRef."${methodName}"(args) 即可。


persons[0].class.declaredMethods.findAll {
it.parameterCount == 0
}.each {
println "method = ${it.name}, callValue = ${persons[0]."${it.name}"()}"
}

文件读写###

Groovy对文件读写提供了非常方便的支持,不需要像Java那样new一堆的实现细节类(应该有一个Facade类),还需要谨慎地打开和关闭文件。

读文件的代码如下:分别是读取整个文本、读取每一行并进行指定处理、将读取的每行存入列表。


File file = new File("README.md")
println file.text
file.eachLine("UTF-8") {
println it
}
println file.readLines()

写文件的代码如下(使用了with语法):


new File("/tmp/result.txt").withPrintWriter { printWriter ->
printWriter.println('The first content of file')
}

是如下写法的简化版:


def printWriter = new File("/tmp/result2.txt").newPrintWriter()
printWriter.write('The first content of file')
printWriter.flush()
printWriter.close()

遍历目录主要是使用过滤器和闭包来遍历匹配的所有文件或目录并进行处理。注意,eachFileRecurse 会递归遍历该目录下所有子目录的所有文件,而 eachFileMatch 则只遍历该目录下的文件而不包含递归子目录的文件。


path.eachFileRecurse(FileType.FILES) {
def filename = it.name
if (filename =~ ~/.*\.groovy$/) {
println filename
}
} path.eachFileMatch(~/.*\..*/) {
println it.name
} path.eachDirRecurse {
def filename = it.canonicalPath
if (filename =~ ~/.*groovy$/) {
println filename
}
}

元编程与反射###

闭包和元编程是Groovy语言的两大精髓。元编程的内容广泛,会专门用一篇文章来讲解。目前读者可以先看简书上的一篇文章:“Groovy学习之-运行时元编程”

Groovy元编程的最简单用法,就是便捷地实现 Java 的反射机制,关键类是:metaClass。如下代码所示:



def propertiesInPerson = Person.metaClass.properties.collect { it.name }
println "propertiesInPerson=${propertiesInPerson}"
persons.each {
person ->
def personName = person.invokeMethod("getName", null)
def personAge = person.metaClass.invokeMethod(person, "getAge", null)
println "${personName} ages ${personAge} "
}

熟练使用 metaClass 和 invokeMethod 是Groovy元编程的入门。

参考文献###

Groovy常用编程知识点简明教程的更多相关文章

  1. Java 并发编程-NIO 简明教程

    问题来源 在传统的架构中,对于客户端的每一次请求,服务器都会创建一个新的线程或者利用线程池复用去处理用户的一个请求,然后返回给用户结果,这样做在高并发的情况下会存在非常严重的性能问题:对于用户的每一次 ...

  2. Groovy元编程简明教程

    同函数式编程类似,元编程,看上去像一门独派武学. 在 <Ruby元编程>一书中,定义:元编程是运行时操作语言构件的编程能力.其中,语言构件指模块.类.方法.变量等.常用的主要是动态创建和访 ...

  3. Vbs 脚本编程简明教程之一

    —为什么要使用 Vbs ? 在 Windows 中,学习计算机操作也许很简单,但是很多计算机工作是重复性劳动,例如你每周也许需要对一些计算机文件进行复制.粘贴.改名.删除,也许你每天启动 计算机第一件 ...

  4. Java网络编程简明教程

    Java网络编程简明教程 网络编程  计算机网络相关概念 计算机网络是两台或更多的计算机组成的网络,同一网络内的任意两台计算机可以直接通信,所有计算机必须遵循同一种网络协议. 互联网 互联网是连接计算 ...

  5. Python 简明教程 --- 26,Python 多进程编程

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 学编程最有效的方法是动手敲代码. 目录 1,什么是多进程 我们所写的Python 代码就是一个程序, ...

  6. AWK 简明教程

    AWK 简明教程 转自:http://coolshell.cn/articles/9070.html 有一些网友看了前两天的<Linux下应该知道的技巧>希望我能教教他们用awk和sed, ...

  7. 2013 duilib入门简明教程 -- FAQ (19)

        虽然前面的教程几乎把所有的知识点都罗列了,但是有很多问题经常在群里出现,所以这里再次整理一下.     需要注意的是,在下面的问题中,除了加上XML属性外,主窗口必须继承自WindowImpl ...

  8. Java8简明教程(转载)

    ImportNew注:有兴趣第一时间学习Java 8的Java开发者,欢迎围观<征集参与Java 8原创系列文章作者>. 以下是<Java 8简明教程>的正文. “Java并没 ...

  9. Lisp简明教程

    此教程是我花了一点时间和功夫整理出来的,希望能够帮到喜欢Lisp(Common Lisp)的朋友们.本人排版很烂还望多多海涵! <Lisp简明教程>PDF格式下载 <Lisp简明教程 ...

随机推荐

  1. 关于 Data URI Scheme -- data:image/jpg;base64

    转载一篇大神的文章 大家可能注意到了,网页上有些图片的src或css背景图片的url后面跟了一大串字符,比如: data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqA ...

  2. java应用零停机,时间索引重建(reindex)

    一个field的设置是不能被修改的,如果要修改一个Field,那么应该重新按照新的mapping,建立一个index,然后将数据批量查询出来,重新用bulk api写入index中 批量查询的时候,建 ...

  3. centos7.2 mysql5.5编译安装

    环境 centos7.2 源码包mysql5.5.38 mysql5.5开始,源码配置编译工具configure变成了cmake,所以先要去把cmake装上.并安装make,bison,cmake,g ...

  4. ETL : kettle Spoon 转换 + 作业

    Kettle能做什么? 前言 : 需将db2中数据导入到mysql中,利用etl工具进行多表转换.以此为切入点,系统整理.学习kettle工具. 提醒: kettle是纯java编写,机器需要有jre ...

  5. ansible的安装及基本使用

    1.安装ansible 如果没有版本和别的要求,这里直接使用yum安装 yum -y install ansible 查看版本 [root@ ~]#ansible --version ansible ...

  6. ls 命令查看文件时候,按修改时间倒序或升序排列

    1,按照时间升序 命令:ls -lrt 详细解释: -l use a long listing format 以长列表方式显示(详细信息方式) -t sort by modification time ...

  7. abp中linq的应用

    private IQueryable<MembershipEntity> SelectOrScrrenMember(GetMemberInput input) { string[] use ...

  8. vue 点击当前的标签,获取当前标签的value值

    点击当前的标签,获取当前标签的value值 html <p class="title" v-for="(item, i) in items" :key=& ...

  9. Linux下安装zookeeper集群(奇数个)

    1.  解压zookeeper压缩包 2.  data里创建“myid”文件(命令touch myid),内容是1(命令 echo 1 >> myid) 3.  zoo.cnf里配置dat ...

  10. zyb的面试(广工14届比赛)

    这道题目在上半年ZOJ模拟上年青岛赛区ACM题的时候就已经出现了.当时我不会写,本来想着赛后补题的最后因为懒惰又没补. 现在这道题又出现了.这是上天对我的惩罚啊!!! 所以这次铁了心也要补这题.然后我 ...