1. 介绍
本篇内容为Groovy学习第二十篇,上一篇学习了关于注解Annotations
的相关知识,而这一篇学习Groovy的特性Traits
。
Traits
是面向对象编程中的一种概念,它表示一组可用于扩展类功能的方法。
本节的知识点有点多,将会拆分为多篇内容进行分享。
特性是语言的一种结构构造,它允许:
- 行为组成。
- 接口的运行时实现。
- 行为覆盖。
- 兼容静态类型检查/编译
等等。
2. trait 关键字
在Groov中,通过trait
修饰符定义Traits,它们可以被视为同时携带默认实现和状态的接口。
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
抽象方法:
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
私有方法。示例:
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当做一个超类。示例如下:
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定义的属性,其实就和在类中间定义属性一样的使用。
这也是trait
和interface
之间比较大的一个差异点。
前面我们也说过,Trait支持private
和public
访问修饰符。所以,它的属性也可以配置这两种修饰符。示例如下:
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 变量访问
上面介绍了普通的成员变量的访问,还有一种情况,可以通过__
标识符访问。示例如下:
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:
trait FlyingAbility {
String fly() { "我想飞的更高!" }
}
trait SpeakingAbility {
String speak() { "我想要大声的怒号!" }
}
如果有一个类implement
同时继承了这两个trait
对象,示例如下:
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的知识点。