☰
Learn X in Y Minutes 系列:Groovy 动态语言完整实战入门——集合、闭包与元编程(基于 learnxinyminutes-docs)
2026/10/6 7:27:44 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】learnxinyminutes-docs

Code documentation written as code! How novel and totally my idea!

项目地址:https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs
点击查看免费下载

本文以 learnxinyminutes-docs 仓库中的 es/groovy.md(西班牙语版 Groovy 教程)为骨架,系统讲解 Groovy——这门运行于 Java 平台之上的动态语言的核心语法与高阶能力。读完本文,你将掌握 Groovy 的安装启动、动态变量、集合与映射的增删改查、Groovy Beans 的编译规则、分支迭代、实用操作符、闭包及其记忆化、Expando 动态 Bean、MOP 元编程,以及 TypeChecked/CompileStatic 静态化手段,并可直接把文中的代码示例粘贴到 groovyConsole 或脚本中运行验证。

环境准备:一分钟跑通 Groovy

Groovy 教程文档在正式进入语法之前,首先给出了最精简的启动路线(见 es/groovy.md 开头的配置注释块):

  1. 安装 GVM(Groovy enVironment Manager,Groovy 环境管理器,其后续版本即今天广泛使用的 SDKMAN),用于管理 Groovy 及其生态工具的版本;
  2. 通过 GVM 安装 Groovy,命令为gvm install groovy;
  3. 在终端输入groovyConsole启动 Groovy 的图形化控制台,即可交互式地逐行执行 Groovy 代码。

除了groovyConsole,日常更常用的是命令行方式:将代码保存为.groovy文件后直接运行groovy 文件名.groovy,或者用groovy -e "println 'Hello'"执行单行脚本。仓库根目录的英文原版 groovy.md 中给出的安装工具是 SDKMAN,对应命令为sdk install groovy,本质是同一套工具链的新旧版本名称,读者可任选其一。值得注意的是,本仓库中 Groovy 教程还有 中文版、加泰罗尼亚语版 与 葡萄牙语版,多语言版本内容保持一致,便于对照阅读。

注释与 Hello World

Groovy 的注释语法与 Java 完全兼容(见文档"注释"小节):

  • 单行注释以两条斜线//开头;
  • 多行注释使用/* ... */包裹。

入门仪式自然是 Hello World。Groovy 中println是语言的默认方法,无需引入任何包即可直接调用:

println "Hola mundo!" // 输出:Hola mundo!

相比 Java 需要完整的类声明与public static void main样板代码,Groovy 脚本直接书写语句即可运行,这正是它作为"可执行代码即文档"的轻量特性的体现——本仓库 README.md 也明确说明所有教程都以"带注释、可运行的代码"形式呈现。

动态变量:def的魔力

Groovy 是一门动态类型语言,变量不需要声明类型。文档用一个x变量连续演示了它可以在不同时刻承载完全不同的类型:

def x = 1 // 整数 println x x = new java.util.Date() // 日期对象 println x x = -3.1499392 // 浮点数 println x x = false // 布尔值 println x x = "Groovy!" // 字符串 println x

def关键字声明一个动态类型的变量,其真实类型在运行时由赋值决定;即便不用def,Groovy 脚本中直接给未声明变量赋值(如文档后文的contained = ...、displayName = ...)也会被隐式创建。这种"变量即类型"的灵活性让原型开发极其高效,也为后文的元编程能力埋下伏笔。

集合与映射实战

文档用相当大的篇幅演示了 Groovy 对列表(List)与映射(Map)的原生语法糖——它们既兼容 Java 的java.util集合 API,又提供了大量 Groovy 独有的快捷写法。

列表(List):增、删、遍历、检查、排序与操作

创建空列表只需[]:

def technologies = []

添加元素有三种方式:

// 方式一:与 Java 完全相同的 add 方法 technologies.add("Grails") // 方式二:左移运算符 << 追加元素,并返回列表本身(可链式调用) technologies << "Groovy" // 方式三:一次添加多个元素 technologies.addAll(["Gradle", "Griffon"])

