- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
本文以 learnxinyminutes-docs 仓库中的 es/groovy.md(西班牙语版 Groovy 教程)为骨架,系统讲解 Groovy——这门运行于 Java 平台之上的动态语言的核心语法与高阶能力。读完本文,你将掌握 Groovy 的安装启动、动态变量、集合与映射的增删改查、Groovy Beans 的编译规则、分支迭代、实用操作符、闭包及其记忆化、Expando 动态 Bean、MOP 元编程,以及 TypeChecked/CompileStatic 静态化手段,并可直接把文中的代码示例粘贴到 groovyConsole 或脚本中运行验证。
环境准备:一分钟跑通 Groovy
Groovy 教程文档在正式进入语法之前,首先给出了最精简的启动路线(见 es/groovy.md 开头的配置注释块):
- 安装 GVM(Groovy enVironment Manager,Groovy 环境管理器,其后续版本即今天广泛使用的 SDKMAN),用于管理 Groovy 及其生态工具的版本;
- 通过 GVM 安装 Groovy,命令为
gvm install groovy; - 在终端输入
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 xdef关键字声明一个动态类型的变量,其真实类型在运行时由赋值决定;即便不用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 条规则:
- 属性名带访问修饰符(
public、private、protected)时,编译后生成的是一个普通字段(field); - 属性名不带访问修饰符时,会生成一个
private字段,并配套生成public的 getter 与 setter——即一个"属性(property)"; - 属性声明为
final时,私有字段也是final,且不会生成 setter(只读属性); - 可以同时声明属性和自定义的 getter/setter;
- 可以声明同名的属性与字段,此时属性将使用该字段;
- 若希望属性是
private或protected,必须自行提供同样为private/protected的 getter 与 setter; - 在类内部以隐式或显式
this(如this.foo或直接foo)访问属性时,Groovy 在编译期直接访问字段本身,而不经过 getter/setter; - 若访问一个不存在的属性(隐式或显式
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!
相关推荐
Learn X in Y Minutes:HTML5 标记语言实战指南(基于 learnxinyminutes-docs 阿拉伯语版精讲)
Learn X in Y Minutes:HTML5 标记语言实战指南(基于 learnxinyminutes docs 阿拉伯语版精讲) HTML(Hyper
文档教程Learn X in Y minutes 之 JavaScript:基于 cs/javascript.md 的语言核心完整实战指南
Learn X in Y minutes 之 JavaScript:基于 cs/javascript.md 的语言核心完整实战指南 本文以 learnxinym
文档教程Learn Groovy in Y Minutes:面向 Java 开发者的 Groovy 动态语言速成指南
Learn Groovy in Y Minutes:面向 Java 开发者的 Groovy 动态语言速成指南 Groovy 是运行于 Java 平台(JVM)之
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考