20. Groovy 面向对象编程-Traits特性学习-第一篇

2022-12-08 17:56:19 浏览数 (1)

1. 介绍

本篇内容为Groovy学习第二十篇,上一篇学习了关于注解Annotations的相关知识,而这一篇学习Groovy的特性Traits

Traits是面向对象编程中的一种概念,它表示一组可用于扩展类功能的方法。

本节的知识点有点多,将会拆分为多篇内容进行分享。

特性是语言的一种结构构造,它允许:

  • 行为组成。
  • 接口的运行时实现。
  • 行为覆盖。
  • 兼容静态类型检查/编译

等等。

2. trait 关键字

在Groov中,通过trait修饰符定义Traits,它们可以被视为同时携带默认实现和状态的接口

代码语言:javascript复制
trait ZinyanDemo {      //声明了一个特性对象,ZinyanDemo                     
        String visit() { "访问:https://zinyan.com" }     //定义了一个visit()方法     
}

上面介绍了,定义trait就是一个默认实现和状态的接口,那么我们就可以当接口对象来使用,例如:

代码语言:javascript复制
class Zinyan implements ZinyanDemo {}          
def b = new Zinyan()    
println(b.visit())  //输出:访问:https://zinyan.com

2.1 声明方法

在Trait中声明方法和类中声明方法是一样的,在上面的示例中就是声明的一个public 公共的方法。

除此外,还可以声明abstract抽象方法:

代码语言:javascript复制
trait ZinyanDemo {      //声明了一个特性对象,ZinyanDemo                     
        String visit() { "访问:https://zinyan.com" }     //定义了一个公共方法
    abstract String name()   //定义了一个抽象方法
}

我们如果定义了抽象方法,那么在引用该trait的时候就需要实现该抽象方法,例如:

代码语言:javascript复制
class Person implements ZinyanDemo {                     
    String name() { 'zinyan.com' }      //因为name()是一个抽象方法,所以必须实例化它                       
}

def p = new Person()
println(p.name()) //输出:zinyan.com

PS:到这里,我们可以得到一个结论,trait就和接口一样的使用,但是它相较于接口,可以有实现的方法。而接口中所有方法都必须是抽象的。 在这一点上trait就和抽象类有些相识了,但是我们使用抽象类的时候必须通过extends关键字,而使用trait可以使用implements关键字。

除了抽象类,trait还可以实现private 私有方法。示例:

代码语言:javascript复制
trait Zinyan {
    //创建一个私有方法
    private String urlInfo() {                      
        'https://zinyan.com'
    }
    String url() {
        def m = urlInfo()                           
        m //返回这个数据值
    }
}
class Demo implements Zinyan {}   //继承之后犹豫没有抽象方法,就可以不用实现任何方法。
def x =new Demo()
println(x.url()) //输出: https://zinyan.com
//而我们强制使用urlInfo方法 就会出现Exception
try {
    assert g.urlInfo()                              
} catch (MissingMethodException e) {
    println "trait中没有找到urlInfo方法"  //输出:trait中没有找到urlInfo方法
}

Trait只支持公共public和私有private方法,不支持protected 受保护的和package private包私有。

我们使用trait主要就是public 和private。当接口使用的。它实现的抽象的还是私有的方法最终都会被编译器加载到继承了trait的实体类中。

如果我们想使用final关键字修饰,避免方法被继承和扩展等情况。建议使用类的形式创建,而不是使用trait创建。

我个人的理解就是,trait是接口的扩展。但并不能取代基类(超类,父类等概念)。

也就是说该用基类进行搭建架构的,大家还是使用基类搭建。

3. this关键字

this表示实现实例。当前实例对象的引用。在trait中使用this,可以把trait当做一个超类。示例如下:

代码语言:javascript复制
trait Introspector {
    def whoAmI() { this }
}
class Foo implements Introspector {} //定义一个类实现Introspector

def foo = new Foo()

println( foo.whoAmI().is(foo)) //输出: true

因为this就是当前的实例对象。

小结:Trait中可以和Class中一样,使用this关键字,来指代本身。

4. interfaces 关键字

我们通常使用interfaces 关键字用来比对类的类型。例如判断该对象是否属于指定的类。

同时也可以判断对象是否属于某个接口。而trait可以说是接口的一种特殊情况。那么它也当然支持interfaces关键字的判断了。示例如下:

代码语言:javascript复制
 //创建一个 Named的接口类                         
interface Named {             
    String name()  //定义了一个抽象方法
}
//创建了一个trait对象,它继承了Named接口
trait Greetable implements Named {                      
    String greeting() { "嗨, ${name()} 欢迎光临Z同学小站!" }  //没有实现name方法,而是创建了一个greeting方法
}
//创建一个Person类,继承Greetable 那么就必须重构 抽象方法 name()
class Person implements Greetable {                     
    String name() { 'zinyan' }                             
}

def x = new Person()
println(x.greeting())  //输出:嗨,zinyan 欢迎光临Z同学小站!
println(x instanceof Named) //输出:true
println(x instanceof Greetable) //输出 true

通过上面的示例,可以看到trait实现的类,可以和接口对象一样,通过instanceof 进行判断。

5. 属性

