作者:renxhui
链接:https://juejin.cn/post/6932778923491065864
配置Groovy环境
由于Groovy是运行在java虚拟机上的,所以首先要确定你的电脑有java环境
Groovy 语法学习
- Groovy 的注释和java一样
// 或者/**/
- Groovy语句可以不用分号结尾,这个其实是为了代码写起来更加简洁
- Groovy中支持动态类型,就是
定义变量的时候可以不定义类型
,Groovy中定义变量可以使用def
关键字,虽然def不是必须的,但是还是建议使用def,让代码更加清晰
def variable1 = 1 //可以不使用分号结尾
def varable2 = "I am a person"
def int x = 1 //变量定义时,也可以直接指定类型
- 函数定义时,参数的类型也可以不指定
String testFunction(arg1,arg2){//无需指定参数类型 ...
}
- 除了定义变量不指定类型外,Groovy中函数的返回值也可以是无类型的
//无类型的函数定义,必须使用 def 关键字
def nonReturnTypeFunc(){
last_line //最后一行代码的执行结果就是本函数的返回值
}
//如果指定了函数返回类型,则可不必加 def 关键字来定义函数
String getString(){
return "I am a string"
}
- 函数返回值:Groovy的函数里,可以不使用
ruturn
,如果不使用return的话,那么函数中最后一句代码执行结果设置为返回值
//下面这个函数的返回值是字符串"getSomething return value"
def getSomething(){
"getSomething return value" //如果这是最后一行代码,则返回类型为 String
1000 //如果这是最后一行代码,则返回类型为 Integer
}
如果函数指明了返回值的类型,那么就必须返回正确的类型,否则就报错,如果返回值是动态类型,才可以返回任意数据类型
- Groovy对字符串支持很强大
1 单引号''中的内容严格对应 Java 中的 String,不对$符号进行转义
def singleQuote='I am $ dolloar' //输出就是 I am $ dolloar
2 双引号""的内容则和脚本语言的处理有点像,如果字符中有$号的话,则它会$表达式先求值。
def doubleQuoteWithoutDollar = "I am one dollar" //输出 I am one dollar
def x = 1
def doubleQuoteWithDollar = "I am $x dolloar" //输出 I am 1 dolloar
3 三个引号'''xxx'''中的字符串支持随意换行 比如
def multieLines = ''' begin
line 1
line 2
end '''
- 最后除了每行代码不用加分号外,Groovy中函数调用的时候还可以不加括号
println("test") ---> println "test"
注意:虽然写代码的时候可以函数调用不带括号,但是函数调用如果是Groovy的API那么可以不带括号,如:print方法,否则还是需要带括号的
,因为Groovy对此的支持还是有一些问题的
Groovy中的数据类型
这里我们只介绍和java中不太一样的东西
基本数据类型
作为动态语言,Groovy中所有的事物都是对象,所以int,boolean这些java中的基本数据类型,在Groovy中其实对应的是他们包装数据类型,比如 int - integer,boolean-Boolean
容器类
Groovy中容器其实就三种
- List:链表,其底层对应java中的List接口,一般用ArrayList作为实现类
- Map:键-值表,其底层对应java中的LinkedHashMap
- Range:范围,他其实是List的一种扩展
List
class Example {
static void main(String[] args) {
//定义list,可以是任何类型的元素
def list = [2,"吃",true]
//list元素赋值,不用担心数组越界,如果超过长度list会自动向该索引填充
list[4]="tt"
//遍历list
for (a in list) {
println a;
}
}
}
Map
Map由[:]定义,冒号左边是key,右边是value,key必须是字符串,value可以是任何的对象,key可以加'',也可以不加,比如 def map = [key1: "测试",key2: true];
这样系统会自动处理成字符串 'key','key'
class Example {
static void main(String[] args) {
//定义map
def map = ['key1': "测试",'key2': true];
//访问map中的值
println map.key2;
println map['key1'];
//定义新的map的key
map.anotherkey = "another";
println map.anotherkey;
}
}
看下输出
$ groovy map.groovy
true
测试
another
如果不加''的话,map的key和变量同名怎么办呢?看下面例子,也就是说直接使用aa
就是简单的字符串,如果需要取变量的值需要用(aa)
的方式取
def aa ="测试";
def map1 = [aa:"11",(aa):22];
println map1.aa;
println map1."测试";
输出
11
22
Range类
Range类是对List的一种扩展
class Text{
static void main(String[] args) {
//包含12345
def range = 1..5;
println range.from;
println range.to;
//包含1234个元素
def range1 = 1..<5;
println range1.from;
println range1.to;
}
}
查看Groovy API技巧
真正写代码的是时候还是要查看 Groovy SDK API的,因为我们不可能记住全部的用法吧
Groovy API文档的地址 www.groovy-lang.org/api.html
我们以上面的Range为例,首先需要定位到Range类,
在API文档查找你需要的使用的函数和属性就行,但是我们发现,这个文档里面并没有,上面使用的from和to
属性的介绍,这是为什么?
我们发现索然没有 from/to属性的介绍,但是却有 getFrom 和 getTo 这俩个函数
原来根据Groovy的规则,假如文件有个 xx 的成员变量,Groovy会自动为他添加 getXx()和setXx()俩个函数,所以当我们看到Range中有getFrom和getTo
的时候,我们就可以确定Range中有from和to
属性
闭包
什么是闭包
闭包英语是Cloure,是Groovy中非常重要的数据类型,他代表了一段可执行代码,如下:
class Example {
static void main(String[] args) {
def closure = {//闭包是一段代码,所以用花括号圈起来
String param1,int param2->//箭头前面是参数,后面是代码
println param1+param2;//这个是代码
//最后一句是返回值,也可以用return,和Groovy函数用法一样
'返回值'
}
def aa = closure("测试",11);
closure.call("你好",22);
println aa;
}
}
看下输出 :
测试11
你好22
返回值
也就是说,闭包的格式是
def xxx = {paramters -> code} //或者
def xxx = {无参数,纯 code} 这种 case 不需要->符号
调用闭包
闭包对象.call(参数)
或者: 闭包对象(参数)
这就是闭包的定义和使用,还需要注意一点
如果闭包没有定义参数的话,那就会有一个隐藏参数,这个参数的名字是it
,和this
的作用相似,it
代表闭包的参数
def closure1 ={ println "hello,$it"}
closure1("小明");
def closure2 ={it->println "hello,$it"}
closure2("小红")
}
上面的写法等同于下面写法,看下输出
hello,小明
hello,小红
但是如果闭包中是这种写法,则表示闭包没有参数
def closure3 = {-> println "text"}
这种写法就不能传参数了,传了的话就会报错
闭包使用需要主要的点
省略圆括号
有些函数最后一个参数是闭包,比如下面:
public static <T> List<T> each(List<T> self, Closure closure)
上面这个函数表示针对List的每一个元素,都会送入闭包做些处理,那该如何使用这个函数呢?
class Example {
static void main(String[] args) {
//定义list,可以是任何类型的元素
def list = [2,"吃",true]
list.each{
println it
}
}
}
上面代码有俩个知识点
- each函数调用的圆括号省略了,在Groovy中当函数的最后一个参数是闭包的话,就可以省略圆括号,比如:
static def text(int a ,String b,Closure closure){
def aa = a + b;
closure(aa);
}
调用text方法
text(1,'第一种调用方式',{
println it;
})
text 2,'第二种调用方式',{
println it;
}
//第三种调用方式,如果最后一个参数是闭包,那么可以放在括号外面
text(3,"第三种调用方式"){
println it
}
打印结果
1第一种调用方式
2第二种调用方式
3第三种调用方式
可以看到上面俩种调用方式,他们的差别就是调用少了括号
这种形式在android中还是很常见的,比如:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
比如这些其实就是闭包的写法,dependencies其实就是一个函数
void dependencies(Closure configureClosure);
看到函数,我们只知道要传入一个闭包,但是参数是什么,返回值是什么?
所以说闭包虽然很方便,但是它和使用它的上下文关联性很强,那么我们到底该怎么确定他的参数和返回值呢? 解决方式只能去看文档
文档介绍的是遍历List,将每个元素传递给给定的闭包。
所以这个闭包只有一个参数,就是遍历的元素
所以闭包的使用还是很坑的,很大程度依赖你对API的熟悉程度,所以在最开始,对API的查询是少不了的
闭包内置对象
闭包内部内置了三个对象,this,owner,delegate,我们可直接访问this,owner,delegate调用,或者调用他们的get
方法
- getThisObject() 等于 this
- getOwner() 等于 owner
- getDelegate() 等于delegate
那么这三个对象分别指的是那个对象呢?
- this:指的是定义的闭包的那个类,指向只能是类
- owner:指向定义闭包的类或者闭包,指向可以是类或者闭包
- delegate:默认和owner一致,但是可以自己设置(重点)
看下面例子
class OuterClass {
class InnerClass {
def outerClosure = {
def innerClosure = {
}
printfMsg("innerClosure", innerClosure)
println("------")
printfMsg("outerClosure", outerClosure)
}
void printfMsg(String flag, Closure closure) {
def thisObject = closure.getThisObject()
def ownerObject = closure.getOwner()
def delegate = closure.getDelegate()
println("${flag} this: ${thisObject.toString()}")
println("${flag} owner: ${ownerObject.toString()}")
println("${flag} delegate: ${delegate.toString()}")
}
}
def callInnerMethod() {
def innerClass = new InnerClass()
innerClass.outerClosure.call()
println("------")
println("outerClosure toString ${innerClass.outerClosure.toString()}")
}
static void main(String[] args) {
new OuterClass().callInnerMethod()
}
}
然后我们看下输出,就可以看出各个对象之间的差距了
innerClosure this: OuterClass$InnerClass@7e4204e2
innerClosure owner: OuterClass$InnerClass$_closure1@29a0cdb
innerClosure delegate: OuterClass$InnerClass$_closure1@29a0cdb
------
outerClosure this: OuterClass$InnerClass@7e4204e2
outerClosure owner: OuterClass$InnerClass@7e4204e2
outerClosure delegate: OuterClass$InnerClass@7e4204e2
------
outerClosure toString OuterClass$InnerClass$_closure1@29a0cdb
闭包中的delegate(委托)
上面说delegate是可以自己设置指向的,就是说可以让闭包通过delegate和某个对象进行关联,下面看下他的具体应用
先定义一个对象
class Person{
String name
int age
Person(String name,int age){
this.name=name
this.age=age
println '初始化'
}
void name(String name){
this.name=name;
}
void age(int age){
this.age=age
}
void eat(String foot){
println "不喜欢吃$foot"
}
String toString(){
"${name}的年龄为${age}"
}
}
class Main{
//定义闭包
def aa ={
//在闭包中直接调用关联对象Person的方法
name '小红'
age 22
eat '香蕉'
}
static void main(String... args){
def main = new Main()
Person person = new Person('小明',11);
person.eat('苹果')
println person.toString();
println '-----------------------'
//把闭包和Person对象通过delegate进行关联
main.aa.delegate=person
main.aa.setResolveStrategy(Closure.OWNER_FIRST)
main.aa.call();
println person.toString();
}
}
看下输出
初始化
不喜欢吃苹果
小明的年龄为11
-----------------------
不喜欢吃香蕉
小红的年龄为22
那如果关联的对象和闭包所在的类,有名称相同的方法,那么会调用那个方法呢?他会根据下面策略调用
-
Closure.OWNER_FIRST是默认策略。优先在owner寻找,owner没有再delegate
-
Closure.DELEGATE_FIRST:优先在delegate寻找,delegate没有再owner
-
Closure.OWNER_ONLY:只在owner中寻找
-
Closure.DELEGATE_ONLY:只在delegate中寻找
脚本类
Groovy可以向java一样,引入其他包的类,比如我在文件夹 com.aa
中创建文件Aa.groovy
package com.aa
class Aa{
String a;
String b;
Aa(String a,String b){
this.a=a;
this.b=b;
}
def print(){
println a+b;
}
}
这个类其实和java的类就比较相似,如果没有加任何权限修饰符(public,privite)那么Groovy中类和变量都是默认就是public修饰符
然后我们在根目录创建另一个文件Bb.groovy
import com.aa.Aa;
Aa aa = new Aa("小明","小红");
aa.print();
Bb文件先 import com.aa.Aa;
然后调用Aa中的print
方法
看下执行Bb之后的结果
$ groovy Bb.groovy
小明小红
脚本到底是什么
在java中,一个类必须要有(class,interface或者其他),不可以不写,但是Groovy可以像写脚本一样,可以直接把想要的事情写入xxx.groovy
文件中,而且可以使用 groovy xxx.groovy 来直接执行文件
IO操作
Groovy中的IO操作比java中的简单一些,Groovy的IO操作其实是在Java IO操作上进行了更为简便的封装,并且使用Closure来简化代码,一下是文档地址
java.io.File: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html
java.io.InputStream: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html
java.io.OutputStream: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.html
java.io.Reader: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.html
java.io.Writer: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.html
java.nio.file.Path: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html
读文件
File
直接查看API文档 java.io.File: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html
文件内容
你好啊
哈哈哈
class Example {
static void main(String[] args) {
//创建文件
def file = new File("text.txt");
//输出文件的每一行
file.eachLine{
String line ->
println line;
}
//文件内容一次性读出,返回类型为 byte[]
byte[] aa = file.getBytes()
String bb = new String(aa);
println bb;
}
}
输出
你好啊
哈哈哈
你好啊
哈哈哈
InputStream
首先查看Api文档http://docs.groovy-lang.org/latest/html/groovyjdk/java/io/InputStream.html
//获取InputStream流
def ism = file.newInputStream()
byte[] cc = ism.getBytes()
String dd = new String(cc);
println dd;
//使用闭包操作InputStream,以后在gradle中都是用这种方式
file.withInputStream{ ism1 ->
byte[] ee = ism1.getBytes()
String ff = new String(ee);
println ff
//操作 ism. 不用 close。Groovy 会自动替你 close
}
输出
你好啊
哈哈哈
你好啊
哈哈哈
写文件
继续看文档 http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.html
def srcFile = new File("text.txt")
def targetFile = new File("copy.txt")
targetFile.withOutputStream{ os->
srcFile.withInputStream{ ins->
//利用 OutputStream 的<<操作符重载,完成从 inputstream 到 OutputStream //的输出
os << ins
}
}
XML操作
Groovy中操作XML也是非常的简洁,首先提供一个xml文件
<response version-api="2.0">
<value>
<books>
<book available="20" id="1">
<title>Don Xijote</title>
<author id="1">Manuel De Cervantes</author>
</book>
<book available="14" id="2">
<title>Catcher in the Rye</title> <author id="2">JD Salinger</author>
</book>
<book available="13" id="3">
<title>Alice in Wonderland</title>
<author id="3">Lewis Carroll</author>
</book>
<book available="5" id="4">
<title>Don Xijote</title>
<author id="4">Manuel De Cervantes</author>
</book>
</books>
</value>
</response>
开始写代码
class Example {
static void main(String[] args) {
def booksData = new XmlParser().parse("text.xml");
// 访问第一个book元素的available属性
def ava = booksData.value.books.book[0].@available
println ava
//访问第二个book元素的title
def data = booksData.value.books.book[2].title.text();
println data
//访问第一个book元素的id属性
def id = booksData.value.books.book[0].@id
println id
println "---------------------"
//遍历boos标签打印title
booksData.value.books.each{books->
books.each{book ->
println book.title.text();
}
}
}
}
输出
20
Alice in Wonderland
1
---------------------
Don Xijote
Catcher in the Rye
Alice in Wonderland
Don Xijote
元对象编程
首先创建一个对象
class Student{
}
可以看到没有定义任何的属性和方法,这样的话外部调用此对象是不可以调用属性和方法的
class StuText{
static void main(String[] args) {
Student student = new Student()
student.age=18
println student.age
def aa= student.name("小明")
println(aa)
}
}
外部这样调用是会报错的,因为没有这样的属性和方法,但是我就是想要这样调用怎么办?
可以用下面的方法
Public interface GroovyInterceptable {
Public object invokeMethod(String methodName, Object args)
Public object getproperty(String propertyName)
Public object setProperty(String propertyName, Object newValue)
Public MetaClass getMetaClass()
Public void setMetaClass(MetaClass metaClass)
}
只需要实现GroovyInterceptable
这个接口就行
class Student implements GroovyInterceptable{
protected dynamicProps=[:]
void setProperty(String pName,val) {
dynamicProps[pName] = val
}
def getProperty(String pName) {
dynamicProps[pName]
}
def invokeMethod(String name, Object args) {
return "called invokeMethod $name $args"
}
}
如上面代码,再次调用,看下输出
18
called invokeMethod name [小明]
这样的话虽然没有定义方法和属性,但是依然还可以调到,并不会报错
methodMissing
上面的代码还有个问题,如果对象定义了name方法,我们依然调不到,因为invokeMethod
会把所有方法拦截
class Student implements GroovyInterceptable{
protected dynamicProps=[:]
def name(def message){
return message
}
void setProperty(String pName,val) {
dynamicProps[pName] = val
}
def getProperty(String pName) {
dynamicProps[pName]
}
def invokeMethod(String name, Object args) {
return "called invokeMethod $name $args"
}
}
可以看到上面我们定义了name方法,调用
class StuText{
static void main(String[] args) {
Student student = new Student()
student.age=18
println student.age
def aa= student.name("小明")
println(aa)
}
}
输出
18
called invokeMethod name [小明]
最终没有调用到我们想要调用的方法
这样肯定不行,辛亏Groovy中支持methodMissing的概念,与invokeMethod不同的地方他只在调用方法报错的情况下才会被调用
class Student implements GroovyInterceptable{
protected dynamicProps=[:]
def name(def message){
return message
}
void setProperty(String pName,val) {
dynamicProps[pName] = val
}
def getProperty(String pName) {
dynamicProps[pName]
}
def methodMissing(String name, def args) {
println "Missing method"
}
}
调用name方法,输出
18
小明
调用到了正确的方法
调用其他没有定义的方法
18
Missing method
就会回调到methodMissing的方法中