15. Groovy 面向对象编程-类型和类知识学习

2022-12-08 17:54:00 浏览数 (1)

1.介绍

前面介绍了基本类结构,运算符等知识,从这篇开始学习Groovy中的面向对象的相关知识。这是一个大的分类。后面会通过多篇内容进行学习分享。

本篇先介绍类型Types和类Classes的面向对象的相关知识。

PS:在学习和整理Groovy的相关语法知识的同时,我也对于Java的相关知识进行了重新梳理,嗯感觉还是挺不错的。

2. 类型-types

Groovy支持Java语言规范中定义的几种基本数据类型:

  • 整数类型: byte (8 bit), short (16 bit), int (32 bit) 和 long (64 bit)
  • 浮点数类型: float (32 bit) 和double (64 bit)
  • 布尔类型:boolean (true or false)
  • 字符类型:char (16 bit,可以用于表示数字类型和表示 UTF-16 编码)

与Java一样,Groovy在需要与任何基元类型对应的对象时使用各自的包装类:

基本类型

包装类

boolean

Boolean

char

Character

short

Short

int

Integer

long

Long

float

Float

double

Double

例如,当调用一个需要包装类的方法并将一个基本类型变量作为参数传递给它时,就会发生自动装箱和取消装箱,反之亦然。这与Java类似,但Groovy更进一步。

在大多数情况下,可以将基本数据类型视为完全对象包装等效物。例如,可以对基本数据类型调用.toString().equals()。Groovy根据需要在引用和基本数据之间自动包装和展开。

简单理解就是,整个数据类型的转换。在某些情况下Groovy帮我们自动封装了,不用我们手动进行各种转换。

示例如下:

代码语言:javascript复制
//创建了一个Demo类
class Demo {
    static int zinyan  //创建一个基本int 数据类型zinyan
}
println(Demo.class.getDeclaredField('zinyan').type) //输出 int
println(Demo.zinyan.class)  //输出:class java.lang.Integer

现在可能会担心,每次在对基本数据类型值的引用上使用数学运算符时(进行加减乘除等运算),都会产生取消装箱和重新装箱的成本。但事实并非如此,因为Groovy会将运算符编译成它们的方法等价物,并使用它们。此外,当调用Java方法时,Groovy将自动取消对基本数据的装箱,该方法接受基本数据参数并自动装箱基本数据方法从Java返回的值。但是,请注意,与Java的方法解析有一些不同。(PS:具体如何不同,我也不太了解。后面的学习和使用也许会帮我理解吧)

2.1 引用类型

除了基本类型,其他的都是一个对象,并有一个定义其类型的关联类。也就是我们自己创建的各种Bean对象等都是引用类型。

我们可以声明两个变量,类型为String和List,如下所示:

代码语言:javascript复制
String url = 'zinyan.com'
List actors = ['打开Z同学网站', '关闭Z同学网站']

要注意,String类型不算基本数据类型哦。

2.2 泛型

java中使用T关键字来代替泛型,Groovy在泛型方面与Java具有相同的概念。定义类和方法时,可以使用类型参数并创建泛型类、接口、方法或构造函数。泛型类和方法的使用,无论它们是在Java还是Groovy中定义的,都可能涉及提供类型参数。

例如List的变量是泛型的:我们可以在创建的时候给它指定为Stringe类型:

代码语言:javascript复制
List<String> actors = ['打开Z同学网站', '关闭Z同学网站']

Java使用类型擦除来向后兼容早期版本的Java。动态Groovy可以被认为是更具攻击性的应用类型擦除。通常,编译时会检查较少的泛型类型信息。Groovy的静态特性在泛型信息方面采用了类似于Java的检查。

总而言之,Groovy也是支持泛型操作的,使用效果和Java中的泛型使用差不多。官网针对这块的介绍并没有太多,要记住泛型起始和动态变量def是有差异的。但是我们平常普通使用时两者之间差异并不大。

3. 类-classes

Groovy类与Java类非常相似,并且在JVM级别与Java类兼容。它们可能有方法、字段和属性(想想JavaBeans属性,但样板较少)。类和类成员可以具有与Java中相同的修饰符(publicprotectedprivatestatic等),但在源代码级别有一些细微的差异。

