单例类型
给定任意引用v,可以得到类型v.type,有两个可能的值,v或者null。对于自身进行修改的方法返回this,通过这种方式将方法调用串连起来
1 2 3 4 5 6 7 8
| class Document{ def setTitle(title:String) = {.....;this} def setAuthor(author:String) = {......;this} ... }
article.setTitle("xxx").setAuthor("aaa")
|
对于子类,上述调用方法会存在问题
1 2 3 4 5 6 7
| class Book extends Document{ def addChapter(chapter:String) = {...;this} ... }
val book = new Book() book.setTtile("xxx").addChapter("faf")
|
setTitle方法返回this,而this的类型是Document,没有addChapter方法。此时需要声明setTitle的返回值类型
1 2 3
| def setTitle(title:String):this.type = {.....;this}
|
用上述类型定义的手段解决方法串接的问题。
如果要定义一个接受object实例作为参数的方法,也可以直接使用单例类型。
1 2 3 4 5 6 7 8 9
| class Document{ private var useNextArgAs: Any = null def set(obj:Title.type):this.type=(useNextArgAs = obj;this) def to(arg:String) = if(useNextArgAs == Title) title=arg; else ... ... }
|
类型投影
使用嵌套类的时候,两个对象的内部嵌套类是不同的类型,无法将它们添加到同一处。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Network{ class Member(val name:String){ val contacts = new ArrayBuffer[Member] } private val members = new ArrayBuffer[Member] def join(name:string) = { val m = new Member(name) members+=m } }
val chatter = new Network val myFace = new Network
val fred = chatter.join("Fred") val barney = myFace.join("Barney")
fred.contacts += barney
|
如果不要这个约束,则可以将Member类直接放在Network类之外,推荐放在Network的伴生对象中。
另一种方式是用类型投影,允许内部类兼容不同对象的内部类
1 2 3 4
| class Network{ class Member(val name:String){ val contacts = new ArrayBuffer[Network#Member] }
|
路径
如下的表达式称为路径
1
| com.horstmann.impatient.Network.Member
|
在最后的类型之前,路径的所有组成部分必须是稳定的,必须指定到单个,有穷的范围。组成部分可以是
- 包
- 对象
- val
- this,super,super[S],c.this
路径组成部分不能是类,即嵌套的内部类不是单个类型,而是给每个实例留出了单独的类型。
同样,类型也不能是var
1 2 3
| var chatter = new Network
val fred = new chatter.Member
|
类型别名
对复杂的类型,可以用type关键字来创建一个简单的别名
1 2 3 4 5 6 7
| class Book{ import scala.collection.mutable._ type Index = HashMap[String,[Int,Int]] }
|
结构类型
结构类型指一组关于抽象方法,字段和类型的规格说明。这些抽象方法,字段和类型是满足改规格的类型必须具备的。
1 2 3 4 5
| def appendLines(target: {def append(str:String):Any}, lines:Iterable[String]){ for(l<-lines){target.append(l);target.append("\n")} }
|
上述函数的参数定义了结构类型,target参数是任何一个定义了append方法的函数,lines是一个支持遍历的类型。
复合类型
复合类型写法如下
想要成为复合类型,某个值必须满足每一个类型(T1,T2,T3)才行。可以用复合类型来操作必须提供多个特质的值。
中置类型
中置类型是一个带有两个类型参数的类型,类型名称写在两个参数中间。
通过多种中置操作符可以定义中置类型
存在类型
存在类型是为了和Java的类型通配符兼容,存在类型的定义方式是在类型表达式之后跟上forSome{},比如
1 2 3
| Array[T] forSome{type T<:JComponent}
Array[_<:JComponent]
|
forSome表示法允许我们使用更复杂的关系,不仅限于类型通配符能表达的那些
1
| Map[T,U] forSome{type T; type U<:T}
|
forSome 中还可以使用val声明。
1
| n.Member forSome{ val n: Network }
|
Scala类型系统


自身类型
特质可以要求混入它的类扩展自另一个类型,可以用自身类型的声明来定义特质
1 2 3 4 5 6 7 8 9
| trait Logged { def log(msg:String) }
trait LoggedException extends Logged{ this: Exception => def log(){log(getMessage())} }
|
依赖注入
在通过组建构建大型系统,而每个组件都有不同实现的时候,我们需要将组件的不同选择组装起来。比如,会有一个模拟数据库和真实数据库,控制台日志和文件日志,某个特定的实现可能想要用真正的数据库和文件日志来运行。要用控制台日志和模拟数据库来测试。
通常,组件之间存在依赖关系,比如,数据访问组件可能会依赖日志功能,Java中Spring框架等工具用来表达这种依赖。每个组件都描述了它所依赖的其他组件的接口。而对实际组件实现的引用,是在程序被组装起来的时候注入的。
Scala中,可以通过特质和自身类型达到一个简单的依赖注入效果。对日志而言
有如下特质和两个实现:ConsoleLogger,FileLogger
认证特质依赖日志功能来记录认真失败
1 2 3 4
| trait Auth{ this: Logger => def login(id:String, password:String): Boolean }
|
应用逻辑依赖认证和日志
1 2 3 4
| trait App{ this: Logger with Auth => ... }
|
可以这样组装应用
1 2 3
| object MyApp extends App with FileLogger("test.log") with MockAuth("user.txt")
|
更好的方式是通过实例变量来表示组件,而不是将组件粘合成一个大类型。蛋糕模式给出了更好的设计。在这个模式中,对每个服务都提供一个组件特质,该特质包含
- 任何所依赖的组件和自身类型表述
- 描述服务接口的特质
- 一个抽象的val,该val将被初始化成服务的一个实例
- 可以有选择的包含服务接口的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| trait LoggerComponent { trait Logger {...} val logger:Logger class FileLogger(file:String) extends Logger{...} ... }
trait AuthComponent{ this: LoggerComponent => trait Auth{...} val auth:Auth class MockAuth(file:String) extends Auth{...} ... }
object AppComponents extends LoggerCompponent with AuthComponent{ val logger = new FileLogger("test.log") val auth = new MockAuth("user.txt") }
|
抽象类型
类或特质可以定义在一个子类中被具体化的抽象类型
1 2 3 4
| trait Reader{ type Contents def read(fileName:String):Contents }
|
在这里,类型Contents是抽象的,具体的子类需要指定这个类型
1 2 3 4 5 6 7 8 9 10
| class StringReader extends Reader{ type Contents = String def read(fileName: String) = source.fromFile(fileName,"UTF-8").mkString }
class ImageReader extends Reader{ type Contents = BufferedImage def read(fileName: String) = ImageIO.read(new File(fileNmae)) }
|
如果类型是在类被实例化时给出的,则使用类型参数。
如果类型是在子类中给出的,则使用抽象类型
家族多态
Java中的事件处理,包含不同种类的事件比如ActionEvent,ChangeEvent等,而每种类型都有单独的监听器接口ActionListener,ChangeListener等。这就是家族多态。
对应到Scala中,先定义一个统一的listener
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| trait Listener[E]{ def occurred(e:E):Unit }
trait Source[E,L<:Listener[E]]{ private val listeners = new ArrayBuffer[L] def add(l:L){listeners+=l} def remove(l:L){listeners-=l} def fire(e:E){ e.source = this for(l<-listeners) l.occurred(e) } }
|
接下来可以定义一个带有按钮事件和按钮监听器的按钮时,就可以将定义包含在一个扩展该特质的模块当中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| object ButtonModule extends ListenerSupport{ type S = Button type E = ButtonEvent type L = ButtonListener class ButtonEvent extends Event trait ButtonListener extends Listener class Button extends Source{ def click(){fire(new ButtonEvent)} } }
object Main{ import ButtonModule._ def main(args:Array[String]){ val b = new Button b.add(new ButtonListener{ override def occurred(e:ButtonEvent){print(e)} }) b.click() } }
|
高等类型
泛型类List依赖于类型T并产出一个特定的类型。比如Int,得到的类型是List[Int]。List被称为类型构造器,Scala中,可以定义出依赖于其他类型的类型的类型。(依赖于泛型的类型)。
1 2 3 4 5 6 7 8 9
| trait Iterable[E]{ def iterator():Iterator[E] def map[F](f:(E)=>F):Iterable[F] }
class BUffer[E] extends Iterable[E]{ def iterator(): Iterator[E] = ... def map[F](f:(E)=>F):Buffer[F] = ... }
|
对于buffer,我们预期map方法返回一个buffer,不仅仅是Iterable。这意味着不能在Iterable特质中实现这个map方法。可以使用类型构造器来参数话Iterable
1 2 3 4 5
| trait Iterable[E,c[_]]{ def iterator():Iterator[E] def build[F](): C[F] def map[F](f:(E)=>F):Iterable[F] }
|
这样以来,Iterable就依赖一个类型构造器来生成结果,以C[_]表示,使得Iterable成为了一个高等类型。
map方法返回的类型可以是也可以不是调用该map方法的原类型。
用range执行map方法,结果可以是另一种类型Buffer[F]
1
| class Range extends Iterable[Int,Buffer]
|
要实现Iterable中的map方法,Iterable需要能产出包含了任何类型F的值的容器。定义一个Container类:
1 2 3 4 5 6 7 8
| trait Container[E]{ def +=(e:E):Unit }
trait Iterable[E,C[X]<:COntainer[X]]{ def build[F]():C[F] ... }
|
类型构造器C现在被限制为一个Container,因此我们知道可以往build方法返回的对象添加项目,不再对参数C使用通配符,因为需要表明C[X]是一个针对同样的X的容器。
之后可以在Iterable特质中实现map方法
1 2 3 4 5 6
| def map[F](f:(E)=>F):C[F]={ val res = build[F]() val iter = iterator() while(iter.hasNext) res+=f(iter.next()) res }
|
由此,可迭代的类就不再需要提供它们自己的map实现类了。
1 2 3 4 5 6 7 8 9
| class Range(val low:Int, val high:Int)extends Iterable[Int,Buffer]{ def iterator() = new Iterator[Int]{ private var i = low def hasNext = i <=high def next() = {i+=1;i-1} } def build[F]() = new BUffer[F] }
|
Range 只是一个Iterable,可以遍历内容,不能添加,Buffer是Iterable和Container
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Buffer[E:ClassTag] extends Iterable[E,Buffer] with Container[E]{ private var capacity = 10 private var length = 0 private var elems = new Array[E](capacity) def iterator() = new Iterator[E]{ private var i=0 def hasNext = i<length def next() = {i+=1;elems(i-1)} } def build[F:ClassTag]() = new Buffer[F] def +=(e:E){ if(length==capacity){ capacity = 2*capacity val nelems = new Array[E](capacity) for(i<-0 until length) nelems(i) =elems(i) elems = nelems } elems(length) = e length+=1 } }
|
上述事例展示了高等类型的典型用例,Iterator依赖Container但Container不是普通的类型,是制作类型的机制。