详解scalal中的特质(Traits)
我们基本的思路,先将官方的文档讲清楚,再结合特质和混入的用法,做结合的说明。
1-1 特质
特质 (Traits) 用于在类 (Class)之间共享程序接口 (Interface)和字段 (Fields)。 它们类似于Java 8的接口。 类和对象 (Objects)可以扩展特质,但是特质不能被实例化,因此特质没有参数。
1-2 定义一个特质
trait HairColor
trait Iterator[ A] {
def hasNext: Boolean
def next( ) : A
}
扩展 trait Iterator [A] 需要一个类型 A 和实现方法hasNext和next。
1-2 使用特质
使用 extends 关键字来扩展特征。然后使用 override 关键字来实现trait里面的任何抽象成员
trait Iterator[ A] {
def hasNext: Boolean
def next( ) : A
}
class IntIterator( to: Int ) extends Iterator[ Int ] {
private var current = 0
override def hasNext: Boolean = current < to
override def next( ) : Int = {
if ( hasNext) {
val t = current
current += 1
t
} else 0
}
}
val iterator = new IntIterator( 10 )
iterator. next( )
iterator. next( )
这个类 IntIterator 将参数 to 作为上限。它扩展了 Iterator [Int],这意味着方法 next 必须返回一个Int。
1-3 子类型
import scala. collection. mutable. ArrayBuffer
trait Pet {
val name: String
}
class Cat( val name: String ) extends Pet
class Dog( val name: String ) extends Pet
val dog = new Dog( "Harry" )
val cat = new Cat( "Sally" )
val animals = ArrayBuffer. empty[ Pet]
animals. append( dog)
animals. append( cat)
animals. foreach( pet => println( pet. name) )
在这里 trait Pet 有一个抽象字段 name ,name 由Cat和Dog的构造函数中实现。最后一行,我们能调用pet.name的前提是它必须在特质Pet的子类型中得到了实现。
1-4 特质总结
基本上,上面的官方文档说明的还是比较的详细,讲特质的基本的用法,讲清楚了,但是,再实际的开发中,情况要复杂些。下面 ,我先将混入讲清楚,再结合起来说一下。他们的用法。
1-2 通过混入(MIXIN)来组合类
abstract class A {
val message: String
}
class B extends A {
val message = "I'm an instance of class B"
}
trait C extends A {
def loudMessage = message. toUpperCase( )
}
class D extends B with C
val d = new D
println( d. message)
println( d. loudMessage)
类D有一个父类B和一个混入C。一个类只能有一个父类但是可以有多个混入(分别使用关键字extends和with)。混入和某个父类可能有相同的父类。 下面例子,其中使用了抽象类:
abstract class AbsIterator {
type T
def hasNext: Boolean
def next( ) : T
}
该类中有一个抽象的类型T和标准的迭代器方法。 接下来,我们将实现一个具体的类(所有的抽象成员T、hasNext和next都会被实现)
class StringIterator( s: String ) extends AbsIterator {
type T = Char
private var i = 0
def hasNext = i < s. length
def next( ) = {
val ch = s charAt i
i += 1
ch
}
}
StringIterator带有一个String类型参数的构造器,可用于对字符串进行迭代。(例如查看一个字符串是否包含某个字符) 现在我们创建一个特质,也继承于AbsIterator
trait RichIterator extends AbsIterator {
def foreach( f: T => Unit ) : Unit = while ( hasNext) f( next( ) )
}
该特质实现了foreach方法——只要还有元素可以迭代(while (hasNext)),就会一直对下个元素(next()) 调用传入的函数f: T => Unit。因为RichIterator是个特质,可以不必实现AbsIterator中的抽象成员。 下面我们要把StringIterator和RichIterator 中的功能组合成一个类。
object StringIteratorTest extends App {
class RichStringIter extends StringIterator( "Scala" ) with RichIterator
val richStringIter = new RichStringIter
richStringIter foreach println
}
新的类RichStringIter有一个父类StringIterator和一个混入RichIterator。如果是单一继承,我们将不会达到这样的灵活性。 基本上我们讲官方文档上面的说清楚了。下面我们结合特质和混入说明下。两者之间的混合使用。
1-3 特质和混入的结合使用
1-3-1 特质的简单说明
java中的接口无法直接构建对象,必须使用实现类 Java中的接口是可以声明方法的.早期版本中的声明的方法,都是抽象的.但是新版本是可以默认实现的 java中的接口是可以声明属性的.属性值无法修改. scala中的特质也无法构建对象 scala中的特质是可以执行代码的 scala中的特质也是无法创建对象的 scala特质中声明的属性和方法都是可以混入的类中调用的
1-3-2 多特质类的使用
object Scala15_Interface2 {
def main( args: Array[ String ] ) : Unit = {
new User15( )
}
}
trait TestTrait15{
println( "trait code ...." )
}
trait Person15 extends TestTrait15{
println( "parent code ...." )
}
class User15 extends Person15 with TestTrait15{
println( "child code ...." )
}
trait code .. ..
parent code .. ..
child code .. ..
特质和父类没有关系,只和当前混入的类有关系,所以,在调用时,父类先执行,然后当前的混入的特质在执行,然后当前的类在执行. 如果父类混入的相同的的特质,那么特质的代码只会执行一遍
1-3-3 多特质类中方法使用
object Scala15_Interface3 {
def main( args: Array[ String ] ) : Unit = {
val mysql: Mysql = new Mysql( )
mysql. insert( )
}
}
trait Operate {
def insert( ) : Unit = {
println( "插入数据" )
}
}
trait DB extends Operate{
override def insert( ) : Unit = {
println( "向数据库插入数据" )
super . insert( )
}
}
trait File extends Operate{
override def insert( ) : Unit = {
println( "向File插入数据" )
super [ Operate] . insert( )
}
}
class Mysql extends File with DB with Serializable{
}
特质可以继承其他的特质 特质重写父特质的方法 多特质混入时,代码执行顺序为从左到右,如果有父特质,回优先执行 特质中super关键字不是指代父特质,指代上一级特质 如果希望super 指向父特质,需要增加特殊操作: super[特质] java的接口可以再scala中当成特质来用
1-3-4 特质的动态混入
def main( args: Array[ String ] ) : Unit = {
val mysql1 = new MysqlVo( ) with Operation
mysql1. insert( )
}
}
trait Operation {
def insert( ) : Unit = {
println( "插入数据" )
}
}
class MysqlVo {
}
根据闭合原则,我们编码的时候,尽量不会为了业务的拓展,修改原来的代码,那么我们再scala 中可以使用动态的特质实现。 动态特质,指的是,我们再不动原来的code,我们声明一个新的业务特质,再new的时候 with 特质就是可以了。这样我们new出来的类,就可以使用新特质的里面的东东了。 在上面的演示例子中,MysqlVo在没有修改任何代码的情况下,在new的时候“ val mysql1 = new MysqlVo() with Operation”,增加了Operation这个特质,然后调用Operation特质中的“insert”方法。
1-3-5 指定特质的使用范围
object Scala15_Interface4 {
def main( args: Array[ String ] ) : Unit = {
val mysql1 = new Mysql18( )
}
}
trait exceptionHandler {
this : Exception =>
def insert( ) : Unit = {
println( "插入数据" )
this . getMessage
}
}
class Mysql18 extends Exception with exceptionHandler{
}
上面code中指定了特质exceptionHandler使用的范围,就是才异常的情况下,我们才会使用,insert这个方法。 这样有个好处,就是指定特质的使用范围。