Groovy类相较于和Java类的区别:

  • 没有可见性修饰符的类或方法将自动公开(可以使用特殊注释来实现包私有可见性)。
  • 没有可见性修饰符的字段会自动转换为属性,这导致代码不那么冗长,因为不需要显式的getter和setter方法。更多的内容在后面后介绍。
  • 类不需要与它们的源文件定义具有相同的基名称,但在大多数情况下强烈建议这样做。
  • 一个源文件可能包含一个或多个类(但如果一个文件包含任何不在类中的代码,则该文件被视为脚本)。脚本只是具有一些特殊约定的类,并且将与它们的源文件同名(因此不要在与脚本源文件同名的脚本中包含类定义)。

通过这些定义,结合前面分享的文章内容。我们会有一种恍然大悟的感觉。这些内容只是一个定义的归纳总结。我们在实际使用Groovy开发过程中,已经实践了上面的这些区别。

示例代码:

代码语言:javascript复制
class Person {                       

    String name                      
    Integer age

    def increaseAge(Integer years) { 
        this.age  = years
    }
}

当我们没有给类添加(publicprotectedprivate等修饰符)的时候,Groovy默认会将它公开,可以说就是public权限了。

而我们要使用该类很简单,通过new 就可以了。示例如下:

代码语言:javascript复制
def p = new Person()

3.1 内部类-inner class

内部类在其他类中定义。封闭类可以像平常一样使用内部类。另一方面,内部类可以访问其封闭类的成员,即使它们是私有的。不允许封闭类以外的类访问内部类。示例如下:

代码语言:javascript复制
class Outer {
    private String privateStr

    def callInnerMethod() {
        new Zinyan().methodA()   //引用了一个内部类
    }
    //创建一个内部类
    class Zinyan {                   
        def methodA() {
            println "${privateStr}." 
        }
    }
}

我们使用内部类的一些优势:

  • 通过对其他类隐藏内部类来增加封装,而其他类不需要知道它。这也导致了更干净的包和工作空间。
  • 通过对仅由一个类使用的类进行分组,它们提供了一个良好的组织。
  • 它们导致了更易于维护的代码,因为内部类靠近使用它们的类。

内部类通常是外部类需要其方法的某个接口的实现。下面的代码说明了这种典型的使用模式,这里与线程一起使用。示例如下:

代码语言:javascript复制
class Outer2 {
    private String privateStr = 'some string'

    def startThread() {
       new Thread(new Inner2()).start()
    }

    class Inner2 implements Runnable {
        void run() {
            println "${privateStr}."
        }
    }
}

PS: 我们在代码中创建对外暴露的接口等类的时候,大部分都是使用的内部类来实现的。例如Android代码中的OnClickListener就是一个典型的内部类创建场景。

Groovy 3 还支持非静态内部类实例化的Java语法,例如:

代码语言:javascript复制
class Computer {
    class Cpu {
        int coreNumber

        Cpu(int coreNumber) {
            this.coreNumber = coreNumber
        }
    }
}

我们除了上面的命名的写法,还可以采用匿名方式创建内部类。示例如下:

代码语言:javascript复制
class Outer3 {
    private String privateStr = 'zinyan.com'

    def startThread() {
        new Thread(new Runnable() {      
            void run() {
                println "${privateStr}."
            }
        }).start()                       
    }
}

这种写法,如果是熟悉java的可能很容易就理解了。

当我们的内部类只使用一次的时候,可以不用定义专门的内部类,提供匿名内部类就可以了。

3.2 抽象类-Abstract class

有抽象方法的类必须定义为抽象类。关键字:abstract。示例:

代码语言:javascript复制
abstract class Abstract {         
    String name

    //创建了一个抽象方法
    abstract def abstractMethod() 

    def concreteMethod() {
        println 'concrete'
    }
}

抽象类可以包含字段/属性和具体方法,但接口只包含抽象方法(方法签名)。此外,一个类可以实现多个接口,而它只能扩展一个类,无论抽象与否。

抽象类实例的时候,必须重构抽象方法。

