Scala 类型系统一

2017/11/17 Scala

Scala 类型系统一

类型边界

在定义一个参数化的类型或者方法,可能需要指定类型参数的边界,容器可能会假定,类型参数都支持某一种方法。

类型边界上限

类型边界上限是指,某一类型必须是另一种类型的子类型。Predef为Array(Java数组)定义了隐式包装类型,如:

implicit def refArrayOps[T <: AnyRef](xs:Array[T]):ArrayOps[T]=new ArrayOps.ofRef[T](xs)
implicit def longArrayOps(xs:Array[Long]):ArrayOps[Long]=new ArrayOps.ofLong(xs)
...

类型参数<:AnyRef表示『任何A都是AnyRef的子类型』,因为一个类型是其本身的子类型和父类型,所以A可以是AnyRef本身,即<:表示左边的类型必须派生出右边的类型,或者两者是同一种类型。(<:是scala的保留字)。这样的话,上文代码第一行表示对任何AnyRef的子类适用,第二行表示对Long类型适用。这样两者不会有冲突。 类型边界和变异标记是互不想干的。两者相互独立。

类型边界下限

类型边界下限表示某个类型必须是另一个类型的父类。如:

sealed abstract class Option[A] extends Product with Serializable{
    @inline final def getOrElse[B>:A](default: => B):B={...}
}

如果想同时指定上限和下限,下限必须先指定。如class C[A >: Lower <:Upper]

抽象类型

先讨论抽象类型的用法,如:

trait example{
    type t1
    type t2 >: t3 <: t1 //t2必须是t3的父类,t1的子类
    type t3 <: t1 //t3必须是t1的子类
    type t4 <: Seq[t1]  //t4必须是t1的序列的子类

    val v1:t1       //必须先定义类型才能初始化
    val v2:t2
    val v3:t3
    val v4:t4
}   //按照scala的类型规则,下面是一种可能的实现

trait T1 { val name:String }
trait T2 extends T1 {val name1:String}
case class C(name:String,name1:String) extends T2

object exp extends example{
    type t1=T1
    type t2=T2
    type t3=C
    type t4=Vector[T1]

    val v1=new T1{val name="T1"}
    val v2=new T2{ val name="T1";val name1="T2"}
    val v3=C("1","2")
    val v4=Vecotr(C("3","4"))
}

scala提供了设计过程中直接限定类型的能力,一种极其强大的语言表达能力,比java的语言表达能力强大很多,相当于C++模版用法的升级版本。 几乎可以使所有的参数化类型支持抽象类型,反之亦然。参数化类型可以很好地用于容器中,如集合。而类型参数所表示的元素类型与容器本身之间并没有什么联系。例如,字符串列表、浮点数列表与整数列表的表现相同。

自类型标记

自类型标记 (self-type annotation)可以达到两个目标。首先,它允许为 this 指定额外的类型期望。其次,它可以被用于创建 this 的别名。如:

abbstract class SubjectObserver{//将观察者模式的关系封装在类型中了
    type S <: Subject //S 是Subject的子类
    type O <: Observer

    trait Subject{
        self:S =>   //自标记类型,声明Subject是子类型S的实例;S是实际上是混入了Subject特征的任何类型

        private var observers =List[O]()

        def addObserver(obserber:O) = observers ::= observer

        def notifyObservers() = observers.foreach(_.receiveUpdate(self))//这里不能用this是因为需要得到具体的Subject子类类型,参数是协变的,receiveUpdate是抽象的,所以必须用自类型标记来明确
    }

    trait Observer {
        def receiveUpdate(subject:S)
    }
}

示例是一种观察者模式的简单实现。 再给出一个例子,一个简单的三层应用程序。

//三层逻辑的定义
trait Persistence { def startPersistence():Unit }
trait Midtier { def startMidtier():Unit}
trait UI{def startUI():Unit}

//三层逻辑的实现
trait Database extends Persistence{
    def startPersistence():Unit = println("starting database")
}

trait BizLogic extends Midtier{
    def startMidtier():Unit = println("starting BizLogic")
}

trait WebUI extends UI{
    def startUI:Unit =println("starting WebUI")
}

trait App{ self:Persistence with Midtier with UI => //定义App自己的类型

    def run(){
        startPersistence()
        startMidtier()
        startUI()
    }
}

Object MyApp extends App with Database with BizLogc with WebUI      //混入三层逻辑的具体trait,静态实例,这个是可以运行的

MyApp.run   //运行

自标记功能类同与继承行为,两者是可以相互替换使用的。自类型标记强调用混入实现组合。继承意味着类之间是父类与子类的关系。除非需要继承大规模的“模块”(trait),且自类型标记能更清楚地表明设计思路的情况,否则大部分 Scala 代码倾向于使用继承的方法,而不是自类型标记。 自标记的第二个功能是做别名,如:

class C1 { self => //这个地方可以用任何合法名字,只是标记这个是C1
    def talk(msg:String) = println("C1.talk:"+msg)
    class C2{   //这里也可以做别名
        class C3{   //这里也可以做别名
            def talk(msg:String) = self.talk("C3.talk:"+msg)
        }
        val c3=new C3
    }
    val c2=new C2
}
val c1=new C1
c1.talk("Hello")
c1.c2.c3.talk("World")

感觉scala真的比java简洁很多,键入速度是恒定的,当然越简洁的语言越喜欢了,毕竟成吨的写java也是一件很无趣的事情。

小结

scala的学习过程同时也是对过去接触过的编程语言的特性整理的过程,多多总结才能早日形成知识网络,使得学习效率成倍提高,从而可以从更多的角度解决问题。

Show Disqus Comments

Search

    Table of Contents