引言:
Groovy 是一种基于 Java 平台的动态编程语言(指在运行时进行类型检查的语言。在使用动态语言编写程序时,变量的类型不需要在声明时明确指定,而是在运行时根据赋给变量的值来确定类型。动态语言在代码执行过程中会进行类型检查)。它旨在简化和增强 Java 开发,提供了更简洁的语法、闭包、动态类型等特性。Groovy 可以无缝地与 Java 代码互操作,并且可以直接使用 Java 库。下面我们来学习一下Groovy的基础
注:需要有一定的java基础哦
Groovy开发环境配置
- 安装Intellij IEDA开发工具:Download IntelliJ IDEA -- The Leading Java and Kotlin IDE
- 下载Groovy SDK开发工具:The Apache Groovy programming language - Download
- 将Groovy SDK开发工具下的bin文件配置到环境变量中,eg:D:\apache-groovy-sdk-4.0.24\groovy-4.0.24\bin
- 检查是否安装成功:groovy -version
学一门语言,第一步该干什么?
-
那就是HelloWorld!了
-
Groovy中可以直接使用java库,也可以直接写java语法
-
例如:
GroovyHelloWorld.groovy class HelloWorld{ public static void main(String[] args){ System.out.println("Hello World!"); } }
-
在Groovy中可以直接调用方法不用写类、main方法
-
最终版本:
GroovySystem.out.println("Hello World!") 或 println("Hello World!") 或 println "Hello World!"
变量
-
java是一种强类型语言,Groovy既是强类型语言,也是弱类型语言
- 强类型:在定义变量时,必须声明其类型,并且后续不能更改其类型
- 弱类型:在定义变量时,无需声明其类型,会自动推断出其类型,并且可以修改
-
Groovy中使用def来定义弱类型变量,也可以省略def
Groovy在java中: int a = 1; a = "abc"; //编译错误,类型不匹配 在Groovy中: 使用java的肯定是不行的 int a = 1; a = "abc";//编译错误,类型不匹配 使用def def a = 2 a = "groovy" 省略def a = 2 a = "groovy"
-
Groovy中的基本数据类型都是以对象的形式存在的,万物皆对象
-
证明:
Groovyint x=1 double y=3.14 char ch='a' boolean flag=true; println x.class //class java.lang.Integer println y.class //class java.lang.Double println ch.class //class java.lang.Character println flag.class //class java.lang.Boolean def x=1 def y=3.14D def ch='a' def flag=true println x.class //class java.lang.Integer println y.class //class java.lang.Double println ch.class //class java.lang.String println flag.class //class java.lang.Boolean
-
尽量使用def来定义弱类型变量,因为直接使用x = 1更像赋值操作
字符串
-
在 Groovy 中,字符串可以使用单引号、双引号和三引号来表示
-
证明:
Groovydef s1='groovy' def s2="groovy" def s3='''groovy''' println s1.class //class java.lang.String println s2.class //class java.lang.String println s3.class //class java.lang.String
-
有什么区别呢?
-
单引号:用于定义普通的字符串,不支持插值,即不支持在字符串中嵌入变量或表达式,就相当于java的" "字符串
-
双引号:用于定义支持插值的字符串,可以在字符串中嵌入变量或表达式,使用
${}
语法。 -
三引号:用于定义多行字符串,支持插值。它允许字符串跨越多行,且保持格式
Groovydef name = 'Groovy' def message1 = 'Hello, ${name}!' // 单引号不支持插值 println message1 // 输出 Hello, ${name}! def message2 = "Hello, ${name}!" println message2 //输出 Hello, Groovy! ======================================================== 实现下面这种效果: Hello, Groovy! Welcome to the world of Groovy. 单引号: def nam1 = 'Groovy' def name2 = 'Hello,'+nam1+'\nWelcome to the world of Groovy.' 双引号: def name = 'Groovy' def message = "Hello,${name}\nWelcome to the world of Groovy." println message 三引号:输出多行字符串,保持格式 def name = 'Groovy' def message = """ Hello, ${name}! Welcome to the world of Groovy. """ 输出结果: Hello, Groovy! Welcome to the world of Groovy.
-
从例子中可以看出,三引号很灵活吧,我们需要换行时,不需要使用\n
闭包Closure
-
在Groovy中,闭包是一种类似于匿名函数的概念,就是一个使用花括号包围的代码块,它可以被赋值给变量或作为参数传递给其他函数,闭包的类型为Closure
-
基本语法:{参数列表 -> 代码块} ,其中如果没有参数时,(参数列表 ->) 可省略
-
无参数闭包、带参数闭包:
Groovy//无参数的闭包 def closure1={ println "hello groovy!" } //使用 closure1() //带参数的闭包 def closure2={String name,int age-> println "hello ${name}:age ${age}" } //使用 closure2("wjb",18)
-
如果闭包只有一个参数,可以使用隐式参数
it
,而不需要显式声明参数。Groovydef greet = { println "Hello, ${it}!" } greet("Groovy") // 输出 Hello, Groovy!
-
闭包的返回值:
Groovydef closure={ println "hello ${it}" return "123" //可以省略return } def result=closure("groovy") println "result="+result
-
闭包的类型是Closure,Closure实现了Runnable、Callable接口,Closure实现了call方法和run方法
Groovypublic abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {}
-
我们可以使用call、run方法来调用我们的闭包
Groovydef closure1={ println "hello groovy!" } closure1.run() //hello groovy! closure1.call() //hello groovy!
-
为什么呢?
-
我们先来看看run方法内部干了什么:
-
可以看到run方法调用了call方法
-
所以我们来看看call方法干了什么
-
可以看到call方法调用了传递任意数量的参数的call方法,最后找到当前的方法doCall,并调用
-
难道是Closure的docall方法?不对,因为Closure内部的docall方法最终还是调用了传递任意数量的参数的call方法,那是不是我们定义的闭包中有docall方法呢?我们来看看class文件(Groovy也是在JVM上运行的,也就是说,Groovy代码会编译成字节码,然后在JVM上执行)
-
可以看到,我们写了一小段代码,但是class文件有很多内容,这都是Groovy编译器帮我完成的,编译器会自动帮你创建一个和文件名同名的类,把你的代码放入run方法,并在main方法中调用run
-
从class文件中可以看到_run_closure1继承了Closure类,所以call方法中,会调用到_run_closure1的docall方法
-
闭包可以作为方法的参数传递
-
大家可以想一下下面这段代码,是java语法还是Groovy语法呢?
Groovyint x=fab(5) int fab(int number){ int result=1; 1.upto(number,{num -> result *= num}) return result } println x;
-
当然是Groovy了,因为方法内有闭包,闭包是Groovy的语法,我们要记住Groovy 可以无缝地与 Java 代码互操作,并且可以直接使用 Java 库,并且可以直接使用java的语法
-
这个代码中我们调用了int的api upto(用来迭代从当前数字到目标数字,类似于 Java 中的
for
循环,但更加简洁和直观。) -
upto方法的参数需要传递一个Closure
-
也可以先定义closure再传入,但是不能在fab方法外部定义,必须定义在fab方法作用域之内
Groovyint fab(int number) { int result = 1 def closure = { num -> result *= num } 1.upto(number, closure) return result }
-
当方法的最后一个参数是Closure时,可以将闭包放在方法调用的括号之外
Groovyint fab(int number) { int result = 1 1.upto(number){ num -> result *= num } return result }
-
闭包中有三个关键变量:this、owner、delegate,这些变量在闭包中用来引用不同的上下文对象
-
从源码中可以看出owner、delegate是Closure类的两个成员变量,并且是在构造方法中进行赋值的
-
this
变量指向定义闭包的类(即包含闭包的类)。-
代码:
Groovyclass A{ class B{ void run(){ def closure = { println "run:"+this //run:org.example.study_1.closure.A$B@3b74ac8 } closure() } } void print(){ new B().run() def closure = { println "print:"+this //print:org.example.study_1.closure.A@7d286fb6 } closure() } } new A().print()
-
代码解释:run方法的closure闭包是定义在B类中的,所以this指向的是B的实例;print方法的closure闭包是定义在A类中的,所以this指向的是A的实例
-
证明:看看对应的class文件
-
我们先看看A类的print方法,可以看到我们定义一个闭包,groovy编译器会为我们创建一个Closure对象,那在java中Object closure = new _print_closure1(this, this);的this是指什么?是不是指向当前这个对象--》A的实例,所以print方法的closure闭包的this指向的是A的实例,那么owner、delegate的值是不是和this相同,是相同的,大家可以测试一下
-
我们再来看看B类的run方法,原理是不是和上面的一样?对的
-
-
总结:this指向的是闭包外第一个类的实例
-
-
owner
变量指向定义闭包的对象,可能是类或闭包。-
代码:
Groovyclass Example { void run() { def nestedClosure = { def innerClosure = { println "innerClosure owner: " + owner //org.example.study_1.closure.Example$_run_closure1@6aa61224 } innerClosure() println "nestedClosure owner: " + owner //org.example.study_1.closure.Example@30c8681 } nestedClosure() } } new Example().run()
-
代码解释:
owner
变量指向定义闭包的对象,先看看innerClosure闭包的owner,innerClosure闭包是定义在nestedClosure闭包内的,那么owner和nestedClosure指向同一个类的实例;那nestedClosure闭包的owner呢?它是定义在Example类内的,所以owner指向的是Example的实例 -
证明:看看对应的class文件
-
我们先看innerClosure闭包,Groovy编译器帮我们创建了对象,在java中,Object innerClosure = new _closure2(this, this.getThisObject());的this指的是哪个类的实例?是_run_closure1这个类吧;我们再来看看nestedClosure闭包,在java中,Object nestedClosure = new _run_closure1(this, this);的this指的是哪个类的实例?是Example这个类吧。
-
-
总结:如果闭包定义在类中,则owner指向的是类的实例;如果闭包外还是闭包,则owner指向的是外层闭包的实例对象
-
-
delegate
变量指向代理(任意)对象,默认情况下与owner
相同,但可以被显式更改。-
代码1:默认情况下,
delegate
和owner相同Groovyclass Example { void run() { def nestedClosure = { def innerClosure = { println "innerClosure owner: " + owner //org.example.study_1.closure.Example$_run_closure1@6aa61224 println "innerClosure delegate: " + delegate //org.example.study_1.closure.Example$_run_closure1@6aa61224 } innerClosure() println "nestedClosure owner: " + owner //org.example.study_1.closure.Example@30c8681 println "nestedClosure delegate: " + delegate //org.example.study_1.closure.Example@30c8681 } nestedClosure() } } new Example().run()
-
代码解释:默认情况下,owner和delegate是相同的
-
证明:从构造方法中可以看出,owner直接赋值给了delegate
-
代码2:修改
delegate
Groovy//修改默认的delegate对象 class Person { } Person p=new Person(); def nestClouser = { def innerClouser = { println "innerClouser:" + this //org.example.study_1.closure.ClosureTest4@6cb6decd println "innerClouser:" + owner //org.example.study_1.closure.ClosureTest4$_run_closure1@40317ba2 println "innerClouser:" + delegate //org.example.study_1.closure.Person@3c01cfa1 } innerClouser.setDelegate(p) //修改delegate innerClouser.call() } nestClouser.call()
-
代码解释:修改delegate的指向为Person
-
证明:Closure只提供了setDelegate方法,并没有提供setOwner方法
-
总结:在默认情况下delegate是等于owner的,delegate可以被修改
-
-
闭包的委托策略:闭包的委托策略决定了闭包在查找属性和方法时的优先级。Groovy 提供了几种不同的委托策略,可以通过
resolveStrategy
属性来设置 -
委托策略,默认策略是Closure.OWNER_FIRST
-
Closure.OWNER_FIRST,优先级:owner > delegate,闭包首先在其owner上查找属性和方法,如果找不到,则在delegate上查找
-
Closure.DELEGATE_FIRST,优先级:delegate > owner,闭包首先在其delegate上查找属性和方法,如果找不到,则在owner上查找
-
Closure.OWNER_ONLY,闭包仅在其owner上查找属性和方法,忽略delegate
-
Closure.DELEGATE_ONLY,闭包仅在其delegate上查找属性和方法,忽略owner
-
Closure.TO_SELF,闭包仅在其自身上查找属性和方法,忽略owner和delegate
-
-
代码:
Groovyclass Student{ String name def pretty={"My name is ${name}"} String toString(){ pretty.call() } } def student=new Student(name: "groovy") //Groovy编译器会帮我们自动添加一个构造方法 class Teacher{ String name } def teacher=new Teacher(name:'andy') println 'pretty: '+student.toString() // My name is groovy println 'delegate: '+student.pretty.delegate //delegate: com.example.Student@<hashcode> student.pretty.delegate=teacher println 'delegate: '+student.pretty.delegate //org.example.study_1.closure.Teacher@563e4951 //闭包的委托策略 student.pretty.resolveStrategy=Closure.DELEGATE_FIRST println 'pretty: '+student.toString() //pretty: My name is andy
-
代码解释:我们这里使用的是Closure.DELEGATE_FIRST策略,那么它就会先从delegate中查找属性和方法
Gradle中常用的数据结构
-
List:
-
定义:
Groovy//使用ArrayList def list=new ArrayList() //使用Groovy def list=[1,2,3,4,5] println list.class //class java.util.ArrayList
-
常用方法:
Groovy//list大小 println list.size() ===========添加元素=========== //使用add方法添加元素 list.add(6) //使用groovy的<<添加元素 list<<2 //也可以使用+添加元素 def plusList=list+5 //指定下标,添加元素 plusList.add(3,9) ===========删除元素=========== //删除下标位置的元素 list.remove(2) //删除指定的元素 list.removeElement(2) //删除符合条件的元素 list.removeAll{ return it%2!=0 } //使用-删除元素 println list-[2,3,4] //将所有数值为2,3,4的全部remove ===========查找元素=========== //查找满足条件的第一个数据 int result=findList.find{ return it%2 == 0 } //查找所有满足条件的数据 def result2=findList.findAll({ return it%2 !=0 }) //查找是否有满足条件的数据 def result3=findList.any{ return it%2 ==0 } //查找是否全部满足条件 def result4=findList.every{ return it%2 ==0 } //查找最大值与最小值 def result5=findList.min{ return it } def result6=findList.max{ return it } //统计满足条件的元素个数 int result7=findList.count{ return it>0 } ===========排序元素=========== //升序 sortList.sort() //降序 sortList.reverse() //根据条件排序 sortList2.sort{ it.length() } ===========遍历元素=========== def list=[1,2,3,4,5] //传统的for循环 //for-in循环 for (element in list) { println element } //each方法 list.each { element -> println element } //eachWithIndex方法 list.eachWithIndex { element, index -> println "Index $index, Value $element" } //iterator方法 def iterator = list.iterator() while (iterator.hasNext()) { println "Iterator: ${iterator.next()}" }
-
-
Map
-
定义:
Groovy//使用java def map = new HashMap<String,Integer>() //使用Groovy //定义一个<Integer,String> def map = [1:"one",2:"two"] println map.getClass() //class java.util.LinkedHashMap //定义一个<String,String> def colors=[red:'ff0000',green:'00ff00',blue:'0000ff'] //会将red转换成String //可以强转为HashMap def colors=[red:'ff0000',green:'00ff00',blue:'0000ff'] as HashMap
-
常用方法:
Groovydef colors=[red:'ff0000',green:'00ff00',blue:'0000ff'] //使用key获取value println colors['red'] 或 println colors.red ===========添加元素=========== //使用put方法 //使用. colors.yellow='ffff00' //往map中再添加一个map colors.map = [key1:1,key2:2] ===========移除元素=========== //使用remove方法 colors.remove(key) ===========遍历元素=========== //使用each teachers.each { key, value -> println "key=${key}---value=${value}" } //带索引 teachers.eachWithIndex{ def key,def value,int index-> println "index=${index}---key=${key}---value=${value}" } ===========查找元素=========== //查询符合条件的元素 def entry=teachers.find{def teacher -> return teacher.value.name=='groovy' } //查询符合条件的所有元素 def entry=teachers.findAll{def teacher -> return teacher.value.name=='groovy' } //查找符合条件的元素个数 def count=teachers.count{def teacher -> return teacher.value.name=='groovy' } ===========排序元素=========== //注意:map会返回一个新的map list是在原来的list中进行排序 def sort=teachers.sort{def t1,def t2 -> return t1.key > t2.key ? 1 : -1 }
-
-
Range
-
在 Groovy 中,
Range
是一个非常实用和灵活的特性,可以用来表示一系列连续的值。这些值可以是数字、字符等。 -
定义:
Groovy//数字范围 def numberRange = 1..5 //字符范围 def charRange = 'a'..'e'
-
常用方法:
Groovydef range=1..10 //获取指定下标的元素 println range[0] //是否包含某元素 println range.contains(8) //使用in println 3 in range //起点 println range.from //终点 println range.to ===========遍历元素=========== //使用each range.each { println it } 使用for-in for(i in range){ println i } ===========switch-case=========== def getGrade(Number score){ def result switch(score){ case 0..<60: result='不及格' break; case 60..100: result='及格' break; default: result='输入异常' } return result } println getGrade(50) println getGrade(80) println getGrade(120)
-
面向对象语法
-
在Groovy中,所有的类都实现了GroovyObject接口
-
在Groovy中,所有类型默认都是public
-
在Groovy中,万物皆对象
Groovyint x=1 double y=3.14 char ch='a' boolean flag=true; println x.class //class java.lang.Integer println y.class //class java.lang.Double println ch.class //class java.lang.Character println flag.class //class java.lang.Boolean
-
Groovy会自动为属性提供set/get方法,并且会将属性私有化:
Groovyclass Person{ String name Integer age } //使用 def person = new Person() person.setName('Alice') person.setAge(18) println 'name:'+ person.getName() //name:Alice println 'age:' + person.getAge() //age:18
- 证明:
- 证明:
-
Groovy中特有的trait关键字,类似于接口
-
为什么类似于接口?
- 看看它的class文件就知道了
- 可以看到DefaultAction1最后被编译成了interface
- 看看它的class文件就知道了
-
trait可以包含方法(抽象方法、具体方法、私有方法)、属性
Groovytrait DefaultAction { def actionName = 'trait' int step= 10 abstract void eat() //void eat() //不允许,接口中才可以 void play(){ println 'I can play!' } private void test(){ println 'test()' } }
-
trait中的方法冲突:如果一个类实现了多个
trait
,并且这些trait
中有同名方法,Groovy会要求类明确指定使用哪个trait
的方法,或者覆盖该方法。Groovytrait A { void greet() { println "Hello from A" } } trait B { void greet() { println "Hello from B" } } class C implements A, B { void greet() { A.super.greet() // 明确调用A trait中的greet方法 } } def c = new C() c.greet() // 输出: Hello from A
-
trait中可以有静态方法,接口没有
Groovytrait Logger { static void log(String message) { println "[LOG] $message" } } class LoggerTest implements Logger{ } LoggerTest.log("wq") //Logger.log("wq") //这种是不允许的
-
trait有构造方法和静态代码块,接口并没有
Groovytrait Initializable { { println "Initializing trait" } static { println 'static' } } class MyClass implements Initializable { MyClass() { println "Initializing class" } } def obj = new MyClass() //输出: static Initializing trait Initializing class
-
trait还有一些注解,例如:@SelfType----注解用于限制
trait
只能被特定类型的类实现、@Delegate----注解可以将trait
中的方法委托给另一个对象等等,这里就不一一介绍了 -
总结:trait类似于接口,功能比接口多
-
JSON解析
-
使用Gson:com.google.code.gson:gson:2.8.9
GroovyGson gson = new Gson(); Person p1 = new Person(name:"jack",age:18) String json = gson.toJson(p1) println "json:$json" Person p2 = gson.fromJson(json, Person.class); String jsonOutput = gson.toJson(p2); println "jsonOutput:$jsonOutput" //{"name":"jack","age":18}
-
使用Groovy自带的json工具:JsonOutput
Groovy//对象转成json字符串 def list=[new Person(name:'jack',age:18), new Person(name:'Alice',age:18)] println JsonOutput.toJson(list) //[{"name":"jack","age":18},{"name":"Alice","age":18}] //格式化 def json=JsonOutput.toJson(list) println JsonOutput.prettyPrint(json) //json字符串转成对象 def jsonSluper=new JsonSlurper() def object=jsonSluper.parse("[{\"age\":18,\"name\":\"jack\"},{\"age\":18,\"name\":\"Alice\"}]".getBytes()) println object def object2=jsonSluper.parse("[{\"abc\":\"jack\"}]".getBytes()) println object2.abc def jsonSlurper = new JsonSlurper() def jsonString = '[{"name":"jack","age":18},{"name":"Alice","age":18}]' def jsonObject = jsonSlurper.parseText(jsonString) // 手动转换为 Person 对象 def personList = jsonObject.collect { map -> new Person(name: map.name, age: map.age) } println personList // 输出 [Person(name: jack, age: 18), Person(name: Alice, age: 18)]
XML解析
-
使用Groovy自带的XmlSlurper解析xml:
Groovyfinal String xml=''' <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.jvm_demo_20200601"> <test>12345</test> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".MainActivity2"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> ''' //解析XML数据 def xmlSluper=new XmlSlurper() def result=xmlSluper.parseText(xml) println result.@package //com.example.jvm_demo_20200601 println result.test.text() //12345 //读取有域名空间的节点 result.declareNamespace('android':'http://schemas.android.com/apk/res/android') println result.application.@'android:allowBackup' //true println result.application.activity[0].@'android:name' //.MainActivity println result.application.activity[1].@'android:name' //.MainActivity2 //遍历XML节点 result.application.activity.each{activity -> println activity.@'android:name' }
-
使用Groovy自带的MarkupBuilder生成xml格式数据
Groovy/** * 生成XML格式数据 * <html> * <title id='123',name='android'>xml生成 * <person></person> * </title> * <body name='java'> * <activity id='001' class='MainActivity'>abc</activity> * <activity id='002' class='SecActivity'>abc</activity> * </body> * </html> */ def sw=new StringWriter() def xmlBuilder=new MarkupBuilder(sw) xmlBuilder.html(){ title(id:'123',name:'android','xml生成'){ person() } body(name:'java'){ activity(id:'001',class:'MainActivity','abc') activity(id:'002',class:'SecActivity','abc') } } println sw
文件操作
Groovy
def file=new File("D:\\JAVA\\Study_Groovy\\test.txt")
//遍历文件
file.eachLine { line ->
println line
}
//返回所有文本
def text=file.getText()
println text
//以List<Stirng>返回文件的每一行
def text=file.readLines()
println text.toListString()
//以java中的流的方式读取文件内容
def reader=file.withReader{reader ->
char[] buffer=new char[100]
reader.read(buffer)
return buffer
}
println reader
//写入数据
file.withWriter { writer ->
writer.write("abc")
}