<<是 Groovy 对List重载的运算符,语义等价于add,但返回值是列表自身,因此特别适合链式追加与流式构建。

删除元素也有两条路径:

// 与 Java 相同 technologies.remove("Griffon") // Groovy 风格:列表减法,返回新列表(不修改原列表) technologies = technologies - 'Grails'

遍历列表使用闭包(Closure):

technologies.each { println "Technology: $it" } technologies.eachWithIndex { it, i -> println "$i: $it" }

each中未命名参数以隐式变量it引用;eachWithIndex则额外传入下标i,闭包参数列表为{ 元素, 索引 -> ... }。

检查内容(返回布尔值):

contained = technologies.contains('Groovy') // Java 风格 contained = 'Groovy' in technologies // Groovy 的 in 操作符 technologies.containsAll(['Groovy', 'Grails']) // 同时检查多个元素

in操作符是contains的语法糖,可读性更强,日常推荐优先使用。

排序分原地排序与非破坏性排序两种:

technologies.sort() // 修改原列表 sortedTechnologies = technologies.sort(false) // 不修改原列表,返回新列表

其他操作则演示了 Groovy 与java.util.Collections工具类的无缝互操作:

// 替换列表中的所有指定元素 Collections.replaceAll(technologies, 'Gradle', 'gradle') // 用随机源打乱列表顺序 Collections.shuffle(technologies, new Random()) // 清空列表 technologies.clear()

映射(Map):创建、赋值、遍历与查询

映射(字典)是 Groovy 中与列表并列的另一大核心数据结构,创建语法极为简洁:

def devMap = [:] // 创建空映射 devMap = ['name':'Roberto', 'framework':'Grails', 'language':'Groovy'] // 字面量创建 devMap.put('lastName', 'Perez') // Java 风格添加键值

遍历映射同样基于闭包,且it是一个Map.Entry,可通过it.key与it.value访问键值:

devMap.each { println "$it.key: $it.value" } devMap.eachWithIndex { it, i -> println "$i: $it" }

查询操作包括键与值的包含性判断、键集合与值集合的获取:

assert devMap.containsKey('name') // 是否包含某键 assert devMap.containsValue('Roberto')// 是否包含某值 println devMap.keySet() // 获取所有键 println devMap.values() // 获取所有值

assert是 Groovy 内建断言关键字,条件为假时抛出异常,非常适合在脚本中做快速自检。

Groovy Beans:极简 JavaBean

文档专门用一个注释块完整列出了 Groovy 编译为字节码时对属性(property)的处理规则,这是理解 Groovy 对象模型的关键。汇总如下 8 条规则:

  1. 属性名带访问修饰符(public、private、protected)时,编译后生成的是一个普通字段(field);
  2. 属性名不带访问修饰符时,会生成一个private字段,并配套生成public的 getter 与 setter——即一个"属性(property)";
  3. 属性声明为final时,私有字段也是final,且不会生成 setter(只读属性);
  4. 可以同时声明属性和自定义的 getter/setter;
  5. 可以声明同名的属性与字段,此时属性将使用该字段;
  6. 若希望属性是private或protected,必须自行提供同样为private/protected的 getter 与 setter;
  7. 在类内部以隐式或显式this(如this.foo或直接foo)访问属性时,Groovy 在编译期直接访问字段本身,而不经过 getter/setter;
  8. 若访问一个不存在的属性(隐式或显式foo),Groovy 会经由元类(meta class)尝试解析,这一过程可能在运行时失败。

文档给出的示例类Foo集中体现了上述规则:

class Foo { // 只读属性:final 字段,无 setter final String name = "Roberto" // 只读属性:public getter + protected setter String language protected void setLanguage(String language) { this.language = language } // 动态类型属性 def lastName }

可以看到,name是final的只读属性;language默认生成 public getter,但通过自定义protected的 setter 收紧了写权限;lastName用def声明,类型完全动态。相比 JavaBean 动辄几十行的样板代码,Groovy Bean 用三行声明即可表达完整的数据模型。

逻辑分支与迭代

经典的 if-else

Groovy 完全支持 Java 风格的if-else if-else结构:

def x = 3 if (x == 1) { println "One" } else if (x == 2) { println "Two" } else { println "X greater than Two" }

三元操作符

def y = 10 def x = (y > 1) ? "worked" : "failed" assert x == "worked"

Elvis 操作符:三元表达式的捷径

当需要"取非空值,否则用默认值"时,三元写法冗长:

displayName = user.name ? user.name : 'Anonymous'

Groovy 提供了 Elvis 操作符?:,直接省略重复部分:

displayName = user.name ?: 'Anonymous'

其语义为:若user.name为真(非 null、非 false、非空字符串等 Groovy 真值规则),则取其值,否则取'Anonymous'。该操作符在配置默认值、参数兜底等场景中极为常用,是 Groovy 最具辨识度的语法特性之一。

for 循环的四种形态

Groovy 的for-in语法可以对范围、列表、数组、映射四种对象统一迭代:

// 1. 数值范围:0..30 是闭区间 Range,含 0 与 30 def x = 0 for (i in 0 .. 30) { x += i } // 2. 列表 x = 0 for (i in [5, 3, 2, 1]) { x += i } // 3. 数组:先把范围转成数组再迭代 array = (0..20).toArray() x = 0 for (i in array) { x += i } // 4. 映射:e 是 Map.Entry,用 e.value 取当前值 def map = ['name':'Roberto', 'framework':'Grails', 'language':'Groovy'] x = "" for (e in map) { x += e.value x += " " } assert x.equals("Roberto Grails Groovy ")

最后一条assert验证了遍历映射时按插入顺序逐个取出值并拼接的结果。

实用操作符

文档挑选了两个在 Groovy 中高频使用的特色操作符(完整的操作符重载清单见 Groovy 官方文档的操作符章节,此处不作外部链接展开)。

展开操作符*.

用于对聚合对象中的所有元素统一调用某个方法,等价于collect与安全导航的组合:

def technologies = ['Groovy', 'Grails', 'Gradle'] technologies*.toUpperCase() // 等价于:technologies.collect { it?.toUpperCase() }

该表达式返回一个新列表,其中每个元素都被执行了toUpperCase()。*.内部对每个元素使用了安全导航语义(?.),即使存在 null 元素也不会抛异常。

安全导航操作符?.

用于避免空指针异常(NPE):

def user = User.get(1) // 假设从数据层取用户,可能返回 null def username = user?.username // 若 user 为 null,则 username 直接为 null,而非抛 NPE

当user为 null 时,user?.username整体求值为 null,而不是抛出NullPointerException,有效消除了 Java 中大量冗长的判空样板。

Closures:闭包

闭包(Closure)是 Groovy 最核心的语法构件,可以理解为"代码块"或"方法指针"——一段被定义、稍后才执行的代码。文档给出的入门示例:

def clos = { println "Hello World!" } println "Executing the Closure:" clos() // 调用闭包,输出 Hello World!

闭包定义使用花括号{ ... },调用方式与普通方法一致(clos()或clos.call())。

传递参数:参数列表位于->左侧,多个参数以逗号分隔:

def sum = { a, b -> println a + b } sum(2, 4) // 输出 6

捕获外部变量:闭包可以引用其定义作用域中的变量(这一点不同于 Java 的 lambda),这是闭包最强大的特性之一:

def x = 5 def multiplyBy = { num -> num * x } println multiplyBy(10) // 输出 50

隐式参数it:若闭包只接收一个参数,可以省略参数声明,直接用it引用:

def clos = { print it } clos("hi") // 输出 hi

正是这个it,让前文technologies.each { println "Technology: $it" }这类遍历写法变得极为简洁。

Closure 记忆化(Memoize):缓存计算结果

Groovy 为 Closure 内置了记忆化(memoization)能力:对相同参数的调用结果会被缓存,第二次调用同一组参数时直接返回缓存值,不再重新执行闭包体。文档用一个模拟耗时处理的闭包演示了这一点:

def cl = { a, b -> sleep(3000) // 模拟耗时 3 秒的处理 a + b } mem = cl.memoize() def callClosure(a, b) { def start = System.currentTimeMillis() mem(a, b) println "Inputs(a = $a, b = $b) - took ${System.currentTimeMillis() - start} msecs." } callClosure(1, 2) // 首次调用,约 3000 msecs callClosure(1, 2) // 命中缓存,接近 0 msecs callClosure(2, 3) // 首次调用,约 3000 msecs callClosure(2, 3) // 命中缓存 callClosure(3, 4) // 首次调用,约 3000 msecs callClosure(3, 4) // 命中缓存 callClosure(1, 2) // 再次命中缓存 callClosure(2, 3) // 再次命中缓存 callClosure(3, 4) // 再次命中缓存

运行后可观察到:每个参数组合的第一次调用耗时约 3000 毫秒,之后的重复调用耗时趋近于 0 毫秒。memoize()的默认实现以参数组合为键缓存结果,此外 Groovy 还提供memoizeAtMost(n)、memoizeAtLeast(n)、memoizeBetween(min, max)等带容量上限的变体,适合在计算昂贵但结果稳定的场景(如配置解析、数据聚合)中直接使用。

Expando:动态 Bean

Expando类是一个"动态 Bean":可以像操作普通对象一样为其实例动态添加属性,甚至把闭包作为方法挂到实例上(见文档 Expando 小节):

def user = new Expando(name: "Roberto") assert 'Roberto' == user.name // 动态添加属性 user.lastName = 'Pérez' assert 'Pérez' == user.lastName // 动态添加方法(闭包),闭包内可直接引用 this 上的属性 user.showInfo = { out -> out << "Name: $name" out << ", Last name: $lastName" } def sw = new StringWriter() println user.showInfo(sw)

构造时传入的name:"Roberto"会被直接转为属性;随后lastName在运行期被动态创建;showInfo则是一个以闭包形式挂载的"方法",内部通过隐式this访问name与lastName。StringWriter接收闭包写入的内容,最终输出Name: Roberto, Last name: Pérez。Expando 非常适合需要临时组装带行为的轻量对象的场景(如测试替身、动态配置对象)。

元编程(MOP):Metaprogramming

Groovy 的元对象协议(Meta Object Protocol, MOP)允许在运行时动态修改类的行为,这是它区别于普通 Java 的标志性能力。文档依次展示了三种 MOP 用法。

ExpandoMetaClass:给既有类动态加方法

// 给所有 String 实例动态添加 testAdd 方法 String.metaClass.testAdd = { println "we added this" } String x = "test" x?.testAdd() // 输出:we added this

通过类的.metaClass.方法名 = 闭包,无需修改 String 类源码,即可为整个类注入新方法(此处挂载在String上,影响所有字符串实例)。?.保证即使x为 null 也不会抛异常。

GroovyInterceptable:拦截全部方法调用

实现GroovyInterceptable接口并覆写invokeMethod,类上的一切方法调用都会被拦截:

class Test implements GroovyInterceptable { def sum(Integer x, Integer y) { x + y } def invokeMethod(String name, args) { System.out.println "Invoke method $name with args: $args" } } def test = new Test() test?.sum(2, 3) // 不执行 sum 本体,输出:Invoke method sum with args: [2, 3] test?.multiply(2, 3) // 连不存在的方法也会被拦截

注意拦截后sum的实际计算逻辑被invokeMethod完全接管,因此输出的是拦截日志而非5;multiply方法并不存在,同样被统一拦截处理。这种机制是 AOP(面向切面编程)在语言层面的直接体现。

propertyMissing:处理属性解析失败

当访问对象上不存在的属性时,Groovy 会回调propertyMissing方法(见文档最后一段示例):

class Foo { def propertyMissing(String name) { name } } def f = new Foo() assertEquals "boo", f.boo // 访问不存在的 boo 属性,返回属性名 "boo"

f.boo本不存在,但被propertyMissing捕获并返回属性名"boo",从而避免了运行时异常。类似的还有methodMissing用于拦截不存在的方法调用。这一机制被广泛用于构建 DSL(领域特定语言),例如让脚本中的自由标识符映射到数据字段。

TypeChecked 与 CompileStatic:为动态语言引入静态检查

Groovy 本质上是且始终是动态语言,但它同时提供了@TypeChecked与@CompileStatic两个编译期注解,让开发者按需获得静态类型检查甚至静态编译的性能收益。

@TypeChecked:编译期类型检查

@TypeChecked只做编译期检查,不改变运行时的动态行为。文档第一个示例故意写了拼写错误的标识符:

import groovy.transform.TypeChecked void testMethod() {} @TypeChecked void test() { testMeethod() // 拼写错误,编译期报错 def name = "Roberto" println naameee // 拼写错误,编译期报错 }

在普通动态模式下,testMeethod()与naameee这类错误要等到运行时才暴露;加上@TypeChecked后,编译器会直接判定其为未定义的方法/变量并报错,将错误前移到编译期。

第二个示例展示了类型不符时的检查:

import groovy.transform.TypeChecked @TypeChecked Integer test() { Integer num = "1" // 字符串赋给 Integer,编译期报错 Integer[] numbers = [1, 2, 3, 4] Date date = numbers[1] // Integer 赋给 Date,编译期报错 return "Test" // 返回 String 与声明返回类型 Integer 不符 }

@CompileStatic:静态编译

@CompileStatic则在@TypeChecked的基础上更进一步,让方法按静态编译方式生成字节码,牺牲部分动态能力换取更优的性能:

import groovy.transform.CompileStatic @CompileStatic int sum(int x, int y) { x + y } assert sum(2, 5) == 7

对于性能敏感的热点路径(如数值计算、频繁调用的工具方法),@CompileStatic是 Groovy 项目中的常见优化手段。实践中可以遵循"整体动态、热点静态"的策略:业务脚本保持动态以享受灵活性,关键计算路径加上静态注解换取性能。

延伸学习与仓库资源

本文内容完整对应仓库中的 es/groovy.md,如果你想继续深入,仓库内还提供以下同主题资源:

  • groovy.md:英文原版教程,内容与本篇基本一致,额外包含"带默认参数的方法"一节,展示 Groovy 对方法默认参数值的支持(def say(msg = 'Hello', name = 'world')可分别以零参、一参、两参三种方式调用);
  • zh-cn/groovy.md:中文版教程,适合快速对照查阅;
  • ca/groovy.md 与 pt-br/groovy.md:加泰罗尼亚语与葡萄牙语版本,多语言并行验证同一份语法事实。

在书籍层面,文档推荐了经典学习资料,包括持续更新的 Groovy 技巧合集《Groovy Goodness》、体系完整的《Groovy in Action》(第二版)以及面向 Java 开发者讲动态生产力的《Programming Groovy 2: Dynamic Productivity for the Java Developer》。这些书籍与本文的语法速览互为补充,前者讲技巧细节,后者讲完整方法论,适合在入门后按需选读。若要在团队中系统引入 Groovy,官方文档、官方用户组与在线控制台都是文档原文推荐的延伸去处(此处按仓库规范不再展开外部链接,可自行在 Groovy 官网检索)。

  • 文档
  • 教程

【免费下载链接】learnxinyminutes-docs

Code documentation written as code! How novel and totally my idea!

项目地址:https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs
点击查看免费下载
上一篇:FIO项目中的MD5校验失败问题分析与解决方案
下一篇:Weighted-Boxes-Fusion算法原理深度解析:从理论到实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询