8. Groovy 运算符 条件运算符,对象运算符学习

2022-12-07 18:25:49 浏览数 (1)

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 判断的一个缩写,一种快捷表达式而已。示例如下:

代码语言:javascript复制
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 进行书写的话,效果如下:

代码语言:javascript复制
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{}

代码语言:javascript复制
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,而不是抛出异常,如下所示:运算符:?

代码语言:javascript复制
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中的对象创建示例代码如下:

PS:Groovy中的类的属性的get和set方法会在编译的时候自动创建。 不需要我们主动创建get和set方法。(PS:注意访问权限和final标识的区别哦。final 就不会创建set方法了)

代码语言: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
    }
}

因为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)

方法指针运算符.&可用于在变量中存储对方法的引用,以便以后调用:

代码语言:javascript复制
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获取指定类的构造函数的方法指针。示例代码如下:

代码语言:javascript复制
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 进行了解学习。

如果看不懂,可以回来看看我写的学习分享资料。

下一篇,继续分享关于运算符的相关学习笔记。

0 人点赞