快学scala 第十章 特质 读书笔记及习题答案代码

chapter 10 特质

标签:快学scala


一、笔记

  1. scala和java一样不允许从多个超类继承,scala提供特质而非接口。特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质。 
    不需要将方法声明为abstract,特质中未被实现的方法默认就是抽象的。在重写特质的抽象方法时不需要给出override关键字。特质跟类更为相像。用with添加额外的特质:
 
 
   
   
   
   
  1. class ConsoleLogger extends Logger with Cloneable with Serialiable

由上,Logger ith Cloneable with Serialiable首先是一个整体,然后再由类来扩展。 
2. 特质的方法并不需要一定是抽象的:

 
 
   
   
   
   
  1. trait ConsoleLogger{
  2. def log(msg: String) { println(msg)}
  3. }
  4. class SavingAccount extends Account with ConsoleLogger {
  5. def withdraw(amount: Double){
  6. if(amount > balance) log("Insufficient funds")
  7. else balance -= amount
  8. }
  9. } //scala与java的区别,SavingAccount从ConsoleLogger特质得到了一个具体的log方法,java的接口是不可能做到的。scala中是ConsoleLogger的功能被混入了SavingAccount类
  1. JVM中,一个类只能扩展一个超类,因此来自特质的字段不能以相同的方式继承。
  2. 对于特质的构造顺序:首先调用超类的构造器。特质构造器在超类构造器之后、类构造器之前执行。特质由左到右被构造。每个特质当中,父特质先被构造。如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造。所有特质构造完毕,子类被构造。
 
 
   
   
   
   
  1. class SavingsAccount extends Account with FileLogger with ShortLogger
  2. //构造顺序是: Account(超类)Looger(第一个特质的父特质)FileLogger(第一个特质)ShortLogger(第二个特质,它的父特质Logger已被构造),SavingsAccount(类)
  1. 特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造器参数是特质和类之间唯一的技术差别。
 
 
   
   
   
   
  1. val acct = new SavingsAccount with FileLogger("myapp.log") //错误:特质不能使用构造器参数

FileLogger构造器先于子类构造器执行,在轮到子类之前,FileLogger的构造器就会抛出一个空指针异常。解决方法是 “提前定义”:

 
 
   
   
   
   
  1. val acct = new{
  2. val filename = "myapp.log"
  3. }with SavingsAccount with FileLogger //提前定义发生在常规的构造序列之前,在FileLogger被构造时,filename已经是初始化过的了
  1. 特质可以扩展另一个特质,特质也可以扩展类,这个类会自动成为所有混入该特质的超类。在特质扩展类时,编译器能够确保的是所有混入该特质的类都认这个类作超类。scala还有另一套机制保证:自身类型
 
 
   
   
   
   
  1. this: 类型 =>
  2. //当特质以这一定义开始时,它便被混入指定类型的子类
 
 
   
   
   
   
  1. trait LoggedException extends Logged{
  2. this: Exception =>
  3. def log() { log(getMessage) }
  4. } //该特质并不扩展Exception类,而是有一个自身类型Exception,这意味着,它只能被混入Exception的子类。
  5. 如果把特质混入一个不符合自身类型要求的类,就会报错
  1. 6.

二、习题答案

10.1 java.awt.Rectangle类有两个很有用的方法translate和grow,但可惜的是像java.awt.geom.Ellipse2D这样的类没有。在Scala中,你可以解决掉这个问题。定义一个RenctangleLike特质,加入具体的translate和grow方法。提供任何你需要用来实现的抽象方法,以便你可以像如下代码这样混入该特质:

 
 
   
   
   
   
  1. val egg = new java.awt.geom.Ellipse2D.Double(5,10,20,30) with RectangleLike
  2. egg.translate(10,-10)
  3. egg.grow(10,20)
 
 
   
   
   
   
  1. import java.awt.geom.Ellipse2D
  2. trait RectangleLike{ //使用自身类型使得trait可以操作x,y
  3. this:Ellipse2D.Double =>
  4. def translate(x: Double, y: Double){
  5. this.x = x
  6. this.y = y
  7. }
  8. def grow(x: Double, y: Double){
  9. this.x += x
  10. this.y += y
  11. }
  12. }
  13. object Test extends App{
  14. val egg = new Ellipse2D.Double(5,10,20,30) with RectangleLike
  15. println("x = " + egg.getX + " y = " + egg.getY)
  16. egg.translate(10, -10)
  17. println("x = " + egg.getX + " y = " + egg.getY)
  18. egg.grow(10, 20)
  19. println("x = " + egg.getX + " y = " + egg.getY)
  20. }

