1. 介绍
本篇内容为Groovy学习分享第8篇,继续分享关于Groovy运算符的相关内容,上一篇介绍了位运算符的一些计算逻辑。本篇介绍条件运算符(Conditional operators),对象运算符(Object operators)等知识。
即使熟练java的语法,下面的对象运算符等知识,也还是需要新学习才行哦。
2. 条件运算符-Conditional operators
条件运算符主要是针对boolean布尔值进行的一些运算,最终得到的结果只有两种:true和false。
首先介绍第一个:!
取反运算符,表达为 "not" 。一般是将结果值进行取反操作。
例如表达式为true,取反后,输出结果为false。表达式为false,取反后,输出为true。
示例如下:
代码语言:javascript复制println(!true) //输出结果为 false
println(!'zinyan') //输出结果为false
println(!'') //输出结果为true
在上面的表达式中,第一个比较好理解。但是为什么针对'zinyan'
这个字符串它也能进行取反并且结果为false?。
因为在Groovy中,对字符串取反比较。其实就是判断了字符串是否为空字符串,根据是否为空返回boolean值,然后再进行取反。
所以上面的示例中:zinyan字符串是非空的所以为true,然后取反输出为false。而下面第三行的代码中字符串为空的,所以值为false,取反得到的就是true了。
PS:在Groovy中判断字符串是否为空可以直接使用这个条件判断符来进行判断。如果对象是null 也是可以判断的。
示例,如果String是null的效果:
代码语言:javascript复制 String zinyan
println(!zinyan) //输出结果为true
2.1 三元运算符
在java中的三元运算符 表达式 ? 结果值1:结果值2
在Groovy当然也是一样支持的。
三元运算符其实就是针对if/else
判断的一个缩写,一种快捷表达式而已。示例如下:
def string ="zinyan.com"
def result = (string!=null && string.length()>0) ? '有网址' : '没有网址'
根据表达式的结果值,如果是true就会返回结果值1,如果是false就会返回结果值2。
例如上面的参数string满足表达式中的比较,就会返回'有网址'
这个结果给到result对象。
那么,如果三元运算符多层嵌套,那么计算优先级是什么呢?示例:
代码语言:javascript复制def string ="zinyan.com"
def result = (string!=null && string.length()>0) ? (string.startsWith('zin')?(string.endsWith('yan')?true:false):false): '没有网址'
println(result)
例如上面的示例中,最后得到的结果是 resutl=false。
结论:在三元运算符的多层表达式嵌套下,计算结果是由外到内进行的计算。并不是先计算最里面的
string.endsWith('yan')?true:false
的值。
2.2 if/else 运算符
我们将上面的三元运算符进行一个展开,通过if/else
进行书写的话,效果如下:
if (string != null && string.length() > 0) {
if (string.startsWith('zin')) {
if (string.endsWith('yan')){
result = true
}else{
result = false
}
}else{
result = false
}
}else{
result = '没有网址'
}
Groovy中的if/else
运算符的逻辑和java中是一样的,写法也是一样的。
2.3 with 运算符
上面这些算是条件运算符的基础了。而在Groovy 3.0.0之后扩展了新的条件运算符with{}
。
import groovy.transform.ToString
@ToString(includePackage = false)
class Zinyan {
String name
int atomicNumber
def static main(def args) {
def he = new Zinyan(name: 'zinyan')
he.with {
name = name ?: 'Z同学' // 如果名称为空 就改为Z同学,
atomicNumber ?= 2 // 如果原子序数为空, 就赋值2
}
println(he.toString()) //输出结果为:Zinyan(zinyan, 2)
}
}
PS:你如果运行不了with。那么应该是Groovy的SDK版本比较老。 我在Windows系统中,通过Visulal Studio 工具,添加Groovy插件 COde Runner插件(这个插件能够运行C,C ,Java,JS,PHP,Python,Perl,Ruby,Go,Groovy等等) 实现的编译。 有空了,列一个详细的安装过程吧,你如果使用AndroidStudio中的Gradle插件自带的Groovy 可能版本过低,3.0之后的特性使用不了。 安装上面的插件并正确使用的前提条件是,需要安装Java SDK ,Groovy SDK 并配置好环境变量才行哦。
例如,我上面的示例中,在对象初始化时不进行复制操作:
代码语言:javascript复制import groovy.transform.ToString
@ToString(includePackage = false)
class Zinyan {
String name
int atomicNumber
def static main(def args) {
def he = new Zinyan()
he.with {
name = name ?: 'Z同学' // 如果名称为空 就改为Z同学,
atomicNumber ?= 2 // 如果原子序数为空, 就赋值2
}
println(he.toString()) //输出结果为:Zinyan(Z同学, 2)
}
}
所以,我们可以在对象的with{}
之中实现状态检查并重新赋值操作。
这个叫做变量一致性检查。
3. 对象运算符-Object operators
在对象运算符中主要分为以下四种。并针对不同的对象状态进行检测:
- 安全导航符(Safe navigation operator):处理对象的
Null
值判断,避免出现NullPointerException
异常。运算符:?
- 对象直接获取运算符(Direct field access operator):处理对象的属性时,可以直接通过该操作符获取属性原始值和修改属性值。而不经过get方法。对象其他函数也可以使用该运算符直接获取。运算符:
.@
- 方法指针运算符(Method pointer operator):可用于在变量中存储对方法的引用。运算符:
.&
- 方法引用运算符(Method reference operator):可用于在需要函数接口的上下文中引用方法或构造函数。(PS:需要Groovy3 以上版本才能使用),运算符:
::
而我们从java转Groovy中很容易造成无法理解代码的地方,就在于Groovy中新增的各种对象运算符。
弄明白对象运算符,Groovy的代码理解起来就可以轻松一大半了。
下面来具体介绍一下每个对象运算符的使用吧。
PS:所有的运算符只是对代码进行了一些缩写,它的初衷是减少我们一些样板代码的编写量。如果你不使用运算符,也可以纯粹使用代码来实现效果。
3.1 安全导航符(Safe navigation operator)
安全导航运算符用于避免NullPointerException
。通常,当您有对对象的引用时,您可能需要在访问对象的方法或属性之前验证它是否为空。为了避免这种情况,安全导航操作符将简单地返回null,而不是抛出异常,如下所示:运算符:?
class Zinyan {
String name
int id
def static main(def args) {
//例如 ,我们正常创建对象,并复制
// def person = new Zinyan(id:2022)
// def name = person.name
// println(name) //输出 null 很正常因为我没有给name属性复赋值
// //但是如果我们person对象是通过外部传入进来的,我们就需要对他验证是否为空
Zinyan person= null
//def name = person.name // 在java的写法中,这一步会出现 NullPointerException异常。
def name =person?.name // 我们这样写之后,就不会出异常了。而是直接给name赋值为null
println(name) //输出 null
}
}
PS:现在各种高级语言都有类似的机制,例如Kotlin中也有。通过这个控制符避免NullPointerException异常
3.2 对象直接获取运算符(Direct field access operator)
我们常见的Groovy中的对象创建示例代码如下:
代码语言:javascript复制PS:Groovy中的类的属性的get和set方法会在编译的时候自动创建。 不需要我们主动创建get和set方法。(PS:注意访问权限和final标识的区别哦。final 就不会创建set方法了)
class Zinyan {
String name
Zinyan(String name){
this.name=name
}
//重构了get方法
String getName(){
"名称: $name"
}
def static main(def args) {
Zinyan zi = new Zinyan("zinyan.com")
println(zi.name) //输出 名称: zinyan.com
}
}
因为Groovy默认帮我们创建了类的get和set方法,所以我们可以直接 对象.属性名
获取该值。上面的示例展示了,我们使用属性的时候会通过调用该属性的get方法获取返回值。
而我们如果想获取纯粹的属性值,不经过get方法那么该怎么办?可以通过@
关键字,直接获取属性值。
示例如下:
代码语言:javascript复制class Zinyan {
String name
Zinyan(String name){
this.name=name
}
//重构了get方法
String getName(){
"名称: $name"
}
def static main(def args) {
Zinyan zi = new Zinyan("zinyan.com")
println(zi.@name) //输出 名称: zinyan.com
}
}
获取的是对象初始化时复制的属性值,而不是从get方法中获取的输出值。
请注意,这个属性获取,主要是针对get方法。set方法没有影响。
代码语言:javascript复制class Zinyan {
String name
Zinyan(String name){
this.name=name
}
//重构了get方法
String getName(){
"名称: $name"
}
//重构set方法
void setName(String name){
this.name="修改值:$name"
}
def static main(def args) {
Zinyan zi = new Zinyan("zinyan.com")
zi.name="z同学"
println(zi.name) //输出:名称: 修改值:z同学
println(zi.@name) //输出 修改值:z同学
}
}
不管是使用 zi.name
还是使用zi.@name
他们都是一样的。
PS:
@
符号除了访问对象属性,也可以用于访问对象的函数方法。但是这样的话意义不大,还不如直接通过.
访问呢。 例如:zi.@getName
的结果和zi.name
的结果是一样的。
3.3 方法指针运算符(Method pointer operator)
方法指针运算符.&
可用于在变量中存储对方法的引用,以便以后调用:
class Zinyan {
def static main(def args) {
def str = '这个消息来自: zinyan.com 网站'
def fun = str.&toUpperCase //将字符串中的字母转为大写
def upper = fun()
println(upper) //得到的结果 : 这个消息来自: ZINYAN.COM 网站
println(str.toUpperCase()) //得到的结果 : 这个消息来自: ZINYAN.COM 网站
}
}
我们会发现使用.&
调用貌似没有什么区别啊为什么不直接使用呢?
使用方法指针有多种优点。首先,这种方法指针的类型是groovy.lang.Closure
,因此它可以在任何使用闭包的地方使用。
特别是适合将现有方法转换为战略模式的需要,示例如下:
代码语言:javascript复制class Zinyan {
String name //名称
int age //年龄
//创建一个转换函数
def transform(List elements, Closure action) {
def result = []
elements.each {
result << action(it)
}
result
}
//创建一个字符串
String describe(Zinyan p) {
"$p.name is $p.age"
}
def static main(def args) {
Zinyan z1 =new Zinyan()
//将方法的调用逻辑 存储起来 ,存储起来的数据变量就是 Closure对象了
def action = z1.&describe
//创建了一个List 对象
def list = [
new Zinyan(name: 'zin', age: 1),
new Zinyan(name: 'yan', age: 2)]
//
def tranList = z1.transform(list, action)
println(tranList) //输出结果为:[zin is 1, yan is 2]
}
}
先将方法的调用给存储起来。然后再给到其他地方进行使用。
方法指针由接收器和方法名称绑定。参数在运行时解析,这意味着如果您有多个同名的方法,语法没有不同,只有要调用的适当方法的解析才会在运行时完成。示例代码如下:
代码语言:javascript复制//创建一个闭包对象 获取字符串并将字符串转为大写字母
def doSomething(String str) { str.toUpperCase() }
//创建一个闭包对象,获取int数值,并将该值翻倍输出
def doSomething(Integer x) { 2*x }
//获取该方法的方法指针
def reference = this.&doSomething
println(reference('zinyan.com')) //输出 ZINYAN.COM
println(reference(123)) //输出 246
PS:我们使用Groovy脚本进行运行的时候,可以用
def static main(def args)
main方法做开头,也可以省略该方法,一样和示例输出的。
在上面的示例上,我们存储的一个方法指针,在后面使用中会根据参数自动调用相应的函数进行运行。
为了与Java8 在方法引用上保持一致,在Groovy3.0 之后我们可以使用new
获取指定类的构造函数的方法指针。示例代码如下:
def foo = BigInteger.&new //获取 BigInteger类的 构造函数
def fortyTwo = foo('42') //创建一个数
println(fortyTwo) //输出42
同样在Groovy3.0和更高版本中,可以获得指向类的实例方法的方法指针。此方法指针接受一个附加参数,作为调用方法的接收器实例。示例代码如下:
代码语言:javascript复制def instanceMethod = String.&toUpperCase //创建一个String的 toUpperCase 字母转大小写方法引用对象。
def tst = instanceMethod('zinyan') //调用该引用。并传入参数
println(tst) //输出 ZINYAN
为了向后兼容,任何恰好具有正确调用参数的静态方法都将优先于实例方法。也就是说,静态方法和普通方法,在优先级上会先调用。
3.4 方法引用运算符(Method reference operator)
在Groovy3.0 以上版本才支持方法引用运算符。可以在需要函数接口的上下文中引用方法或者构造函数。
如果单纯看描述,可能会觉得方法引用运算符(::
)和方法指针运算符(.&
)功能有重叠了。但实际上两者是有差别的,
对于动态Groovy,方法引用运算符只是方法指针运算符的别名,两种功能等效。对于静态Groovy,方法引用运算符产生的字节码类似于Java为相同上下文生成的字节码。
上面的文字没有看懂,没关系。我们结合代码示例进行理解:下面都是静态对象中的使用。
代码语言:javascript复制import groovy.transform.CompileStatic
import static java.util.stream.Collectors.toList
@CompileStatic //静态Groovy方法
void methodRefs() {
//示例1 将List集合中的数据,使用stream流输出,并进行reduce累加函数, 将最后的计算结果输出。
def x = [1G, 2G, 3G].stream().reduce(0G, BigInteger::add) //将会返回一个BigInteger 对象
println(x) //结果为 6
//示例2 将List集合中的数据,使用stream流输出,并进行map 映射操作 3::add 就是转换为BigInteger.add 3的操作, collect中转换为List对象。
def y = [1G, 2G, 3G].stream().map(3G::add).collect(toList()) //将会得到一个List<BigInteger>对象
println(y) //输出内容 [4, 5, 6]
//示例3 将List集合中的数据,使用stream流输出,并进行map转换操作, collect中转换为List对象。
def z = [1L, 2L, 3L].stream().map(BigInteger::valueOf).collect(toList()) //将会得到一个List<BigInteger>对象
println(z) //输出内容[1, 2, 3]
//示例3 将List集合中的数据,使用stream流输出,并进行map转换操作, collect中转换为List<BigInteger>对象。
def n = [1L, 2L, 3L].stream().map(3G::valueOf).collect(toList())
println(n) //输出内容[1, 2, 3]
}
//执行该静态函数
methodRefs()
PS:如果对于stream(),map()等方法不理解。那么需要补充一些关于stream的相关知识了。其中map步骤在流中是属于映射操作。也就是将流里面的对象映射成另外一个类型。 相关知识不太懂可以百度搜索:
java stream相关知识
。这里就不深入扩展了。
下面展示一个比较典型,方法引用运算符的示例:
代码语言:javascript复制import groovy.transform.CompileStatic
import static java.util.stream.Collectors.toList
@CompileStatic
void constructorRefs() {
//一个字符串List通过stream流进行操作。在map步骤中转换为integer,最后以List<Integer> 的数据结果进行输出
def z = ['1', '2', '3'].stream().map(Integer::valueOf).collect(toList()) //类构造函数引用
println(z) //打印: [1, 2, 3]
//一个数字List通过stream流进行操作,输出成Integee Array数组对象。
def result = [1, 2, 3].stream().toArray(Integer[]::new) //数组构造函数引用
println(result) //打印: [1, 2, 3]
}
//最后,执行该静态函数
constructorRefs()
到这里方法引用运算符的简单介绍就结束了。
而引用运算符没有弄明白的话,只要记住它的真实用处:可以在需要函数接口的上下文中引用方法或者构造函数。
之后使用中,多用就能明白和理解了。
4. 小结
本篇再次刷新了对于Groovy的理解程度,学习了条件运算符(和Java差不多可以说是一样的,只是多了with的运算符)。
而对象运算符可以说是需要我们深刻理解和领会的知识点,领会该操作符的各种作用后。再看其他人写的Groovy脚本就能够比较容易看明白。
否则代码中多耦合介个对象运算符,你估计得彻底的懵逼了。
相关资料可以通过Groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_conditional_operators 和http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_object_operators 进行了解学习。
如果看不懂,可以回来看看我写的学习分享资料。
下一篇,继续分享关于运算符的相关学习笔记。