Scala 的trait

在Scala中,trait是一种特殊概念。trait可以作为接口,同时也可以定义抽象方法。类使用extends继承trait,在Scala中,无论继承类还是继承trait都用extends关键字。在Scala中, 类继承trait后必须实现其中的抽象方法,实现时不需要使用override关键字,同时Scala支持多重继承trait,使用with关键字即可。

1.1Scala的特质

本节通过对Scala的特质概念、作用以及语法的相关阐述来说明Scala中的特质应用。通过与Java 中的接口相互对比可以加深对特质的了解。

1.Scala的特质定义

由于Scala没有Java中接口的概念,所以Scala的特质就相当于Java中的接口,但是Scala的特质比接口的功能强大。Scala的特质定义如下:

Scala 复制代码
trait identified{
}

其中trait为定义特质的关键字,identified表示一个合法的标识,可以自定义。登中可以定义一些成员、属性或方法等。

2.Scala的特质作用

Scala的特质可以封装成员和方法。Java中的接口不提供具体的实现,Scala的特质同样也是封装一些成员属性和方法。例如定义一个Scala特质,相关代码如下:

Scala 复制代码
trait Person{
   val name="scala"
   def a():Unit
}

Scala的特质相当于抽象类和接口的合体。在JDK1.7中,Java的接口只能定义一些没实现的方法体和一些赋值的变量,而Scala的trait相当于接口和抽象类的合体。

3.Scala的特质语法

在Java中实现多接口可以通过implements关键字定义,例如定义一个类P实现多个接口(A和B 为接口),即class P implements A, B。而在Scala中定义特质A和B时,实现特质的语法为:extends A with B,A和B的位置可以互换。例如 class Pextends A with B表示在一个类中实现多个特质。当类P中混入类S时,S的位置必须在extends之后,特质A或B不可以与类S互换位置,但是A和B的位置可以互换,例如class P extends S with A with B。

1.2 Scala的trait的用法

下面主要介绍Scala的trait的用法:

·只有抽象方法的trait。

·只有抽象成员和方法的trait。

·具体成员的变量和方法。

·对象继承特质。

相关代码如下:

Scala 复制代码
//定义trait
//1.不是类,不能实例化
//2.它的构造器不能带参数!即:不能添加()
trait Shentihao{
//  abstract class Shentihao{
  //具体属性
  var KM_1 = 5
  //抽象属性
  var sports:String

  //具体方法
  def say(): Unit = {}
  //抽象方法
  def run
}

class Student1 extends Shentihao{
  var sports = "跳绳"

  def run():Unit={
    println("1000m 在4.5分钟内跑完")
  }
}

object Test20 {
  def main(args: Array[String]): Unit = {
    val s1 = new Student1()
  }
}

对象继承特质是Scala中比较特殊的一点,可以为单独的某一对象继承trait。相关代码如下:

Scala 复制代码
//多继承

//美貌
trait Beauty {
  val leg:Double

}

//智慧
trait Wisdom {
  val EQ:Int
}


//一个类,实现了两特质。用 with 隔开
//多个特质可以交换顺序
class Girl extends Beauty with Wisdom {
  val leg = 180
  val EQ = 180

  override def toString: String = s"leg=${leg},eq=${EQ}"
}
object Test20_1 {
  def main(args: Array[String]): Unit = {
    val girl = new Girl()
    println(girl)
  }
}

1.3 trait的mix

下面介绍一个类继承了一个特质后,特质中的成员的处理方式。成员分为抽象成员和具体成员。成员包括方法和属性,没有方法体实现的方法称为抽象方法,没有赋值的属性称为抽象属性;方法体中有具体实现的方法称为具体方法,赋予了一个具体值的属性称为具体属性。

抽象成员包括抽象方法和抽象属性。如果一个类继承了特质,那么抽象方法一定要实现方法体。抽象属性可以通过val或var关键字修饰,如果子类要访问由val或var修饰的抽象成员,要求变量修饰必须要对应,不加 override关键字。

具体成员包括具体方法和具体属性。如果是一个具体的方法,那么需要使用关键字override重写方法。使用override重写方法时,方法的名称、参数列表以及返回值必须相同。 具体属性同样使用val或var关键字修饰,val的重写需加上 override关键字,即override val 属性名称。var的重写不需override关键字和var修饰,只需属性名称即可。

1.4 trait的加载顺序

在Java中构造器的调用顺序为先调用父类构造器再调用子类构造器。Scala 中的调用顺序与Java中的十分相似。trait的加载顺序为先执行超类(父类)中的构造器,再调用子类的构造器。如果混入的trait有父类,会按照继承关系先调用父类。如果有多个父类,则按照从左到右的顺序调用,最后才会调用本类构造器。当有超类调用构造器时按照从左到右、从父类到子类的顺序调用即可。

Scala 复制代码
//继承多个特质时,加载的顺序

//多个特质加载顺序
//1,先父后子
//2.从左到右
trait  A051{
  println("1")
}

trait B051{
  println("2")
}

class AB extends A051 with B051{
  println("AB")
}

object Test20_2 {
  def main(args: Array[String]): Unit = {
    new AB()
  }
}

下面定义多个父类和子类演示构造器的执行顺序。相关代码如下:

Scala 复制代码
trait A051{ println("A051") }

trait AA051 extends A051 {println("AA051")}

trait AB051 extends A051 {println("AA051")}

trait B051 {println("B051")}

trait BA051 extends B051{println("BA051")}

trait BB051 extends B051 {println("BB051")}

