大连网站制作建设,太湖县住房和城乡建设局网站,本地网站搭建,珍岛外贸网站建设在Scala中#xff0c;trait是一种特殊概念。trait可以作为接口#xff0c;同时也可以定义抽象方法。类使用extends继承trait#xff0c;在Scala中#xff0c;无论继承类还是继承trait都用extends关键字。在Scala中#xff0c; 类继承trait后必须实现其中的抽象方法#x… 在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 namescaladef 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 180val EQ 180override def toString: String sleg${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:Stringprintln(父类,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.txtprintln(子类,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:Stringprintln(父类,filename)//lazy表示不立刻求值而是等到这个变量被使用的时候去求值//lazy val fileout new PrintWriter(filename) 去掉lazyval fileout new PrintWriter(filename)//用来把 msg 写入到对应文件中def log(msg: String): Unit {fileout.println(msg)fileout.flush()}
}class Test211 extends FileLogger{val filename 2024-10-28.txtprintln(子类,filename)
}object Test21_1 {def main(args: Array[String]): Unit {
// val t1 new Test211()val t1 new {val filename2024-10-29.txt} with FileLoggert1.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继承自AD继承自B和C如果A有一个方法被B和C重载而D不对其重载那么D应该实现谁的方法B还是C解决菱形问题的方法是采用最右优先深度遍历进行搜索。
4.多重继承的惰性求值 惰性求值相当于懒加载问题即当使用时再去求它的值。当使用子类调用父类的方法出现惰性求值的问题时只有调用父类中真正的方法时才会对子类中的方法求值。