1.介绍
本篇为Groovy学习笔记第十八篇,学习类成员知识点中,字段和属性的相关定义。
本篇内容跟多的是一些规范性和概念的介绍。会比较枯燥。
2. 字段-Fields
字段是指存储数据的类、接口或特征的成员。Groovy源文件中定义的字段具有:
- 强制访问修饰符(公共
public
、受保护protected
或私有private
)。 - 一个或多个可选修饰符(
static
、final
、synchronized
)。 - 可选类型。
- 名称必须有。
示例如下:
代码语言:javascript复制class Demo {
//一个名为id的私有的int型字段
private int id
//一个名为description的受保护的String类型的字段
protected String description
//一个名为DEBUG的公共的,静态的,不可变的,参数值为false的布尔型字段。
public static final boolean DEBUG = false
}
字段可以在声明的时候直接初始化。例如上面的DEBUG
字段。
在Groovy中,我们可以省略字段的类型,例如:
代码语言:javascript复制class BadPractice {
private mapping
}
但是,建议大家还是使用定义类型的字段。提高代码可读性。例如:
代码语言:javascript复制class GoodPractice {
private Map<String,String> mapping
}
如果以后要使用可选的类型检查,这两者之间的区别很重要。作为记录类设计的一种方式,它也很重要。
在某些情况下,如脚本编写或如果您想依赖动态类型,则省略类型可能很有用。
总而言之,省略数据类型的字段定义,在Groovy中是支持的。脚本编写中通常使用省略写法比较多。
3. 属性-properties
属性是类的外部可见特征。通常就是给字段提供的get和set方法。Groovy遵循java的基本约束的前提下,提供了一种更简单的方法来定义属性。
- 可以缺少访问修饰符(不用添加:
public
,protected
和private
)。 - 可以有一个或者多个可选修饰符(
static
,final
,synchronized
)。 - 返回类型可选。
- 名称必须有。
本段讲述的其实就是类里面的get和set方法的定义规则。
例如:
代码语言:javascript复制class Person {
String name
int age
}
Groovy 会自动给上面的字段添加getName()
和setName()
,以及getAge()
和setAge()
方法。
而如果我们定义修饰符为final。示例:
代码语言:javascript复制class Person {
final String name
final int age
Person(String name, int age) {
this.name = name
this.age = age
}
}
Groovy只会自动创建getName()
和getAge()
方法。不会创建set方法。我们如果要初始化就只能在构造函数中对参数进行初始化赋值了。
在前面介绍运算符时,介绍过类成员变量的缩写方式,可以省略get和set写法:
代码语言:javascript复制class Person {
String name
int age
}
def p = new Person()
p.name = 'zinyan' //可以直接赋值 ,调用的就是变量的 set方法
println(p.name) //打印的时候,调用的是变量的 get方法,输出:zinyan
同时,根据类的properties字段,可以获取到全部的类属性值。例如:
代码语言:javascript复制class Person {
String name
int age
}
def p = new Person()
p.name ='zinyan'
p.age= 1024
println(p.properties.keySet()) //输出:[name, age, class]
println(p.properties.values()) //输出:[zinyan, 1024, class Person]
可以一次性获取全部的字段名和参数值。但还是建议大家使用方法进行访问。
同时,如果我们创建get和set方法时,并没有创建类成员变量。Groovy也可以自动进行识别。例如:
代码语言:javascript复制class PseudoProperties {
void setName(String name) {}
String getName() {
"这是Zinyan网站内容"
}
int getAge() { 1024 }
void setGroovy(boolean groovy) { }
}
def p = new PseudoProperties()
p.name = 'zinyan'
println(p.name) //打印:这是Zinyan网站内容
println(p.age) // 打印1024
p.groovy = true
如果是标准的get和set方法。我们就可以直接采用上面的节省写法。但是要注意了,这只是方法的节省写法。并不代表Groovy会自动帮我们创建变量进行存储哦。
这种语法糖是用Groovy编写的许多DSL的核心。
也就是说很多实用Groovy语言的插件工具在编写DSL语法时,逻辑就是基于Groovy的这个特性进行了代码的缩写。
3.1 命名规范
通常建议属性名称的前两个字母为小写,对于多单词属性,使用驼峰大小写。对于其他数据类型都是get 属性名,或者set 属性名。
而对于boolean类型,它的get方法是:is 属性名来定义的。
例如:
代码语言:javascript复制getLength() 指向的就是 this.length
setFirstName() 指向的就是 this.firstname
isEmpty 指向的就是 this.empty
以大写字母开头的属性名称将具有仅添加前缀的getter/setter。因此,属性Foo
是允许的,即使它没有遵循推荐的命名约定。对于此属性,访问器方法将是setFoo
和getFoo
。这样做的结果是,不允许同时拥有foo和foo属性,因为它们将具有相同的命名访问器方法。示例:
class PseudoProperties {
String Foo
}
def s =new PseudoProperties()
s.Foo ='zinyan'
但是不能同时有另外的属性定义名称为: String foo
因为它们两个身材的get和set方法名称会冲突
Java规范为通常可能是首字母缩写的属性提供了一个特例。如果属性名的前两个字母是大写的,则不执行大写(更重要的是,如果从访问器方法名生成属性名,则不进行大写)。因此,getURL
将是URL属性的get方法。
由于Java规范中特殊的“首字母缩写处理”属性命名逻辑,与属性名称的转换是不对称的。这导致了一些奇怪的边缘情况。
Groovy采用了一种命名约定,避免了一种可能看起来有点奇怪但在Groovy's设计时很流行的模糊性,并且由于历史原因(到目前为止)一直存在。Groovy查看属性名称的第二个字母。如果是大写,则该属性被视为首字母缩写形式的属性之一,不进行大写,否则进行正常大写。尽管我们从不推荐它,但它确实允许您拥有看似“重复命名”的属性,例如,您可以拥有aProp
和AProp
,或者pNAME
和PNAME
。get方法分别是getaProp
和getAProp
,以及getpNAME
和getPNAME
。
PS:这些规范,不要追求死记硬背。在实际使用中多看,多写。我们就会理解这些规范了。
3.2 属性的修改
一般情况下,我们创建的字段都会自动生成对应的属性访问方法。但是有两个修饰符需要注意:
- final 只读属性。不会生成set方法
- static 静态属性。会生成一个全局的get和set静态属性访问。
示例:
代码语言:javascript复制class PseudoProperties {
static String Foo
}
PseudoProperties.Foo ='zinyan'
println(PseudoProperties.Foo) //输出:zinyan
3.3 属性中的注释
注释(包括与AST转换关联的注释)将复制到属性的背景字段中。这允许将适用于字段的AST变换应用于属性,例如:
代码语言:javascript复制class Animal {
int lowerCount = 1024
@Lazy String name = { lower().toUpperCase() }()
String lower() { lowerCount ; 'zinyan' }
}
def a = new Animal()
println(a.lowerCount) //输出:1024
println(a.name) //输出:ZINYAN
println(a.lowerCount) //输出:1025
其中Lazy注释 是一个懒加载注释逻辑。
3.4 使用显式字段拆分属性定义
当我们的类设计遵循与Java实践一致的某些约定时,Groovy的属性语法是一种方便的速记。
如果我们的类不完全符合这些约定,当然可以像在Java中那样手工编写getter、setter和返回字段。
然而,Groovy确实提供了拆分定义功能,它仍然提供了缩短的语法,同时允许对约定进行轻微调整。对于拆分定义,我们可以编写具有相同名称和类型的字段和属性。只有一个字段或属性可以具有初始值。
对于拆分属性,字段上的注释保留在属性的背景字段上。定义的属性部分的注释被复制到getter和setter方法上。
如果标准属性定义不完全符合我们的需求,该机制允许属性用户可能希望使用的许多常见变体。
例如,如果支持字段应该是受保护的protected
而不是私有的private
实现示例如下所示:
class HasPropertyWithProtectedField {
protected String name
String name
}
和上面一样主动添加修饰符,也可以同注解的方式实现:
代码语言:javascript复制class HasPropertyWithPackagePrivateField {
String name
@PackageScope String name
}
作为最后一个示例,我们可能希望应用与方法相关的AST转换,或者通常对setter/getter应用任何注释,例如,使访问器同步:
代码语言:javascript复制class HasPropertyWithSynchronizedAccessorMethods {
private String name
@Synchronized String name
}
PS: 不得不说,我对于这块的并不是太能理解。后面使用中渐渐熟悉吧。
3.5 显示访问方法
如果类中存在getter或setter的显式定义,则不会自动生成get和set方法。这允许您根据需要修改此类getter或setter的正常行为。通常不考虑继承的set和set方法,但如果继承的访问程序方法被标记为final,这也将导致不生成额外的访问程序,以满足此类方法不进行子类化的最终要求。
也就是说,我们如果不创建get和set方法。Groovy会自动帮我们创建。我们如果自己创建了get和set方法,Groovy就不会自动创建了。
我们如果继承父类的时候标注为final了。子类也不会获取和自动生成相应的get和set方法。
4. 小结
以上内容,可以通过groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_fields_and_properties 学习。
本篇内容,比较偏概念性的知识。如果我们没有写过相关Groovy代码,理解可能会有些困难。
下一篇介绍,注解方面的知识点。