16. Groovy 面向对象编程-类成员学习-第一篇

2022-12-08 17:54:27 浏览数 (2)

1. 介绍

Groovy学习笔记第16篇。接着上一篇介绍的类和数据类型,继续学习面向对象的相关知识。

本篇主要是类成员的相关知识点学习。

2. 构造函数-Constructors

构造函数是用于初始化具有特定状态的对象的特殊方法。与普通方法一样,只要每个构造函数都有唯一的类型入参,类就可以声明多个构造函数。如果对象在构造过程中不需要任何参数,则可以使用无参数构造函数。如果没有提供构造函数,Groovy编译器将提供一个空的无参数构造函数。

Groovy支持两种创建样式:

  • 位置参数的使用方式与Java构造函数的使用方式类似。
  • 命名参数允许您在调用构造函数时指定参数名称。

下面来具体介绍两种构造方式。

2.1 位置参数

要使用位置参数创建对象,相应的类需要声明一个或多个构造函数。对于多个构造函数,每个构造函数都必须具有唯一的类型签名。

构造函数也可以使用groovy.transform.TupleConstructor注释添加到类中(后面学习注释的时候再介绍)。

其实,就是标准的java的构造函数创建和应用方式,在Groovy中叫做位置参数定义构造函数。

为什么叫做位置参数是因为定义完毕后,初始化时传参是固定了的。示例如下:

代码语言:javascript复制
class Demo{
    String name
    String url
    Integer age
    //创建构造函数 
    Demo(String name, Integer age) {          
        this.name = name
        this.age = age
    }
    Demo(String name,String url){
        this.name = name
        this.url = url
    }
}

Demo zin = new Demo("z同学","zinyan.com")  //这种可以创建,创建成功
Demo z1 = new Demo(age:3,name:'zinyan')   //这种无法创建 会出现错误异常

上面的两种初始化成功的就是位置参数初始化,而不能成功的就是命名参数初始化。

结论:当我们重构构造函数后,命名参数初始化就会失败了。

我们如果希望在保持位置参数定义的前提条件下,仍然支持命名参数初始化。那么有两种办法:

  1. 创建一个空参数的构造函数。
  2. 创建一个Map对象入参的构造函数。

示例如下:

代码语言:javascript复制
class Demo{
    String name
    String url
    Integer age
    //创建构造函数 
    Demo(String name, Integer age) {          
        this.name = name
        this.age = age
    }
    Demo(String name,String url){
        this.name = name
        this.url = url
    }
    Demo(){

    }
}

Demo zin = new Demo("z同学","zinyan.com")
Demo z1 = new Demo(age:3,name:'zinyan')
println(z1.name) //输出 zinyan

或者如下:

代码语言:javascript复制
class Demo{
    String name
    String url
    Integer age
    //创建构造函数 
    Demo(String name, Integer age) {          
        this.name = name
        this.age = age
    }
    Demo(String name,String url){
        this.name = name
        this.url = url
    }
    Demo(Map map){
        this.age= map['age']
        this.name =map['name']
        this.url= map['url']
    }
}

Demo zin = new Demo("z同学","zinyan.com")
Demo z1 = new Demo(age:3,name:'zinyan')
println(z1.name) //输出 zinyan

基于这种特性,我们可以直接将Map对象通过as关键字强制转换为我们定义的对象。只要参数符合的话。

示例如下:

代码语言:javascript复制
class Demo{
    String name
    String url
    Integer age
    //创建构造函数 
    Demo( name,  age, url) {          
        this.name = name
        this.url = url
        this.age = age
    }
   
}
def zinyan = ['zinyan',3,"zinyan.com"] as Demo
println(zinyan.url) //输出: zinyan.com
println(zinyan.name) //输出: zinyan

2.2 命名参数

如果没有声明(或没有参数)构造函数,则可以通过以映射(属性/值对)的形式传递参数来创建对象。在希望允许多种参数组合的情况下,这很有用。否则,通过使用传统的位置参数,需要声明所有可能的构造函数。还支持使用第一个(可能也是唯一一个)参数是Map参数的构造函数-也可以使用groovy.transform.MapConstructor注释添加这样的构造函数。

示例如下:

代码语言:javascript复制
class Demo{
    String name
    String url
    Integer age
}
def zinyan =new Demo()
def zinyan1 = new Demo(name:'zinyan')
def zinyan2 =new Demo(age:2)
def zinyan3= new Demo(name:'zinyan',age:2,url:'https://zinyan.com')

我们可以减少很多的构造函数样板代码。实现动态的参数传递并创建对象。

但是命名参数会赋予构造函数调用者更多的权力,同时也增加了调用者的责任,保名称和值类型正确。因此,如果需要更大的控制,则最好使用位置参数声明构造函数。

总的来说,优缺点显著。

