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') //这种无法创建 会出现错误异常
上面的两种初始化成功的就是位置参数初始化,而不能成功的就是命名参数初始化。
结论:当我们重构构造函数后,命名参数初始化就会失败了。
我们如果希望在保持位置参数定义的前提条件下,仍然支持命名参数初始化。那么有两种办法:
- 创建一个空参数的构造函数。
- 创建一个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
语句,将返回在执行的最后一行中计算的值。示例如下:
//创建一个 返回值为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
异常。示例如下:
//可以通过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
如果觉得我归纳的还可以,希望能够给我点个赞。谢谢。
下一篇继续学习方法的相关配置,以及字段和属性的定义。