Scala入门系列(八):面向对象之trait

基础知识

1 将trait作为接口使用

此时Trait就与Java中的接口非常类似,不过注意,在Scala中无论继承还是trait,统一都是extends关键字。

Scala跟Java 8前一样不支持对类进行多继承,但是支持多重继承trait,使用with关键字即可


trait HelloTrait{ 

def sayHello(name: String) 

} 

trait MakeFriends{ 

def makeFriends(p: Person) 

} 

class Person(val name: String) extends HelloTrait with MakeFriends { 

def sayHello(name: String) = println("Hello, " + name) 

def makeFriends(p: Person) = println("hello " + p.name + ", I‘m " + name) 

} 

defined trait HelloTrait 

defined trait MakeFriends 

defined class Person 

scala> val p = new Person("spark") 

p: Person = Person@2f29e630 

scala> val p2 = new Person("jack") 

p2: Person = Person@52f118aa 

scala> p.sayHello("jack") 

Hello, jack 

scala> p.makeFriends(p2) 

hello jack, I‘m spark 

2 在trait中定义具体方法

Trait不仅可以定以抽象方法,还可以定以具体方法,此时Trait更像是包含了通过工具方法的东西。

有一个专有名词来形容这种情况,叫做Trait功能混入了类

举例:trait中可以包含一些很多类都通用的方法,比如说打印日志等,Spark中就是用了trait来定义了通用的日志打印方法。


trait Logger { 

def log(msg: String) = println("log: " + msg) 

} 

class Person(val name: String) extends Logger { 

def sayHello { println("Hello, I‘m " + name); log("sayHello is invoked") } 

} 

defined trait Logger 

defined class Person 

scala> val p = new Person("leo") 

p: Person = Person@3b0c38f2 

scala> p.sayHello 

Hello, I‘m leo 

log: sayHello is invoked 

3 在trait中定义具体字段

Trait可以定以具体field, 但是这种继承trait field的方式与继承class是原理不同的:如果是继承class获取的field,实际是定以在父类中的;而继承trait获取的field,就直接被添加到了继承类中。 

4 在trait中定义抽象字段

Trait中可以定以抽象field, 而trait中的具体方法可以使用抽象field,但是继承trait的类必须要覆盖抽象field,提供具体的值,否则程序会运行出错。


// trait中的具体方法可以使用抽象field 

trait SayHello{ 

val msg: String 

def sayHello(name: String) = println(msg + "," + name) 

} 

// 继承trait中必须覆盖抽象field 

class Person(val name: String) extends SayHello{ 

val msg: String = "hello" 

def makeFriends(p: Person){ 

sayHello(p.name) 

println("I‘m" + name + ", want to make friends with you") 

} 

} 

defined trait SayHello 

defined class Person 

// 测试 

scala> val p1 = new Person("leo") 

p1: Person = Person@67372d20 

scala> val p2 = new Person("Sparks") 

p2: Person = Person@4f1f2f84 

scala> p1.makeFriends(p2) 

hello,Sparks 

I‘mleo, want to make friends with you 

Trait进阶

为实例混入trait

有时我们可以在创建类的对象时,指定该对象混入某个trait,这样就只有这个对象混入该trait的方法,而类的其他对象则没有


trait Logged { 

def log(msg: String) {} 

} 

trait MyLogger extends Logged { 

override def log(msg: String) {println("log: " + msg)} 

} 

class Person (val name: String) extends Logged { 

def sayHello { println("Hi, I‘m "+ name); log("sayHello is invokend!")} 

} 

defined trait Logged 

defined trait MyLogger 

defined class Person 

scala> val p1 = new Person("leo") 

p1: Person = Person@36f80ceb 

scala> p1.sayHello 

Hi, I‘m leo 

// 混入trait,覆盖log方法! 

scala> val p2 = new Person("jack") with MyLogger 

p2: Person with MyLogger = $anon$1@30a6984c 

scala> p2.sayHello 

Hi, I‘m jack 

log: sayHello is invokend! 

trait调用链

Scala中支持让类继承多个trait后,依次调用多个trait中的同一个方法(Java中做不到),只要让多个trait的同一个方法中,在最后都执行super.method即可

类中调用多个trait中都有的这个方法时,首先会从最右边的trait方法开始执行,然后依次往左,最终形成一个调用链

这种特性非常强大,其实就相当于设计模式中责任链模式的一种具体实现。


trait Handler{ 

def handle(data: String) {} 

} 

trait DataValidHandler extends Handler { 

override def handle(data: String){ 

println("check data:" + data) 

// 最后都执行super.method 

super.handle(data) 

} 

} 