总结:

  • 可以提供无参数构造函数或第一个参数是Map的构造函数,来实现命名参数的构造函数调用支持。
  • 当声明无(或无参数)构造函数时,Groovy会用对无参数构造函数的调用替换命名构造函数调用,然后对每个提供的命名属性调用setter。
  • 当第一个参数是Map时,Groovy将所有命名参数组合成一个Map(不考虑排序),并将该Map作为第一个参数提供。如果您的属性被声明为final,这可能是一个很好的方法(因为它们将在构造函数中设置,而不是使用setter在之后设置。ps:final声明的属性不会自动生成set方法的)。
  • 通过提供位置构造函数以及无参数或映射构造函数,可以支持命名和位置构造。
  • 可以通过使用构造函数来支持混合构造,其中第一个参数是Map,但还有其他位置参数。但是这种样式需要谨慎使用。

3. 方法-Methods

Groovy方法与其他语言非常相似。所有高级语言定义方法的底层逻辑可以说是一样的。只是代码表现形式有些差异。而原理和概念可以说是通用的

3.1 方法定义

使用返回类型或def关键字定义方法的返回值。方法还可以接收任意数量的参数,这些参数可能没有显式声明它们的类型。Java修饰符可以正常使用,如果没有提供可见性修饰符,则该方法是公共的public

Groovy中的方法总是返回一些值。如果未提供return语句,将返回在执行的最后一行中计算的值。示例如下:

代码语言:javascript复制
//创建一个 返回值为def的方法
def someMethod() {
     '这是一个动态函数def 返回'
 } 

//创建一个 返回值为String的方法。 
String anotherMethod() {
    def s= 1 2
    'zinyan.com' 
}
//创建一个def入参的方法,返回值为def类型             
def thirdMethod(param1) {
     "$param1 是你传进入的参数" 
}                   
//创建一个静态方法,返回值为String ,入参为String类型
static String fourthMethod(String param1) {
     "$param1 是String 类型值" 
}
//创建一个无返回值的方法
void method(param1){
     "$param1 在方法中打印的,没有返回"
}

println(someMethod())  //打印:这是一个动态函数def 返回
println(anotherMethod())  //打印:zinyan.com
println(thirdMethod('zinyan'))  //打印:zinyan 是你传进入的参数
println(fourthMethod('zinyan'))  //打印:zinyan是String 类型值
println(method('zinyan.com'))  //打印: null   因为这个方法没有返回

上面的示例,可以说将基本常见的方法创建都进行了展示。

必要注意的一点在于:返回值可以不用return关键字进行修饰。也可以使用return关键。

3.2 命名参数

与构造函数一样,也可以使用命名参数调用普通方法。为了支持这种表示法,在方法的第一个参数是Map时使用了一个约定。在方法体中,可以像在映射表(map.key)中一样访问参数值。如果方法只有一个map参数,则必须命名所有提供的参数。

示例如下:

代码语言:javascript复制
//创建一个入参为Map对象的方法
def foo(Map args) {
     "${args.name}: ${args.age}"
 }

 //调用方法并给它传值
foo(name: 'Marie', age: 1)

这种写法,如果命名配置进行了修改,很容易造成错误。

我们也可以混合命名参数和位置参数在普通方法中的使用。示例如下:

代码语言:javascript复制
//可以通过map 传递入参,和int入参
def foo(Map args, Integer number) {
     "${args.name}: ${args.age}, and the number is ${number}" 
}
//使用方法1:
def x =foo(name: 'zin', age: 3, 20)
//使用方法2:  
def y =foo(40, name: 'yan', age: 2)  

println "$x"  //输出:zin: 3, and the number is 20
println "$y"  //输出:yan: 2, and the number is 40

通过上面的示例,你会发现 Integer值不管是在前面,还是在后面,效果是一样的。

但是请注意,千万不能将Integer混合在命名参数中间去,例如:

代码语言:javascript复制
def y =foo( name: 'yan',40, age: 2)  

这样会出现异常:

代码语言:javascript复制
Caught: groovy.lang.MissingPropertyException: No such property: y for class: tempCodeRunnerFile
groovy.lang.MissingPropertyException: No such property: y for class: tempCodeRunnerFile
    at tempCodeRunnerFile.run(tempCodeRunnerFile.groovy:1)

还有一种情况,我们如果将Map入参定义在方法非第一位参数,然后使用命名参数传值。那么也会出现MissingPropertyException异常。示例如下:

代码语言:javascript复制
//可以通过map 传递入参,和int入参
def foo(Integer number,Map args) {
     "${args.name}: ${args.age}, and the number is ${number}" 
}
//使用方法1: 
 def x =foo(name: 'zin', age: 3, 20)
//使用方法2:  
def y =foo( 40,name: 'yan', age: 2)  