class AB extends AA051 with BA051 with AB051 with BB051{
  println("AB")
}

object Test21{
  def main(args: Array[String]): Unit = {
    new AB()
  }
}

//A051 AA051 B051 BA051 AA051 BB051 AB

1.5 解决空指针异常问题

通过前面的介绍,了解了Scala构造器的调用顺序并解释了父类构造器打印为0的问题。 下面介绍调用构造器引起的第二个问题,即空指针异常问题。

1.trait的抽象成员父类使用问题

在新定义一个对象时,该对象会先调用父类的构造器。而在父类构造器中由于变量没有赋值,实际相当于null,再通过变量(实际为null)调用方法时就会报空指针异常的问题。

2.解决方法

解空指针异常的方式有两种,分别是提前定义和懒加载。提前定义就是在调用对象之前给变量赋值,即提前定义法。懒加载就是在调用的过程中通过lazy关键字解决问题。

下面定义一个Logger演示构造器执行顺序,造成空指针问题。相关代码如下:

方法1:使用lazy关键字

Scala 复制代码
import java.io.PrintWriter
//经典错误:空指针异常

trait FileLogger{
  //抽象属性,没有=
  val filename:String
  println("父类",filename)

  //lazy表示,不立刻求值,而是等到这个变量被使用的时候,去求值
  lazy val fileout = new PrintWriter(filename)

  //用来把 msg 写入到对应文件中
  def log(msg: String): Unit ={
    fileout.println(msg)
    fileout.flush()
  }
}

class Test211 extends FileLogger{

  val filename = "2024-10-28.txt"
  println("子类",filename)
}

object Test21_1 {
  def main(args: Array[String]): Unit = {
    val t1 = new Test211()
    t1.log("test!")
  }
}

方法2:提前定义

Scala 复制代码
import java.io.PrintWriter
//经典错误:空指针异常

trait FileLogger{
  //抽象属性,没有=
  val filename:String
  println("父类",filename)

  //lazy表示,不立刻求值,而是等到这个变量被使用的时候,去求值
  //lazy val fileout = new PrintWriter(filename)  去掉lazy
  val fileout = new PrintWriter(filename)

  //用来把 msg 写入到对应文件中
  def log(msg: String): Unit ={
    fileout.println(msg)
    fileout.flush()
  }
}

class Test211 extends FileLogger{

  val filename = "2024-10-28.txt"
  println("子类",filename)
}

object Test21_1 {
  def main(args: Array[String]): Unit = {
//    val t1 = new Test211()
    val t1 = new {val filename="2024-10-29.txt"} with FileLogger
    t1.log("test!")
  }
}

1.6 trait与类的相关特性

通过之前的介绍我们对trait和类都有了一个初步的了解,下面通过trait和类的相同点以及不同点来说明trait的相关特性。

1.trait与类的相同点

类和trait都以定义成员变量和方法,成员变量和方法可以是抽象的也可以是具体的。如果类是抽象类,则可以定义抽象成员和抽象方法,如果类是普通类,则可以定义一些普通的变量和方法。trait相当于抽象类和接口,所以trait 既可以定义抽象成员也可以定义普通成员。在继承方面它们都可以使用extends关键字。

2.trait与类的不同点

定义类或抽象类时可以有构造参数,而trait构造器不能带参数。关于多继承问题,Java中的类不支持多继承,接口支持多实现:而在Scala中trait可以支持多继承,也可以在多继承的同时混入多个特质。

1.7trait多继承

下面从trait多继承的实现方法和混入多trait的语法格式对多继承做一个简单介绍,通过多继承产生的问题进一步加深对trait名继承的了解。下面简单介绍多继承广生的问题以解决方案。

1.trait多继承的定义

可以通过混入多个trait实现多继承。例如,定义特质t1、t2,在类A中混入多个特质,即class A extends tl with t2表示在类A中混入 t1和t2两个特质。

2.混入多trait的语法

混入多trait的语法如下:

Scala 复制代码
extends A with B with C

其中多个特质可以互换位置,即可以 extends C with A with B。多个特质互换位置不影响实现多继承。

3.多重继承

多重继承容易产生菱形问题。菱形问题可以描述为B和C继承自A,D继承自B和C,如果A有一个方法被B和C重载,而D不对其重载,那么D应该实现谁的方法,B还是C?解决菱形问题的方法是采用最右优先深度遍历进行搜索。

4.多重继承的惰性求值

惰性求值相当于懒加载问题,即当使用时再去求它的值。当使用子类调用父类的方法出现惰性求值的问题时,只有调用父类中真正的方法时才会对子类中的方法求值。

相关推荐
AiFlutter4 分钟前
Java对接GraphQL
java·开发语言·graphql
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ28 分钟前
springboot单元测试配置
spring boot·后端·单元测试
掘金狂热勇士29 分钟前
GCC 编译过程的三个阶段
后端
技术拾荒者42 分钟前
.net core mvc 控制器中页面跳转
后端·c#·asp.net·mvc·.netcore
写bug写bug1 小时前
Git 中的撤销工作区、暂存区和已提交的更改
前端·git·后端
Aliano2171 小时前
Java的jackson库
java·开发语言
车载testing1 小时前
使用Python和Vosk库实现语音识别
开发语言·python·语音识别
bobz9651 小时前
ovn ovs ctl 和 libovsdb mock 的 db 进行交互
后端
折枝寄北1 小时前
C语言进阶:二.数据的存储(2)
c语言·开发语言·学习
ac-er88882 小时前
PHP的四大安全策略
开发语言·安全·php