10.2 把scala.math.Ordered[Point]混入java.awt.Point的方式,定义OrderedPoint类。按辞典编辑方式排序,也就是说,如果

 
 
   
   
   
   
  1. x<x'或者x=x'y<y'则(x,y)<(x',y')
 
 
   
   
   
   
  1. import scala.math.Ordered
  2. import java.awt.Point
  3. class OrderedPoint(x: Int, y: Int) extends Point(x,y) with Ordered[Point]{ //Point(x,y)写全
  4. def compare(that: Point): Int = {
  5. if(this.x <= that.x){
  6. if(this.x == that.x) {
  7. if(this.y < that.y) -1
  8. else if(this.y > that.y) 1
  9. else 0
  10. }else -1
  11. }else -1
  12. }
  13. override def toString = "[%d, %d]".format(getX(), getY())
  14. }
  15. val x1 = new OrderedPoint(1,1)
  16. val x2 = new OrderedPoint(1,-1)
  17. val x3 = new OrderedPoint(2,1)
  18. println(x1 < x2)
  19. println(x1 >= x3)

10.4 提供一个CryptoLogger类,将日志消息以凯撒密码加密。缺省情况下密匙为3,不过使用者也可以重写它。提供缺省密匙和-3作为密匙是的使用示例

 
 
   
   
   
   
  1. trait Logger {
  2. def log(msg: String) = {}
  3. }
  4. trait ConsoleLogger extends Logger {
  5. override def log(msg: String) = Console.println(msg)
  6. }
  7. trait CaesarLogger extends Logger {
  8. val shift: Int = 3
  9. override def log(msg: String) = {
  10. super.log((for(x <- msg) yield (x + shift).toChar).mkString)
  11. // more elegant
  12. super.log(msg.map(_ + shift).map(_.toChar).mkString)
  13. // speedup but less elegant
  14. super.log(msg.map((x : Char) => (x + shift).toChar).mkString)
  15. }
  16. }
  17. class Sample extends Logger {
  18. def doSomeWork() = {
  19. log("Some Log Message")
  20. }
  21. }
  22. val x = new Sample with ConsoleLogger
  23. x.doSomeWork
  24. val y = new Sample with ConsoleLogger with CaesarLogger
  25. y.doSomeWork
  26. val z = new { override val shift = -3} with Sample with ConsoleLogger with CaesarLogger
  27. z.doSomeWork

10.5 JavaBean规范里有一种提法叫做属性变更监听器(property change listener),这是bean用来通知其属性变更的标准方式。PropertyChangeSupport类对于任何想要支持属性变更通知其属性变更监听器的bean而言是个便捷的超类。但可惜已有其他超类的类—比如JComponent—必须重新实现相应的方法。将PropertyChangeSupport重新实现为一个特质,然后将它混入到java.awt.Point类中

 
 
   
   
   
   
  1. import java.awt.Point
  2. import java.beans.PropertyChangeSupport
  3. trait PropertyChange extends PropertyChangeSupport
  4. val p = new Point() with PropertyChange

10.6 在Java AWT类库中,我们有一个Container类,一个可以用于各种组件的Component子类。举例来说,Button是一个Component,但Panel是Container。这是一个运转中的组合模式。Swing有JComponent和JContainer,但如果你仔细看的话,你会发现一些奇怪的细节。尽管把其他组件添加到比如JButton中毫无意义,JComponent依然扩展自Container。Swing的设计者们理想情况下应该会更倾向于图10-4中的设计。但在Java中那是不可能的。请解释这是为什么?Scala中如何用特质来设计出这样的效果?

 
 
   
   
   
   
  1. Java只能单继承,JContainer不能同时继承自ContainerJComponentScala可以通过特质解决这个问题.