trait SignatureValidHandler extends Handler { 

override def handle(data: String){ 

println("check signature: " + data) 

// 最后都执行super.method 

super.handle(data) 

} 

} 

class Person(val name: String) extends SignatureValidHandler with DataValidHandler { 

def sayHello = {println("hello, " + name); handle(name)} 

} 

defined trait Handler 

defined trait DataValidHandler 

defined trait SignatureValidHandler 

defined class Person 

scala> val p = new Person("Sparks") 

p: Person = Person@4b37d1a4 

// 从右往左执行方法 

scala> p.sayHello 

hello, Sparks 

check data:Sparks 

check signature: Sparks 

混合使用trait的具体方法和抽象方法

可以让具体方法依赖于抽象方法,而抽象方法则放到继承trati的类中去实现

这种trait其实就是设计模式中模板设计模式的体现


trait Valid{ 

// 将getName交给继承类实现,这里直接在具体方法中使用抽象方法 

def getName: String 

def valid: Boolean = { 

getName == "Sparks" 

} 

} 

class Person(val name: String) extends Valid { 

println(valid) 

def getName = name 

} 

defined trait Valid 

defined class Person 

// 测试 

scala> val p = new Person("Sparks") 

true 

p: Person = Person@351fadfa 

trait的构造机制

在Scala中,trait也是有构造代码的,也就是trait中除了method中的所有代码 
而继承了trait的类的构造顺序如下:

  1. 父类的构造函数
  2. trait的构造代码,多个trait从左到右依次执行
  3. 构造trait时先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次
  4. 所有trait构造完毕后,自身构造函数执行

class Person{ println("Person‘s constructor!")} 

trait Logger { println("Logger‘s constuctor!")} 

trait MyLogger extends Logger { println("MyLogger‘s constructor!")} 

trait TimeLogger extends Logger { println("TimeLogger constructor")} 

class Student extends Person with MyLogger with TimeLogger { 

println("Student‘s constructor") 

} 

defined class Person 

defined trait Logger 

defined trait MyLogger 

defined trait TimeLogger 

defined class Student 

// 测试构造顺序 

scala> val s = new Student 

Person‘s constructor! 

Logger‘s constuctor! 

MyLogger‘s constructor! 

TimeLogger constructor 

Student‘s constructor 

s: Student = Student@467421cc 

trait field初始化

在Scala中,trait是没有接收参数的构造函数的,这是trait与class的唯一区别,但是如果需求就是要trait能够对field进行初始化,那该怎么办呢?

这时候就需要使用Scala中非常特殊的一种高级特性——提前定义

出错示例:


trait SayHello { 

val msg: String 

println(msg.toString) 

} 

class Person extends SayHello{ 

val msg: String = "init" 

} 

defined trait SayHello 

defined class Person 

// 因为要首先初始化trait,但是println中使用了抽象field,所以报错 

scala> val p = new Person 

java.lang.NullPointerException 

  
使用提前定义特性初始化trait field


trait SayHello { 

val msg: String 

println(msg.toString) 

} 

class Person 

defined trait SayHello 

defined class Person 

// 提前定义 

scala> val p = new { 

|   val msg: String = "init" 

| }with Person with SayHello 

init 

p: Person with SayHello = $anon$1@445c693 

// 提前定义另一种写法 

scala> class Person extends { 

|   val msg: String = "init" 

| } with SayHello{} 

defined class Person 

scala> val p = new Person 

init 

p: Person = Person@121c1a08 

  
使用lazy + override初始化trait field


scala> trait SayHello { 

|   lazy val msg: String = null 

|   println(msg.toString) 

| } 

defined trait SayHello 

// 覆盖lazy值 

scala> class Person extends SayHello { 

|   override lazy val msg: String = "init" 

| } 

defined class Person 

scala> val p = new Person 

init 

p: Person = Person@753c7411 

trait继承class

在Scala中,trait也可以继承自class,此时这个class就会成为所有继承该trait的类的父类。


class MyUtil { 

def printMessage(msg: String) = println(msg) 

} 

trait Logger extends MyUtil{ 

def log(msg: String) = printMessage("log: " + msg) 

} 

class Person(val name:String) extends Logger{ 

def sayHello{ 

log("Hi, I‘m" + name) 

printMessage("hi,I‘m " + name) 

} 

} 

defined class MyUtil 

defined trait Logger 

defined class Person 

scala> val p = new Person("sparks") 

p: Person = Person@5bc44d78 

// 既可以调用Logger中的方法也可以调用MyUtil中的方法 