这里只是简单涉及,整体来说Groovy中关于抽象类的定义和使用与Java中保持一致。

而如果对于抽象类的相关概念不太了解的,建议可以学习java中关于抽象类和匿名类的定义。这里我就不深入扩展了。

3.3 继承

面向对象的三要素:封装,继承和多态。如果对于这方面的概率不太熟悉的话,下面的内容阅读起来可能会稍微费劲一些。

Groovy中的继承类似于Java中的继承。它为子类(或子类)提供了重用父类(或超类)中的代码或属性的机制。通过继承关联的类形成继承层次结构。通用行为和成员被上层次结构实现以减少重复。专业化等在子类中实现。

支持的不同的继承类型为:

  • 实现继承:子类重用来自超类(父类)或一个或多个特征的代码(方法、字段或属性)。
  • 契约继承:其中类承诺提供在超类中定义的特定抽象方法,或在一个或多个特性或接口中定义的抽象方法。

3.4 超类-superclasses

父类与子类共享可见字段、属性或方法。一个子类最多可以有一个父类。extends关键字来实现继承关系。

示例如下:

代码语言:javascript复制
class Person{
    String name
    int age
}

class Blog extends Person{
    String url

    String toString(){
        "名称:$name, 年龄:$age, 网址:$url"
    }
}

Blog blog = new Blog(name:'Z同学',url:'zinyan.com',age:3)
println(blog)  //输出 名称:Z同学, 年龄:3, 网址:zinyan.com

3.5 接口-Interfaces

接口定义了类需要遵守的契约。接口只定义了需要实现的方法列表,但不定义方法的实现。

代码语言:javascript复制
interface Greeter {                                         
    void greet(String name)                                 
}

定义了一个接口,所有继承该接口的类,都需要实现接口中定义方法。例如上面的接口Greeter它的方法greet继承时必须实现该接口。

或者类定义为抽象类就可以不用实现接口中的方法。

接口的方法总是公共的,修饰符为public 。不管是类还是方法都是public。我们如果定义为私有或者其他的都是错误的示例如下:

代码语言:javascript复制
interface Greeter {
    protected void greet(String name)       //错误的声明方式      
}

例如我们Greeter接口:

代码语言:javascript复制
interface Greeter {                                         
    void greet(String name)                                 
}
class SystemGreeter implements Greeter {                    
    void greet(String name) {                               
        println "Hello $name"
    }
}

SystemGreeter blog = new SystemGreeter()
if(blog instanceof  Greeter){  // 判断blog对象是否属于 Greeter类型
    println'true'  //输出true
}else{
    println'false'
}

一个接口可以扩展另一个接口:也就是说接口与接口之间可以继承通过 extends关键字。

而接口与类之间,只能通过implements关键字实现扩展。示例如下:

代码语言:javascript复制
interface ExtendedGreeter extends Greeter {                 
    void sayBye(String name)
}

我们如果实现类扩展了ExtendedGreeter接口,就需要重构实现 greet方法和sayBye方法。示例如下:

代码语言:javascript复制
interface Greeter {                                         
    void greet(String name)                                 
}

interface ExtendedGreeter extends Greeter {                 
    void sayBye(String name)
}

class SystemGreeter implements ExtendedGreeter {                    
    void greet(String name) {                               
        println "欢迎 $name"
    }

    void sayBye(String name){
        println "再见 $name"
    }
}

如果是继承了接口,我们可以通过as关键字将类强制转换为指定的接口对象,例如:

代码语言:javascript复制
def greeter = new SystemGreeter()                              
def coerced = greeter as Greeter

那么coerced 对象类型就是Greeter了。

Groovy接口不支持像Java8接口那样的默认实现。

4. 小结

本章简单学习了Groovy中的类的定义和基本类型。这些知识和java的知识可以说是一样的。即时相差也差不了多少。不影响我们的使用和定义。

本篇内容更多的参考官网:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_object_orientation 的介绍。

上面介绍的知识可能比较概念化。后面将会更具体的介绍Groovy中的面向对象编程需要注意的要点以及数据类型等等知识。

0 人点赞