10.7 市面上有不下数十种关于Scala特质的教程,用的都是些"在叫的狗"啦,"讲哲学的青蛙"啦之类的傻乎乎的例子。阅读和理解这些机巧的继承层级很乏味且对于理解问题没什么帮助,但自己设计一套继承层级就不同了,会很有启发。做一个你自己的关于特质的继承层级,要求体现出叠加在一起的特质,具体的和抽象的方法,以及具体的和抽象的字段

 
 
   
   
   
   
  1. trait Fly{
  2. def fly(){
  3. println("flying")
  4. }
  5. def flywithnowing()
  6. }
  7. trait Walk{
  8. def walk(){
  9. println("walk")
  10. }
  11. }
  12. class Bird{
  13. var name: String= _
  14. }
  15. class BlueBird extends Bird with Fly with Walk{
  16. def flywithnowing(){
  17. println("BlueBird flywithnowing")
  18. }
  19. }
  20. object Test extends App{
  21. val b = new BlueBird()
  22. b.walk()
  23. b.flywithnowing()
  24. b.fly()
  25. }

10.8 在java.io类库中,你可以通过BufferedInputStream修饰器来给输入流增加缓冲机制。用特质来重新实现缓冲。简单起见,重写read方法

 
 
   
   
   
   
  1. import java.io.{InputStream, FileInputStream}
  2. trait Buffering{
  3. this: InputStream =>
  4. val BUF_SIZE: Int = 5
  5. private val buf = new Array[Byte](BUF_SIZE)
  6. private var bufsize: Int = 0
  7. private var pos: Int = 0
  8. override def read(): Int = {
  9. if(pos >= bufsize){
  10. bufsize = this.read(buf, 0, BUF_SIZE)
  11. if(bufsize > 0) -1
  12. pos = 0
  13. }
  14. pos += 1
  15. buf(pos - 1)
  16. }
  17. }
  18. val f = new FileInputStream("test.txt") with Buffering
  19. for( i <- 1 to 10) println(f.read())

10.9 使用本章的日志生成器特质,给前一个练习中的方案增加日志功能,要求体现缓冲的效果

 
 
   
   
   
   
  1. import java.io.{InputStream, FileInputStream}
  2. trait Logger {
  3. def log(msg: String)
  4. }
  5. trait NoneLogger extends Logger {
  6. def log(msg: String) = {}
  7. }
  8. trait PrintLogger extends Logger {
  9. def log(msg: String) = println(msg)
  10. }
  11. trait Buffering {
  12. this: InputStream with Logger =>
  13. val BUF_SIZE: Int = 5
  14. private val buf = new Array[Byte](BUF_SIZE)
  15. private var bufsize: Int = 0
  16. private var pos: Int = 0
  17. override def read(): Int = {
  18. if (pos >= bufsize) {
  19. bufsize = this.read(buf, 0, BUF_SIZE)
  20. log("buffered %d bytes: %s".format(bufsize, buf.mkString(", ")))
  21. if (bufsize > 0) -1
  22. pos = 0
  23. }
  24. pos += 1
  25. buf(pos-1)
  26. }
  27. }
  28. val f = new FileInputStream("test.txt") with Buffering with PrintLogger
  29. for(i <- 1 to 10) println(f.read())

10.10 实现一个IterableInputStream类,扩展java.io.InputStream并混入Iterable[Byte]特质

 
 
   
   
   
   
  1. trait IterableInputStream extends java.io.InputStream with Iterable[Byte]{
  2. class InputStreamIterator(outer: IterableInputStream) extends Iterator[Byte]{
  3. def hasNext: Boolean = outer.available() > 0
  4. def next: Byte = outer.read().toByte
  5. }
  6. override def iterator: Iterator[Byte] = new InputStreamIterator(this)
  7. // override def read(): Int = 0
  8. }
  9. val f = new java.io.FileInputStream("test.txt") with IterableInputStream
  10. for(b <- f) println(b.toChar)

你可能感兴趣的:(scala,scala)