scala> p.sayHello 

log: Hi, I‘msparks 

hi,I‘m sparks

时间: 2024-10-13 15:50:45

Scala入门系列(八):面向对象之trait的相关文章

C语言快速入门系列(八)

C语言快速入门系列(八) C语言位运算与文件 本章引言: 在不知不觉中我们的C快速入门系列已经慢慢地接近尾声了,而在这一节中,我们会对 C语言中的位运算和文件进行解析,相信这两章对于一些人来说是陌生的,因为很多 老师都会跳过这两个大知识点,其实这两个也是灰常重要的!比如一个问题,叫你算 变量a乘以2,怎么写效率高?直接a *2,很多人都这样写,但是如果你会位运算的话,你会a<<1; 位运算的效率可是比a*2高的哦!另一个问题,不用变量左中间值,直接交换两个变量的值? 你怎么做?也是用到位运算!

python入门系列:面向对象

类和对象的创建 类 经典类 没有继承 object的类 新式类 继承了 object的类 class Money: # 2.x中默认是经典类,3.x中是新式类pass class Money(object): # 兼容的一种写法pass Money既是类的name属性名,又是一个引用该类的变量 print(Money.name) # Moneyxxx = Moneyprint(xxx.name) # Money对象 one = Money()print(one) # <main.Money ob

Scala入门系列(九):函数式编程

引言 Scala是一门既面向对象,又面向过程的语言,Scala的函数式编程,就是Scala面向过程最好的佐证.也真是因此让Scala具备了Java所不具备的更强大的功能和特性. 而之所以Scala一直没有替代Java,一是因为Java诞生早,基于Java开发了大量知名的工程,并且最重要的是Java现在不只是一门编程语言,而是一个庞大的技术生态圈,所以未来十年内Scala也不会完全替代Java,但是Scala会在自己特有的领域大发光彩.   将函数赋值给变量 Scala中函数是一等公民,可以独立定

Scala入门系列(六):面向对象之object

object object相当于class的单个实例,类似于Java中的static,通常在里面放一些静态的field和method.   第一次调用object中的方法时,会执行object的constructor,也就是object内部不在method或者代码块中的所有代码,但是object不能定义接受参数的constructor   注意:object的构造函数只会在第一次被调用时被执行一次,这点类似Java类中static的初始化. object Person { private var

快学Scala 第十八课 (trait多继承)

trait多继承: trait的继承并不像类拥有相同的含义!在下面这个例子中,如果还是运用类的继承的思想,那么运行结果将是什么也没有. trait Logged { def log(msg: String){ } } trait ConsoleLogger extends Logged { override def log(msg: String){ super.log(msg) } } 但是事实并非如此: trait Logged { def log(msg: String){ println

Scala入门系列(三):数组

Array 与Java的Array类似,也是长度不可变的数组,此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组. 注意:访问数组中元素使用()而不是Java中的 [] scala> val a = new Array[String](10) a: Array[String] = Array(null, null, null, null, null, null, null, null, null, n ull) scala> val

Scala入门系列(十三):类型参数

引言 Scala中类型参数是什么呢?其实就类似于Java中的泛型.定义一种类型参数,比如在集合.类.函数中定义类型参数,然后就可以保证使用到该类型参数的地方就只能是这种类型,从而实现程序更好的健壮性.   泛型类 泛型类,顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或method,就可以使用这些泛型类型. 使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性. 在使用类的

Java入门教程八(面向对象)

对象概念 一切皆是对象.把现实世界中的对象抽象地体现在编程世界中,一个对象代表了某个具体的操作.一个个对象最终组成了完整的程序设计,这些对象可以是独立存在的,也可以是从别的对象继承过来的.对象之间通过相互作用传递信息,实现程序开发.对象有以下特点:对象具有属性和行为.对象具有变化的状态.对象具有唯一性.对象都是某个类别的实例. 三大特性 封装 封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用. Java 语言的基本封装单位是类.由于类的用途是封装

Golang 入门系列-八怎样实现定时任务,极简版.

感谢平台分享-http://bjbsair.com/2020-04-10/tech-info/53303.html 前面讲介绍了Go 语言的基础入门及Golang的语法结构.同时也介绍Golang的接口及协程等内容.感兴趣的朋友可以先看看之前的文章.接下来说一说Golang 如何实现定时任务. golang 实现定时服务很简单,只需要简单几步代码便可以完成,不需要配置繁琐的服务器,直接在代码中实现. 1.使用的包 github.com/robfig/cron 2.示例 1.创建最简单的最简单cr