首页 > 其他分享 >Scala 的trait

Scala 的trait

时间:2024-10-28 16:48:20浏览次数:9  
标签:特质 val Scala trait extends println

  在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的特质定义如下:

trait identified{
}

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

2.Scala的特质作用

  Scala的特质可以封装成员和方法。Java中的接口不提供具体的实现,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。

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

·对象继承特质。

相关代码如下:

//定义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。相关代码如下:

//多继承

//美貌
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有父类,会按照继承关系先调用父类。如果有多个父类,则按照从左到右的顺序调用,最后才会调用本类构造器。当有超类调用构造器时按照从左到右、从父类到子类的顺序调用即可。 

//继承多个特质时,加载的顺序

//多个特质加载顺序
//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()
  }
}

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

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关键字

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:提前定义 

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的语法如下:

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.多重继承的惰性求值

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

标签:特质,val,Scala,trait,extends,println
From: https://blog.csdn.net/2301_81748087/article/details/143188374

相关文章

  • Scala的内部类和trait
          ......
  • Scala
         ......
  • Scala的抽象类
    定义:抽象类是一个包含一个或多个抽象方法的类,抽象方法是不带任何实现的方法。抽象类是为继承而生的类目的:是为子类提供公共的接口和部分实现,并确保子类实现抽象方法和成员。//定义抽象类//抽象类是一个包含一个或多个抽象方法的类//抽象方法:没有方法体,只有def和方法名abs......
  • 论文翻译 | Scalable Prompt Generation for Semi-supervised Learning with Language
    摘要         基于提示的学习方法在半监督学习(SSL)设置中已被文献证明在多个自然语言理解(NLU)数据集和任务上有效。然而,手动设计多个提示和表述词需要领域知识和人力投入,这使得在不同数据集上扩展变得困难且昂贵。在本文中,我们提出了两种方法来自动设计多个提示,并在......
  • Scala的trait的用法
    ①只有抽象方法的trait②只有抽象成员和方法的trait③具体成员的变量和方法④对象继承特质例子://定义一个特质//特质可以有抽象属性,具体属性,抽象方法,具体方法traitBeauty{//抽象方法valhair:String//具体属性varage=18valheight=180//val不......
  • Scala的高阶函数
    高阶函数作用:把其他函数当做参数或返回值defmain(args:Array[String]):Unit={//定义数组,Array(1,2,3,4)vararr=Array(10,22,13,54)//定义一个数组有4个元素//println(arr(2))//获取第一个元素//遍历for(i<-arr)......
  • Scala的继承(不劳而获)
    Scala中继承的定义为在原有类的基础上定义一个新类,原有类称为父类,新类称为子类 定义:在原有的基础上定义一个新类,原有类称为父类,新类称为子类。class子类名extends父类名 {类体}【问】当子类从父类继承的方法不能满足需要时,子类需要有自己的行为,怎么办?此时使用ove......
  • Scala的多态
     从编译的角度解释Scala的多态含义:在执行期间而非编译期间确定所引用对象的类型,根据实际类型调用其方法。一个编译型语言有两种类型,分别为编译类型和运行类型。程序中定义的引用变量所指向的具体类型和通过该变量发出的方法调用在编译时不确定,而是在程序运行期间才确定的。cl......
  • Scala的sortedWith
    sortedWith:基于函数的排序,通过一个comparator函数,实现自定义排序的逻辑。sortWith方法:它使用传入的比较函数对集合进行排序。在排序过程中,根据比较函数的返回值来决定元素的顺序。如果比较函数返回true,则表示第一个参数应该排在第二个参数之前;如果返回false,则表示第一个参......
  • Scala的抽象类
    1.定义:抽象类是一个不能被实例化的类。无论在Scala中还是Java中都不可以直接创建对象,抽象类通过子类创建对象。//抽象类:制定标准,不要求去具体实现//abstract:抽象abstractclassAICar(){varbrand=""//自动驾驶:不要去具体实现功能defautoRun}//抽象类......