基础语法
groovy是一种用于java虚拟机的一种动态语言,与java语言一样,被编译为.class文件后在jvm上执行。不同的是,groovy既可以以类的方式执行,也可以以脚本的方式执行。与大多数动态语言类似,groovy可以省略结束符;
。
// 类的形式
class HelloGroovy {
static void main(args) {
println('hello groovy !!!')
}
}
//直接写脚本
println('hello groovy !!!')
groovy定义变量继承了java的数据类型,即可以使用(int、double、String…)定义变量,但定义的变量都是引用类型(groovy中没有基本数据类型)。
int x = 10
println x
println x.class
作为动态语言的一种,groovy当然支持类型推断。使用def定义变量,编译器会自动推断数据类型。
def x = 10
println x.class
使用动态类型的好处是可以为变量赋其他类型的值,可以使程序更加灵活。
def x = 10
println x.class
x = 22.36
println x.class
字符串
groovy字符串继承了java中String类所有功能的同时增加了更多的方法和操作符,字符串的功能变得更加灵活。
字符串可以使用单引号,双引号,和三引号定义。
// 单引号
def name = 'Lucy'
// 三引号定义的字符串可以固定格式
def emails = '''\
Lucy@139.com
Alice@google.com
Tom@163.com
'''
/**
** 双引号定义的变量中可以填充groovy表达式
** 在这种情况下字符串类型为GString而非String
**/
def code = "19fv32"
println code.class
def result = "code : ${code}"
println result.class
groovy对字符串扩展了各种操作符
def strA = 'hello groovy'
def strB = 'hello'
// 根据索引取得字符
println strA[0]
// 根据索引范围截取字符
println strA[0..4]
// 根据编码比较字符
println strA > strB
// 字符串减法
println strA - strB
在java.lang.String类的基础上,groovy为字符串增加了许多操作方法。
def str = 'groovy'
// 居中填充
println str.center(8,'a')
// 左侧填充
println str.padLeft(8,'a')
// 右侧填充
println str.padRight(8,'a')
// 首字母大写
println strA.capitalize()
// 转换为其他类型
println '123'.toInteger().class
逻辑控制
groovy中的if_else与java中的用法相同,但是switch_case与java中的用法稍有差别,switch可以进行任意类型的匹配。
def x = 12,result
switch(x) {
case 'string':
result = 'string'
break
case [4,5,6,'7']: // 列表
result = 'list'
break
case 2..9: // 范围
result = 'list'
break
case Integer:
result = 'integer'
break
case BigDecimal:
result = 'Big Decimal'
break
default:
result = 'undefined'
}
println result
for循环增量加了更加灵活的方式,可以对范围循环,可以使用for-in语法。
// 对范围进行循环
def sum = 0
for (x in 0..9) {
sum += x
}
// 对list进行循环
for (i in [0, 1, 2, 3, 4, 5, 6]) {
sum += i
}
// 对map进行循环
for (entity in ['first':1,'second':2]) {
sum += entity.value
}
闭包基础
groovy中的闭包是一段大括号包裹的代码块,可以传递参数(与java中的方法相似)。
// 闭包的定义与调用
def closer = {
println 'hello groovy !!!'
}
closer.call()
closer()
闭包传递参数类似与lamda表达式的形式,在箭头前面的是传递的参数,如果有多个参数,使用逗号进行分割。
def closer = {String name,int age -> println "hello ${name} , my age is ${age}"}
closer('groovy',10)
闭包存在一个默认参数(隐式参数),如果只用到了一个参数,可以省略参数的定义和箭头(默认参数写做it),在使用时直接传入即可。
def closer = {println "hello ${it}"}
closer('groovy');
闭包一定会参在返回值。
def closer = {String name,int age -> return "hello ${name} , my age is ${age}"}
println closer('groovy',10)
如果在闭包中不适用return关键字进行返回,则默认返回为null
def closer = {String name,int age -> println "hello ${name} , my age is ${age}"}
println closer('groovy',10)
闭包结合方法求指定数字的阶乘。
int fab(int num) {
int result = 1;
/**
* 从1增长至num,每增长一次,调用一次闭包
*/
1.upto(num, {number -> result *= number})
// num.downto(1, {number -> result *= number})
return result;
}
println fab(5)
闭包也可以将大括号写在方法外并省略小括号,例:从零累加至某一值。
int sum(int num) {
int result = 0;
/**
* times方法会从0增长至num
*/
num.times{ //闭包写在方法外
number -> result += number
}
return result;
}
println sum(101)
闭包也可以结合字符串使用。
def str = 'the 2 and 3 is 5'
// 遍历字符串
str.each {temp -> print temp}
// 查找符合条件的第一个字符
println str.find{temp -> temp.isNumber()}
// 查找符合条件的所有字符
println str.findAll{temp -> temp.isNumber()}
// 判断字符串中是否有字符满足条件
println str.any{temp -> temp.isNumber()}
// 判断字符串中是否全部字符都满足条件
println str.every{temp -> temp.isNumber()}
// 将字符串中每一个字符转换后返回
println str.collect{it.toUpperCase()}
闭包进阶
闭包有三个重要变量:this、owner、delegate。this代表闭包定义处的类,owner代表闭包定义处的类或对象(闭包可以定义在其他闭包中,这样owner表示的就不是当前类了,与this对象就不一样了),delegate代表任意第三方对象,其默认值为owner所指向的对象。因此,在大多数情况下this、owner、delegate指向同一对象。
def closure = {
println this == owner
println this == delegate
}
closure.call()
在闭包中定义闭包,此时this与owner就会指向不同的对象(this指向当前类,owner和delegate只想外部闭包outClosure的实例对象)
def outClosure = {
def innerClosure = {
println 'this : ' + this
println 'owner : ' + owner
println 'delegate : ' + delegate
}
innerClosure.call()
}
outClosure.call()
delegate的指向可以被修改,修改后的delegate可与owner的只想不一样
class Person{}
def p = new Person();
def outClosure = {
def innerClosure = {
println 'this : ' + this
println 'owner : ' + owner
println 'delegate : ' + delegate
}
innerClosure.delegate = p
innerClosure.call()
}
outClosure.call()
闭包的委托策略
class Student {
String name
def closure = {"my name is ${name}"}
String toString() {
println closure.call()
}
}
class Teacher{
String name
}
Student student = new Student(name: 'Tom');
Teacher teacher = new Teacher(name: 'Annie')
student.closure.delegate = teacher
student.closure.resolveStrategy = Closure.DELEGATE_FIRST
student.toString()
将student对象内closure的delegate修改为指向teacher,再将其委托策略改为delegate优先,那么闭包中使用到的属性会优先在delegate指向的对象中查找并使用,如果delegate指向的对象找不到要用的属性,再从owner指向的对象中查找。如果将委托策略改为DELEGATE_ONLY,则闭包只会在delegate指向的对象中查找属性,找不到则报错。
class Student {
String name
def closure = {"my name is ${name}"}
String toString() {
println closure.call()
}
}
class Teacher{
String nameX
}
Student student = new Student(name: 'Tom');
Teacher teacher = new Teacher(nameX: 'Annie')
student.closure.delegate = teacher
student.closure.resolveStrategy = Closure.DELEGATE_ONLY
student.toString()
以上例子可以发现,合理的修改闭包策略,可以实现通过student调用teacher中的方法或属性。
数据结构
groovy数据结构包括列表、映射和范围。
groovy中list结构类似于java数组,并且默认使用java.util.ArrayList。
def list = [1, 2, 3, 4, 5]
println list.class
println list.size()
由于list定义使用了中括号,所以在定义数组时需用用as关键字指定数组类型(或直接使用强类型进行定义)。
def array = [1, 2, 3, 4, 5] as int[]
println array.class
println array.length
int[] a = [1, 2, 3, 4, 5]
groovy在java集合的基础上对list新增了许多操作
- list从小到大排序
def list = [9, -2, 3, 7, 6, 1, -5] list.sort() // 对list从小到大排序 println list
- list按绝对值从大到小排序
def list = [9, -2, 3, 7, 6, 1, -5] // 使用闭包自定义排序规则 list.sort{ a,b -> a == b ? 0 : Math.abs(a) > Math.abs(b) ? 1 : -1 } println list
- 按字符串长度对字符串排序
def list = ['java', 'groovy', 'python', 'scala', 'c++'] // 使用闭包自定义排序规则 list.sort { it.length() } println list
- 列表的其他方法
def list = [-3, 9, 6, 2, -7, 1, 5] // 查找第一个偶数 println list.find { it % 2 == 0 } // 查找所有的偶数 println list.findAll { it % 2 == 0 } // 判断列表是否存在偶数 println list.any { it % 2 == 0 } // 判断列表是否全都为偶数 println list.every { it % 2 == 0 } // 找出绝对值最大的数 println list.max { Math.abs(it) } // 找出绝对值最小的数 println list.min { Math.abs(it) } // 统计列表中偶数的个数 println list.count { it % 2 == 0 }
映射的功能类似于java中的Map(实际上映射默认使用java.util.LinkedHashMap实现),但写法稍有不同。
// 定义一个映射
def colors = ['red': '#ff0000','green':'#00ff00','blue':'#0000ff']
使用中括号+key的形式可以获取到指定key对应的value值。
println colors['red']
也可以通过.
操作符获取指定key对应的value。
println colors.red
添加(更新)操作也通过.
操作符来完成。
colors.yellow = '#ffff00' // 添加新元素
colors.red = 'rgb(255,0,0)' //更新元素
值得注意的是,groovy 中的映射可以添加任意类型。
groovy基于Map的基础上,为映射添加了许多快捷操作。
// 定义一个映射
def students = [
1 : ['number': '0001','name':'Bob','score':55,'sex':'male'],
2 : ['number': '0002','name':'Johnny','score':62,'sex':'female'],
3 : ['number': '0003','name':'Claire','score':73,'sex':'female'],
4 : ['number': '0004','name':'Amy','score':66,'sex':'male'],
]
-
遍历映射
// each遍历映射 students.each { student -> println "key: " + student.key + "\tvalue: " + student.value } // 带索引形式遍历映射 students.eachWithIndex { student, index -> println "index:" + index + "\tkey: " + student.key + "\tvalue: " + student.value } // 直接通过key,value遍历 students.eachWithIndex { key, value, index -> println "index:" + index + "\tkey: " + key + "\tvalue: " + value }
-
查找操作
// 查找第一个及格的学生 println students.find { it.value.score >= 60 } // 查找所有及格的学生 println students.findAll { it.value.score >= 60 } // 统计及格男生的总人数 println students.count { it.value.score >= 60 && it.value.sex == 'male' } // 查找所有及格的学生,并返回姓名 println students.findAll { it.value.score >= 60 } .collect { it.value.name } // 将及格,不及格学生进行分组 println students.groupBy { def student -> student.value.score >= 60 ? '及格' : '不及格' }
-
排序操作
映射的排序与列表不同,列表sort方法会直接对原列表排序(原列表会发生改变), 映射sort方法会返回一个排序好的map(原映射数据不会改变)。
// 根据分数排序 println students.sort{def studentA, def studentB -> def scoreA = studentA.value.score def scoreB = studentB.value.score return scoreA==scoreB ? 0 : scoreA > scoreB ? 1 : -1 }
范围是groovy新增的数据结构,通过数字..数字
定义,使用中括号和下标的方式获取范围值。
// 定义一个范围
def range = 1..10
// 输出起始值
println range[0]
也可以通过范围的from属性和to属性获得范围的其实质和终止值。
println range.from // 输出起始值
println range.to // 输出终止值
-
遍历范围
范围的遍历可以通过for循环和闭包两种方式进行。
// 定义一个范围 def range = 1..10 // for循环遍历 for(def x in range) { println x } // 闭包遍历 range.each {println it}
-
switch-case中使用范围
def grade(Number score){ def result switch(score) { case 0..<60: // <表示不包含60 result = '不及格' break case 60..<70: result = '及格' break case 70..<85: result = '良好' break case 85..100: result = '优秀' break } return result } println grade(75)
面向对象基础
groovy中也可以定义类和接口,并且所有的类默认都是public类型的(内部的字段,方法默认也是public类型)。
/* 定义一个groovy类 */
class Person {
String name
Integer age
def incAge(Integer year) {
age += year
}
}
groovy使用new关键字进行对象的实例化,实例化时可以指定属性的内容(此处与java不同,java需要提供带参数的构造方法)。
def person = new Person(name: 'Annie', age: 18 ) // 实例化一个Person对象
println "the name is ${person.name}, the age is ${person.age}"
通过实例对象.属性
的方式调用,实际上调用的时java类中的setter、getter方法,尽管在Class中未手动定义。
groovy中对方法的调用与java中相同。
person.incAge(10) // 调用incAge方法
groovy中接口的定义与java中相似(实际上是java接口去掉public关键字,因为所有的方法默认都是public,并且不允许使用其他范围的关键字)。
/* 定义一个groovy接口 */
interface Action {
void eat()
void drink()
void play()
}
接口的使用也与java相同,类通过implements关键字实现接口并需要覆写所有方法。
/* 定义一个groovy类 */
class Person implements Action{
@Override
void eat() {}
@Override
void drink() {}
@Override
void play() {}
}
groovy中可以定义一个trait,trait具有java抽象类和接口的特性(是二者的结合),类通过implements关键字实现trait并覆写需要的方法(接口需要覆写所有方法),也就是说在trait中可以有方法默认的实现。
/* 定义一个trait */
trait DefaultAction {
abstract void eat()
void play() {
println 'i can play !!!'
}
}
面向对象高级
groovy中提供了元编程(在运行时期执行的代码)。
在java中调用一个方法,会在调用者所属的类及父类中寻找这个方法,如果找不到则编译器就会报错。
在groovy中调用一个方法,如果在其类及父类中找不到,则会查找MethodClass是否有此方法,如果有则调用MethodClass中的方法,如果没有则继续查看是否覆写了methodMissing方法,如果重写了methodMissing方法,则调用methodMissing,如果没有覆写则查看是否覆写了inkokeMethod方法,如果覆写了则调用,否则抛出异常(MissingMethodException)。
/* 定义一个groovy类 */
class Person {
String name
Integer age
def incAge(Integer year) {
age += year
}
}
Person p = new Person();
print p.cry(); //调用类中不存在的方法
运行上面的代码发现程序抛出了groovy.lang.MissingMethodException
异常,覆写了inkokeMethod方法后再次调用发现程序执行了inkokeMethod方法。
/* 定义一个groovy类 */
class Person {
String name
Integer age
def incAge(Integer year) {
age += year
}
@Override
def invokeMethod(String name, Object args) {
return "the method is ${name}, the parameters is ${args}"
}
}
Person p = new Person();
print p.cry(); //调用类中不存在的方法
上述程序输出结果为:the method is cry, the parameters is []
class Person {
String name
Integer age
def incAge(Integer year) {
age += year
}
@Override
def invokeMethod(String name, Object args) {
return "the method is ${name}, the parameters is ${args}"
}
def methodMissing(String name, Object args) {
return "the method ${name} is missing"
}
}
Person p = new Person()
print p.cry() //调用类中不存在的方法
覆写了methodMissing方法后,程序输出:the method cry is missing
metaClass可以在运行时为类动态的添加属性和方法。
// 为类动态添加属性
Person.metaClass.sex = 'male'
Person p = new Person(name: 'Annie', age: 18)
println p.sex
// 为类动态添加方法
Person.metaClass.sexToUpCase = { -> sex.toUpperCase() }
println p.sexToUpCase()
除了上面事例中为类添加普通属性和方法外,还可以动态为类添加static方法。
// 为类动态添加static方法
Person.metaClass.static.create = {
String name, Integer age -> new Person(name: name, age: age)
}
Person person = Person.create('Tom', 12)
println "my name is ${person.name} and age ${person.age}"
使用metaClass属性注入的属性和方法只在本脚本中有效,如果想要使注入的属相和方法全局生效,则需要设置ExpandoMetaClass全局可用。
ExpandoMetaClass.enableGlobally()
JSON操作
groovy支持将json转换为对象或将对象转换为json字符串。
-
实体对象转换为JSON
import groovy.json.JsonOutput class Person { String name int age } def list = [new Person(name: 'Tom', age: 15), new Person(name: 'Annie', age: 18)] def json = JsonOutput.toJson(list) //实体对象转换为JSON println JsonOutput.prettyPrint(json) // JSON格式化输出
-
JSON字符串转换为实体对象
def json = '''[ { "age": 15, "name": "Tom" }, { "age": 18, "name": "Annie" } ]''' def jsonSlurper = new JsonSlurper() def object = jsonSlurper.parseText(json); println object println object.class
XML操作
在java语言中,对xml的处理主要有DOM文档驱动处理和SAX事件驱动处理两种方式。SAX解析采用逐行读入方式减少了解析事件,但相比于groovy对xml的处理,仍然是比较复杂的。
-
XML解析
// 定义一个xml字符串 def xmlText = '''<?xml version="1.0" encoding="UTF-8"?> <books> <book id="1" avaliable="20" > <title>Groovy从入门到精通</title> <author>李刚</author> <price>23.56</price> </book> <book id="2"> <title>Gradle实践</title> <author>Annie</author> <price>80.91</price> </book> <book id="3"> <title>Vue从入门到精通</title> <author>李刚</author> <price>26.91</price> </book> </books> ''' def xmlSlurper = new XmlSlurper() // xml解析器 def books = xmlSlurper.parseText(xmlText) // 解析xml
text()方法用于输出节点的文本,使用@+属性名的方式可以访问节点的属性。
// 输出解析结果 println books.book[0].title.text() println books.book[0].@id
groovy提供了深度遍历方法来查找节点。(例如查找所有作者为李刚的书名)。
// depthFirst()可以用'**'来代替 def titles = books.depthFirst().findAll { book -> book.author.text() == '李刚' ? true : false } println titles
广度遍历与深度遍历的用法几乎一(找到id为2的书名)。
// children()可以用'*'来代替 def titles = books.children().findAll { book -> book.name() == 'book' && book.@id == 2 }.collect{ node -> node.title.text() } println titles
-
XML生成( 生成xml数据需要用到MarkupBuilder类)。
def sw = new StringWriter() def xmlBuilder = new MarkupBuilder(sw) // 用来生成xml数据的核心类 xmlBuilder.books(type: '计算机技术') { // 第一个book节点 book(id:'1',avaliable:'20'){ title('Groovy从入门到精通') author('李刚') price('23.56') } // 第二个book节点 book{ title('Gradle实践') author('Annie') price('80.91') } // 第三个book节点 book{ title('Vue从入门到精通') author('李刚') price('26.91') } } // 打印生成的xml println sw
文件操作
groovy在java流的基础上封装了许多操作文件的方法。
-
eachLine() 方法读取文件。
// 读取文件 def file = new File('E:/groovy.txt') file.eachLine {line -> println line}
-
getTex() 方法读取所有内容。
def text = file.getText(); println text
-
readLines() 方法读取每一行内容,以list形式返回。
def textList = file.readLines() println textList
-
withReader() 方法可以读取文件指定部分的数据。
def text = file.withReader { reader -> { char[] buffer = new char[100] reader.read(buffer) buffer } } println text
-
withWriter() 方法可以向文件写入数据。
file.withWriter { writer -> { writer.write('code: 1DfGc4') } }
-
withObjectOutputStream() 方法可以将对象写入文件,withObjectOutputStream() 方法可以从文件中获取对象,使用它们可以实现对象的序列化和反序列化。
class Person implements Serializable{ String name Integer age @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } /** * 使用文件保存对象 */ def serialize = { String path, def obj -> { try { def file = new File(path) if (!file.exists()) { // 文件不存在,创建新文件 file.createNewFile() } // 将对象写入文件 file.withObjectOutputStream { out -> out.writeObject(obj) } } catch (Exception e) { println e } } } /** * 从文件中读取对象 */ def deserialize = { String path -> { def obj = null try { def file = new File(path) if (!file.exists()) { // 文件不存在,创建新文件 return null } // 将对象写入文件 file.withObjectInputStream { input -> obj = input.readObject() } } catch (Exception e) { println e } obj } } def person = new Person(name:'Annie',age:18) serialize('./person.bin',person) def p = deserialize('./person.bin') println person // 原对象 println p // 从文件中读取出的对象 println p==person