18. Groovy 面向对象编程-类成员中字段和属性学习-第三篇

2022-12-08 17:55:31 浏览数 (1)

1.介绍

本篇为Groovy学习笔记第十八篇,学习类成员知识点中,字段和属性的相关定义。

本篇内容跟多的是一些规范性和概念的介绍。会比较枯燥。

2. 字段-Fields

字段是指存储数据的类、接口或特征的成员。Groovy源文件中定义的字段具有:

  • 强制访问修饰符(公共public、受保护protected或私有private)。
  • 一个或多个可选修饰符(staticfinalsynchronized)。
  • 可选类型。
  • 名称必须有。

示例如下:

代码语言: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的基本约束的前提下,提供了一种更简单的方法来定义属性。

  • 可以缺少访问修饰符(不用添加:publicprotectedprivate)。
  • 可以有一个或者多个可选修饰符(staticfinalsynchronized)。
  • 返回类型可选。
  • 名称必须有。

本段讲述的其实就是类里面的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是允许的,即使它没有遵循推荐的命名约定。对于此属性,访问器方法将是setFoogetFoo。这样做的结果是,不允许同时拥有foo和foo属性,因为它们将具有相同的命名访问器方法。示例:

代码语言:javascript复制
class PseudoProperties {
    String Foo 
}
def s =new PseudoProperties()
s.Foo ='zinyan'   

但是不能同时有另外的属性定义名称为: String foo 因为它们两个身材的get和set方法名称会冲突

Java规范为通常可能是首字母缩写的属性提供了一个特例。如果属性名的前两个字母是大写的,则不执行大写(更重要的是,如果从访问器方法名生成属性名,则不进行大写)。因此,getURL将是URL属性的get方法。

由于Java规范中特殊的“首字母缩写处理”属性命名逻辑,与属性名称的转换是不对称的。这导致了一些奇怪的边缘情况。

Groovy采用了一种命名约定,避免了一种可能看起来有点奇怪但在Groovy's设计时很流行的模糊性,并且由于历史原因(到目前为止)一直存在。Groovy查看属性名称的第二个字母。如果是大写,则该属性被视为首字母缩写形式的属性之一,不进行大写,否则进行正常大写。尽管我们从不推荐它,但它确实允许您拥有看似“重复命名”的属性,例如,您可以拥有aPropAProp,或者pNAMEPNAME。get方法分别是getaPropgetAProp,以及getpNAMEgetPNAME

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 实现示例如下所示:

代码语言:javascript复制
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代码,理解可能会有些困难。

下一篇介绍,注解方面的知识点。

0 人点赞