不管是方法1还是方法2的调用。都会出现类似下面的异常:

代码语言:javascript复制
Caught: groovy.lang.MissingMethodException: No signature of method: Zinyan.foo() is applicable for argument types: (LinkedHashMap, Integer) values: [[name:yan, age:2], 40]
Possible solutions: foo(java.lang.Integer, java.util.Map), run(), run(), any(), find(), wait()
groovy.lang.MissingMethodException: No signature of method: Zinyan.foo() is applicable for argument types: (LinkedHashMap, Integer) values: [[name:yan, age:2], 40]
Possible solutions: foo(java.lang.Integer, java.util.Map), run(), run(), any(), find(), wait()
    at Zinyan.run(Zinyan.groovy:8)

所以,如果需要方法支持命名入参,那么必须在方法定义的第一个参数设置为Map类型。

我们如果在第二个参数或者第三个参数作为Map传入。那么我们就必须使用显式的Map对象了,示例如下:

代码语言:javascript复制
//可以通过map 传递入参,和int入参
def foo(Integer number,Map args) {
     "${args.name}: ${args.age}, and the number is ${number}" 
}
def y =foo( 40,[name: 'yan', age: 2])  

尽管Groovy允许您混合命名参数和位置参数,但这可能会导致不必要的混淆。小心混合命名和位置参数。

3.3 默认参数

默认参数使参数成为可选参数。如果未提供参数,则该方法采用默认值。示例如下:

代码语言:javascript复制
//创建了一个方法,传值String 和Int,而int的默认值为1023
def foo(String par1, Integer par2 = 1023) {
     [name: par1, age: par2]
 }
 println(foo('zinyan'))  //输出:[name:zinyan, age:1023]

例如上面的代码。我没有给par2传值,那么它就会采用默认的1023这个值,我们如果传值就会替换为我们传入的值。这就是默认参数的定义了。

如果没有传值,参数将从右侧删除,但强制参数永远不会删除。

代码语言:javascript复制
def baz(a = 'a', int b, c = 'c', boolean d, e = 'e') {
     "$a $b $c $d $e" }
println(baz(42, true) )  //输出:a 42 c true e
println(baz('A', 42, true ) )// 输出:A 42 c true e
println(baz('A', 42, 'C', true) )  //输出:A 42 C true e
println(baz('A', 42, 'C', true, 'E') )  //输出:A 42 C true E

同样的规则适用于构造函数和方法。如果使用@TupleConstructor,则会应用其他配置选项。有关于注解的相关知识,之后会进行分享。

3.4 可变参数

Groovy支持参数数量可变的方法。示例如下:

代码语言:javascript复制
//创建一个可变的入参的方法
def foo(Object... args) {
     args.length 
}

println(foo()) //输出 :0
println(foo(1024)) //输出:1
println(foo(1024,2048)) //输出:2

其实本质上就是一个T[]的数组。我们可以通过...修饰符进行代替而已。相关的操作在Java中也是一样的。

上面的方法可以变动为:

代码语言:javascript复制
//创建一个可变的入参的方法
def foo(Object[] args) {
     args.length 
}

println(foo()) //输出 :0
println(foo(1024)) //输出:1
println(foo(1024,2048)) //输出:2

如果使用null作为可别参数调用带有可变参数的方法,则该参数将为null,而不是长度为1的数组,其中null是唯一的元素。

示例如下:

代码语言:javascript复制
//创建一个可变的入参的方法
def foo(Object... args) {
     args
}

println(foo(null)) //输出 :null

如果使用数组作为参数调用可变参数的方法,则参数将是该数组,而不是长度为1的数组,该数组将给定数组作为唯一元素。

代码语言:javascript复制
//创建一个可变的入参的方法
def foo(Object... args) {
     args
}
String[] str=['zin','yan','com']
println(foo(str)) //输出 :[zin, yan, com]

另一个重要点是可变参数与方法重载相结合。在方法重载的情况下,Groovy将选择最具体的方法。例如,如果一个方法foo接受一个T类型的可变参数,而另一个方法foo也接受一个类型为T的参数,则首选第二个方法。示例代码如下:

代码语言:javascript复制
//创建一个可变的入参的方法
def foo(Object... args) {
     'Z同学'
}
def foo(Object x){
    'zinyan'
}

println(foo())  //输出: Z同学
println(foo(1)) //输出: zinyan
println(foo(1,2,3,4)) //输出: Z同学

4. 小结

本篇学习了构造函数和方法的一些创建和调用过程。其中方法的介绍还没有完毕,下一篇继续学习方法的相关定义和配置。

本篇内容参考于Groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_class_members

如果觉得我归纳的还可以,希望能够给我点个赞。谢谢。

下一篇继续学习方法的相关配置,以及字段和属性的定义。

0 人点赞