trait不止是可以创建方法,还可以定义属性。

5.1 成员变量

我们通过简单的示例,看看trait的属性定义吧。示例如下:

代码语言:javascript复制
trait Named {
    String name                             
}
class Person implements Named {} 
//创建一个对象,我们传值饿name就是trait中定义的属性值。

def x = new Person(name:'zinyan.com')
println(x.name) //输出:zinyan.com

通过上面的示例,我们可以看到。通过trait定义的属性,其实就和在类中间定义属性一样的使用。

这也是traitinterface 之间比较大的一个差异点。

前面我们也说过,Trait支持privatepublic访问修饰符。所以,它的属性也可以配置这两种修饰符。示例如下:

代码语言:javascript复制
trait Counter {
    private int count = 0    //创建一个私有属性值               
    int count() {
        count  = 1
        count
    }       
}
//创建一个类,继承Counter对象。
class Foo implements Counter {}             
def f = new Foo()

println(f.count()) //输出: 1
println(f.count()) //输出:2

这与Java8虚拟扩展方法的主要区别在于虚拟扩展方法不携带状态,但特征Trait可以。此外,Groovy中的Trait从Java6开始就受到支持,因为它们的实现不依赖于虚拟扩展方法。这意味着,即使可以从Java类中将trait视为常规接口,该接口也不会有默认方法,只有抽象方法。

5.2 public 变量访问

上面介绍了普通的成员变量的访问,还有一种情况,可以通过__标识符访问。示例如下:

代码语言:javascript复制
trait Named {
    String name                             
}
class Person implements Named {} 

def x = new Person(name:'zinyan.com')
println(x.Named__name) //输出:zinyan.com

简而言之就是访问trait类型,我们可以将.全部替换为_而最后的变量就是__。例如有一个叫做Zinyan的trait对象。

它所在的包名结构为:com.zinyan.demo 。那么我们在其他地方使用这个对象可以写为: com_zinyan_demo_Zinyan

而如果Zinyan中有一个数据变量为String类型的url。那么就可以写为:def s = com_zinyan_demo_Zinyan__url

PS:虽然说有这种写法,但是Groovy中不建议大家这样使用。所以简单的了解一下相关知识点就可以了。

6.行为组成

trait可以被用来以一种受控的方式实现多重继承。例如我们创建两个不同的trait:

代码语言:javascript复制
trait FlyingAbility {                           
        String fly() { "我想飞的更高!" }          
}
trait SpeakingAbility {
    String speak() { "我想要大声的怒号!" }
}

如果有一个类implement同时继承了这两个trait对象,示例如下:

代码语言:javascript复制
class Demo implements FlyingAbility, SpeakingAbility {} 

def zin = new Demo()
println(zin.fly())  //输出:我想飞的更高!
println(zin.speak()) //输出:我想要大声的怒号!

trait鼓励在对象之间重用功能,并通过现有行为的组合来创建新类。

这个特性就和接口对象一样,可以多重组合。

多种trait组合成一个实体类的属性和方法。要比接口和抽象类更灵活。

7. 方法重载

仍然使用上面的示例,我们除了可以继承以外。我们还可以重载继承的方法。示例如下:

代码语言:javascript复制
//仍然是这两个trait对象
trait FlyingAbility {                           
        String fly() { "我想飞的更高!" }          
}
trait SpeakingAbility {
    String speak() { "我想要大声的怒号!" }
}

class Demo implements FlyingAbility, SpeakingAbility {
     String fly() { "zinyan.com 要飞的更高" }    
     String speak() { "zinyan.com 会分享更多的内容" }
} 

def zin = new Demo()
println(zin.fyl())  //输出:zinyan.com 要飞的更高
println(zin.speak()) //输出:zinyan.com 会分享更多的内容

我们可以直接重载trait中的方法。上面的方法都是public公共的,如果碰见了private的方法会怎么样?示例如下:

代码语言:javascript复制
//仍然是这两个trait对象
trait FlyingAbility {                           
        String fly() { 
            "我想飞的更高!" 
            }          
}
trait SpeakingAbility {
   private String speak() {
         "我想要大声的怒号!" 
         }
         String getSpeak(){
            "说的是:${speak()}"
         }
}

class Demo implements FlyingAbility, SpeakingAbility {
     String fly() { "zinyan.com 要飞的更高" }    
     String speak() { "zinyan.com 会分享更多的内容" }
} 

def zin = new Demo()
println(zin.fly())  //输出:zinyan.com 要飞的更高
println(zin.speak()) //输出:zinyan.com 会分享更多的内容
println(zin.getSpeak())  //输出:说的是:我想要大声的怒号!

可以看到,private定义的方法,完全不会受到Demo类的影响。保持了独立性。

8. 小结

学到了新的知识点Trait。这个特质在Java中是没有的,java中没有trait关键字。只能创建interface对象。

我们如果将interface的知识代入进来学习,就能很快理解trait的定义和作用了。

本质上来说trait可以说是一个超级接口对象。将接口能实现的场景做了一个很大的扩展。

以上内容可以参考Groovy官网:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_traits 进行学习。

本篇只是介绍了部分trait特性的知识,下一篇继续介绍trait的知识点。

0 人点赞