快学Scala学习笔记及习题解答(10-11特质与操作符)

本文Scala使用的版本是2.11.8

第10章 特质

10.1 基本使用

特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质。

import java.util.Date

trait AbsLogged {
    // 特质中未被实现的方法默认就是抽象的.
    def log(msg: String)
}

trait Logged extends AbsLogged {
    // 重写抽象方法, 此处为空实现
    override def log(msg: String) { }
}

trait ConsoleLogger extends Logged {
    override def log(msg: String) { println("ConsoleLogger: " + msg) }
}

trait FileLog extends Logged {
    override def log(msg: String) { println("FileLog: " + msg) }
}

trait TimeLog extends Logged {
    override def log(msg: String): Unit = {
        super.log(new Date() + " " + msg)
    }
}

trait ShortLogger extends Logged {
    val maxLength = 15
    override def log(msg: String): Unit = {
        super.log(
            if (msg.length < maxLength) msg else msg.substring(0, maxLength - 3) + "..."
        )
    }
}

// 如果需要的特质不止一个,可以用with关键字添加额外的特质
class SavingsAccount extends Logged with Cloneable with Serializable {
    def withdraw(amount: Double): Unit = {
        if (amount > 10.0) log("Insufficient funds")
        else log("else")
    }
}

// 测试
object Test {
    def main(args: Array[String]) {
        // 如下可以混入不同的特质
        val acct1 = new SavingsAccount with ConsoleLogger
        acct1.log("acct1")
        val acct2 = new SavingsAccount with FileLog
        acct2.log("acct2")

        // 可以叠加多个特质, 一般从最后一个开始被处理
        val acct3 = new SavingsAccount with ConsoleLogger with TimeLog with ShortLogger
        acct3.withdraw(11)
        val acct4 = new SavingsAccount with ConsoleLogger with ShortLogger with TimeLog
        acct4.withdraw(11)
    }
}

// 运行结果
ConsoleLogger: acct1
FileLog: acct2
ConsoleLogger: Tue Nov 22 07:10:15 CST 2016 Insufficient...
ConsoleLogger: Tue Nov 22 0...

10.2 当做富接口使用的特质

特质可以包含大量工具方法,而这些方法可以依赖一些抽象方法来实现。

trait Logger {
    def log(msg: String)
    def info(msg: String) { log("INFO: " + msg) }
    def warn(msg: String) { log("WARN: " + msg) }
    def error(msg: String) { log("ERROR: " + msg) }
}

10.3 特质中的字段

给出初始值的是具体字段;否则为抽象字段,子类必须提供该字段。
这些字段不是被子类继承,而是简单地被加到子类中。

10.4 特质的构造顺序

和类一样,特质也可以有构造器,由字段的初始化和其他特质中的语句构成。

构造器以如下顺序执行:

  1. 首先调用超类的构造器。
  2. 特质构造器在超类构造器之后、类构造器之前执行。
  3. 特质由左到右被构造。
  4. 每个特质当中,父特质先被构造。
  5. 如果多个特质共用一个父特质,而这个父特质已经被构造,则不会再次构造。
  6. 所有特质构造完毕,子类被构造。

10.5 初始化特质中的字段

特质不能有构造器参数。每个特质都有一个无参数的构造器。而且构造顺序的问题,在子类中初始化特质的字段,可能会有陷阱。

import java.io.PrintStream

trait Logger {
    def log(msg: String) {}
    def info(msg: String) { log("INFO: " + msg) }
    def warn(msg: String) { log("WARN: " + msg) }
    def error(msg: String) { log("ERROR: " + msg) }
}

trait FileLogger extends Logger {
    val fileName: String
    val out = new PrintStream(fileName)

    override def log(msg: String) { out.println(msg); out.flush() }
}

class SavingsAccount2 extends Logger {
    def withdraw(amount: Double): Unit = {
        if (amount > 10.0) log("Insufficient funds")
        else log("else")
    }
}

// 类的提前定义
class TestAccount extends {
    val fileName = "test.log"
} with SavingsAccount2 with FileLogger

object Test2 {

    def main(args: Array[String]) {
        // 特质的提前定义
        val acct = new {
            val fileName = "myapp.log"
        } with SavingsAccount2 with FileLogger
        acct.withdraw(11)

        // 类的提前定义
        val test = new TestAccount
        test.withdraw(1)
    }
}

另一种是在FileLogger构造器中使用懒值:

lazy val out = new PrintStream(fileName)

10.6 扩展类的特质

// 特质可以扩展类
trait LoggedException extends Exception with Logger {
    def log() { log(getMessage) }
}

class UnhappyException extends LoggedException {
    override def getMessage = "arggh!"
}

// 如果类已经扩展了另一个类, 只要这个类是特质的超类的一个子类就可以
class UnhappyException2 extends IOException with LoggedException {
    override def getMessage = "UnhappyException2!"
}

object Test2 {

    def main(args: Array[String]) {

        val ex = new UnhappyException
        ex.log()

        val ex2 = new UnhappyException2
        ex2.log()
    }
}

10.7 自身类型

当特质以如下代码开始定义时

this: 类型 =>

它便只能被混入指定类型的子类。

trait LoggedException2 extends Logged {
    this: Exception =>
        def log() { log(getMessage) }
}

下面这种特质可以被混入任何拥有getMessage方法的类。

trait LoggedException3 extends Logged {
    this: { def getMessage(): String } =>

    def log() { log(getMessage()) }
}

10.8 习题解答


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

val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RectangleLike
egg.translate(10, -10)
egg.grow(10, 20)

package com.zw.demo.tenth

import java.awt.geom.Ellipse2D

trait RectangleLike {
  this:Ellipse2D.Double =>
  def translate(dx : Int, dy : Int): Unit = {
    this.x += dx
    this.y += dy
  }
  def grow(h : Int, v : Int): Unit = {
    this.width = v
    this.height = h
  }
}

// 测试类
package com.zw.demo.tenth

object One {

  def main(args: Array[String]): Unit = {
    val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30) with RectangleLike

    println("x = " + egg.getX + " y = " + egg.getY)
    egg.translate(10, -10)
    println("x = " + egg.getX + " y = " + egg.getY)

    println("w = " + egg.getWidth + " h = " + egg.getHeight)
    egg.grow(10, 21)
    println("w = " + egg.getWidth + " h = " + egg.getHeight)
  }
}

// 结果
x = 5.0 y = 10.0
x = 15.0 y = 0.0
w = 20.0 h = 30.0
w = 21.0 h = 10.0


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

package com.zw.demo.tenth

import java.awt.Point

class OrderedPoint(
                  x:Int,
                  y:Int
                  ) extends Point(x:Int, y:Int) with Ordered[Point]{

  override def compare(that: Point): Int = {
    if (this.x <= that.x && this.y < that.y) -1
    else if (this.x == that.x && this.y == that.y) 0
    else 1
  }
}

// 测试类
package com.zw.demo.tenth

object Two {

  def main(args: Array[String]) {
    val arr : Array[OrderedPoint] = new Array[OrderedPoint](3)
    arr(0) = new OrderedPoint(4,5)
    arr(1) = new OrderedPoint(2,2)
    arr(2) = new OrderedPoint(4,6)
    val sortedArr = arr.sortWith(_ > _)
    sortedArr.foreach((point:OrderedPoint) => println("x = " + point.getX + " y = " + point.getY))
  }

}

// 结果
x = 4.0 y = 6.0
x = 4.0 y = 5.0
x = 2.0 y = 2.0


3. 查看BitSet类,将它的所有超类和特质绘制成一张图。忽略类型参数([…]中的所有内容)。然后给出该特质的线性化规格说明。

类图

Sorted、SetLike、SortedSetLike、Set、SortedSet、BitSetLike、BitSet


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

package com.zw.demo.tenth

trait CryptoLogger {
  def crypto(str : String, key : Int = 3) : String = {
    for ( i <- str) yield
      if (key >= 0) (97 + ((i - 97 + key)%26)).toChar
      else (97 + ((i - 97 + 26 + key)%26)).toChar
  }
}

// 测试类
package com.zw.demo.tenth

/**
  * Created by zhangws on 16/10/28.
  */
object Three {

  def main(args: Array[String]) {
    val log = new CryptoLogger {}

    val plain = "abcdef"
    println("明文为:" + plain)
    println("加密后为:" + log.crypto(plain))
    println("加密后为:" + log.crypto(plain, -3))
  }
}

// 结果
明文为:abcdef
加密后为:defghi
加密后为:xyzabc


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

package com.zw.demo.tenth

import java.beans.PropertyChangeSupport

trait PropertyChange {
  val propertyChangeSupport : PropertyChangeSupport
}

// 测试
package com.zw.demo.tenth

import java.awt.Point
import java.beans.{PropertyChangeSupport, PropertyChangeEvent, PropertyChangeListener}

object Five {

  def main(args: Array[String]) {
    val p = new Point() with PropertyChange {
      val propertyChangeSupport = new PropertyChangeSupport(this)
      propertyChangeSupport.addPropertyChangeListener(new PropertyChangeListener {
        override def propertyChange(evt: PropertyChangeEvent): Unit = {
          println(evt.getPropertyName
            + ": oldValue = " + evt.getOldValue
            + " newValue = " + evt.getNewValue)
        }
      })
    }
    val newX : Int = 20
    p.propertyChangeSupport.firePropertyChange("x", p.getX, newX)
    p.move(newX, 30)
  }
}

// 结果
x: oldValue = 0.0 newValue = 20


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

Java只能单继承,JContainer不能同时继承自Container和JComponent。Scala可以通过特质解决这个问题.


7. 做一个你自己的关于特质的继承层级,要求体现出叠加在一起的特质、具体的和抽象的方法,以及具体的和抽象的字段。

package com.zw.demo.tenth

trait Fly{
  def fly(){
    println("flying")
  }

  def flywithnowing()
}

trait Walk{
  def walk(){
    println("walk")
  }
}

class Bird{
  var name:String = _
}

class BlueBird extends Bird with Fly with Walk{
  def flywithnowing() {
    println("BlueBird flywithnowing")
  }
}

object Seven {

  def main(args: Array[String]) {
    val b = new BlueBird()
    b.walk()
    b.flywithnowing()
    b.fly()
  }
}


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

import java.io.{FileInputStream, InputStream}

trait Buffering {
    this: InputStream =>

    val BUF_SIZE: Int = 5
    val buf: Array[Byte] = new Array[Byte](BUF_SIZE)
    var bufsize: Int = 0 // 缓存数据大小
    var pos: Int = 0 // 当前位置

    override def read(): Int = {
        if (pos >= bufsize) { // 读取数据
            bufsize = this.read(buf, 0, BUF_SIZE)
            if (bufsize <= 0) return bufsize
            pos = 0
        }
        pos += 1 // 移位
        buf(pos - 1) // 返回数据
    }
}

object Eight {

    def main(args: Array[String]) {
        val f = new FileInputStream("myapp.log") with Buffering
        for (i <- 1 to 30) println(f.read())
    }
}


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

import java.io.{FileInputStream, InputStream}

trait Logger {
    def log(msg: String)
}

trait PrintLogger extends Logger {
    def log(msg: String) = println(msg)
}

trait Buffering {
    this: InputStream with Logger =>

    val BUF_SIZE: Int = 5
    val buf: Array[Byte] = new Array[Byte](BUF_SIZE)
    var bufsize: Int = 0 // 缓存数据大小
    var pos: Int = 0 // 当前位置

    override def read(): Int = {
        if (pos >= bufsize) { // 读取数据
            bufsize = this.read(buf, 0, BUF_SIZE)
            if (bufsize <= 0) return bufsize
            log("buffered %d bytes: %s".format(bufsize, buf.mkString(", ")))
            pos = 0
        }
        pos += 1 // 移位
        buf(pos - 1) // 返回数据
    }
}

object Eight {

    def main(args: Array[String]) {
        val f = new FileInputStream("myapp.log") with Buffering with PrintLogger
        for (i <- 1 to 20) println(f.read())
    }
}

// 结果
buffered 5 bytes: 73, 110, 115, 117, 102
73
110
115
117
102
buffered 5 bytes: 102, 105, 99, 105, 101
102
105
99
105
101
buffered 5 bytes: 110, 116, 32, 102, 117
110
116
32
102
117
buffered 4 bytes: 110, 100, 115, 10, 117
110
100
115
10
-1


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

import java.io.{FileInputStream, InputStream}

/**
  * Created by zhangws on 16/10/28.
  */
trait IterableInputStream extends InputStream with Iterable[Byte] {

    class InputStreamIterator(outer: IterableInputStream) extends Iterator[Byte] {
        def hasNext: Boolean = outer.available() > 0

        def next: Byte = outer.read().toByte
    }

    override def iterator: Iterator[Byte] = new InputStreamIterator(this)
}

object Ten extends App {
    val fis = new FileInputStream("myapp.log") with IterableInputStream
    val it = fis.iterator
    while (it.hasNext)
        println(it.next())
}
fis.close()

10.9 参考

《快学scala》习题解答-第十章-特质

第11章 操作符

11.1 基本使用

yield在Scala中是保留字,如果访问Java中的同名方法,可以用反引号

Thread.`yield`()

中置操作符

1 to 10 相当于 1.to(10)
1 -> 10 相当于 1.->(10)

使用操作符的名称定义方法就可以实现定义操作符

class Fraction(n: Int, d: Int) {
    val num: Int = n
    val den: Int = d
    def *(other: Fraction) = new Fraction(num * other.num, den * other.den)

    override def toString = {
        "num = " + num + "; den = " + den
    }
}

object StudyDemo extends App {
    val f1 = new Fraction(2, 3)
    val f2 = new Fraction(4, 5)
    val f3 = f1 * f2
    println(f3)
}

一元操作符

# 后置操作符
1 toString 等同于 1.toString()

# 前置操作符(例如+、-、!、~)
-a 转换为unary_操作符的方法调用 a.unary_-

赋值操作符

a 操作符= b 等同于 a = a 操作符 b
例如:a += b 等同于 a = a + b


1. <=、>=和!=不是赋值操作符;
2. 以=开头的操作符不是赋值操作符(==、===、=/=等);
3. 如果a有一个名为操作符=的方法,那么该方法会被直接调用。

结合性

除了以下操作符,其他都是左结合的

1. 以冒号(:)结尾的操作符;
2. 赋值操作符。

例如

2 :: Nil 等同于Nil.::(2)

11.2 优先级

除复制操作符外,优先级由操作符的首字符决定。

最高优先级:除以下字符外的操作符字符
* / %
+ -
:
< >
! =
&
^
|
非操作符
最低优先级:赋值操作符


后置操作符优先级低于中置操作符:

a 中置操作符 b 后置操作符
等价于
(a 中置操作符 b) 后置操作符

11.3 apply和update方法

f(arg1, arg2, ...)

如果f不是函数或方法,等同于

f.apply(arg1, arg2, ...)

表达式

f(arg1, arg2, ...) = value

等同于

f.update(arg1, arg2, ..., value)

apply常被用在伴生对象中,用来构造对象而不用显示地使用new。

class Fraction(n: Int, d: Int) {
    ...
}

object Fraction {
    def apply(n: Int, d: Int) = new Fraction(n, d)
}

val result = Fraction(3, 4) * Fraction(2, 5

11.4 提取器

所谓提取器就是一个带有unapply方法的对象。可以当做伴生对象中apply方法的反向操作。

unapply接受一个对象,然后从中提取值。

val author = "Cay Horstmann"
val Name(first, last) = author // 调用Name.unapply(author)

object Name {
    def unapply(input: String) = {
        val pos = input.indexOf(" ")
        if (pos == -1) None
        else Some((input.substring(0, pos), input(substring(pos + 1))
    }
}

每一个样例类都自动具备apply和unapply方法。

case class Currency(value: Double, unit: String)

带单个参数的提取器

object Number {
    def unapply(input: String): Option[Int] = {
        try {
            Some(Integer.parseInt(input.trim))
        } catch {
            case ex: NumberFormatException => None
        }
    }
}

val Number(n) = "1223"

无参数的提取器

object IsCompound {
    def unapply(input: String) = input.contains(" ")
}

author match {
    // 作者Peter van der也能匹配
    case Name(first, last @ IsCompound()) => ...
    case Name(first, last) => ...
}

unapplySeq方法,提取任意长度的值得序列。

object Name {
    def unapplySeq(input: String): Option[Seq[String]] = {
        if (input.trim == "") None
        else Some(input.trim.split("\\s+"))
    }
}

// 这样就可以匹配任意数量的变量了

author math {
    case Name(first, last) => ...
    case Name(first, middle, last) => ...
    case Name(first, "van", "der", last) => ...
}

11.5 习题解答


1. 根据优先级规则,3+4->5和3->4+5是如何被求值的?

scala> 3 + 4 -> 5
res0: (Int, Int) = (7,5)

scala> 3 -> 4 + 5
<console>:12: error: type mismatch;
 found   : Int(5)
 required: String
       3 -> 4 + 5
                ^


2. BitInt类有一个pow方法,但没有用操作符字符。Scala类库的设计者为什么没有选用**(像Fortran那样)或者^(像Pascal那样)作为乘方操作符呢?

因为优先级问题,在scala中*优先于^,但数学中乘方优先于乘法。所以没有提供^作为乘方的操作符。


3. 实现Fraction类,支持 + - * / 操作。支持约分,例如将15/-6变成-5/2。除以最大公约数,像这样:

class Fraction(n: Int, d: Int) {
  private val num: Int = if (d == 0) 1 else n * sign(d) /gcd(n, d);
  private val den: Int = if (d == 0) 0 else d * sign(d) /gcd(n, d);
  override def toString = num + "/" + den
  def sign(a: Int) = if (a > 0) 1 else if (a < 0) -1 else 0
  def gcd(a: Int, b: Int): Int = if (b == 0) abs(a) else gcd(b, a % b)
  ...
}

import scala.math.abs

class Fraction(n: Int, d: Int) {
    private val num: Int = if (d == 0) 1 else n * sign(d) / gcd(n, d)
    private val den: Int = if (d == 0) 0 else d * sign(d) / gcd(n, d)

    override def toString = num + "/" + den

    // 正负号
    def sign(a: Int): Int = if (a > 0) 1 else if (a < 0) -1 else 0

    // 最大公约数
    def gcd(a: Int, b: Int): Int = if (b == 0) abs(a) else gcd(b, a % b)

    def +(other: Fraction): Fraction = {
        Fraction((this.num * other.den) + (other.num * this.den), this.den * other.den)
    }

    def -(other: Fraction): Fraction = {
        Fraction((this.num * other.den) - (other.num * this.den), this.den * other.den)
    }

    def *(other: Fraction): Fraction = {
        Fraction(this.num * other.num, this.den * other.den)
    }

    def /(other: Fraction): Fraction = {
        Fraction(this.num * other.den, this.den * other.num)
    }
}

object Fraction {
    def apply(n: Int, d: Int) = new Fraction(n, d)
}

object Three extends App {
    val f = new Fraction(15, -6)
    val p = new Fraction(20, 60)
    println(f)
    println(p)
    println(f + p)
    println(f - p)
    println(f * p)
    println(f / p)
}

// 结果
-5/2
1/3
-13/6
-17/6
-5/6
-15/2


4. 实现一个Money类,加入美元和美分字段。提供+、-操作符以及比较操作符==和<。举例来说Money(1, 75) + Money(0, 50) == Money(2, 25)应为true。你应该同时提供*和/操作符吗?为什么?

金额的乘除没有实际意义。

class Money(val dollar: Int, val cent: Int) {
    def +(other: Money): Money = {
        Money(this.dollar + other.dollar, this.cent + other.cent)
    }

    def -(other: Money): Money = {
        Money(0, this.toCent - other.toCent)
    }

    def <(other: Money): Boolean = this.dollar < other.dollar || (this.dollar == other.dollar && this.cent < other.cent)

    def ==(other: Money): Boolean = this.dollar == other.dollar && this.cent == other.cent

    private def toCent = this.dollar * 100 + this.cent

    override def toString = { "dollar = " + this.dollar + " cent = " + this.cent}
}

object Money {
    def apply(dollar: Int, cent: Int) = {
        val d = dollar + cent / 100
        new Money(d, cent % 100)
    }
}

object Four extends App {
    val m1 = Money(1, 200)
    val m2 = Money(2, 2)
    println(m1 + m2)
    println(m1 - m2)
    println(m2 - m1)
    println(m1 == m2)
    println(m1 < m2)
    println(Money(1, 75) + Money(0, 50))
    println(Money(1, 75) + Money(0, 50) == Money(2, 25))
}

// 结果
dollar = 5 cent = 2
dollar = 0 cent = 98
dollar = 0 cent = -98
false
false
dollar = 2 cent = 25
true


5. 提供操作符用于构造HTML表格。例如:

Table() | "Java" | "Scala" || "Gosling" | "Odersky" || "JVM" | "JVM, .NET"

应产出

<table><tr><td>Java</td><td>Scala</td></tr><tr><td>Gosling...

class Table {
    var s: String = ""

    def |(str: String): Table = {
        Table(this.s + "<td>" + str + "</td>")
    }

    def ||(str: String): Table = {
        Table(this.s + "</tr><tr><td>" + str + "</td>")
    }

    override def toString: String = {
        "<table><tr>" + this.s + "</tr></table>"
    }
}

object Table {
    def apply(): Table = {
        new Table
    }

    def apply(str: String): Table = {
        val t = new Table
        t.s = str
        t
    }
}

object Five extends App {
    println(Table() | "Java" | "Scala" || "Gosling" | "Odersky" || "JVM" | "JVM,.NET")
}

// 结果
<table><tr><td>Java</td><td>Scala</td></tr><tr><td>Gosling</td><td>Odersky</td></tr><tr><td>JVM</td><td>JVM,.NET</td></tr></table>


6. 提供一个ASCIIArt类,其对象包含类似这样的图形:


 /\_/\
( ' ' )
(  -  )
 | | |
(__|__)
提供将两个ASCIIArt图形横向或纵向结合的操作符。选用适当优先级的操作符命名。纵向结合的实例:
 /\\_/\     -----
( ' ' )  / Hello \
(  -  ) 
import scala.collection.mutable.ArrayBuffer

class ASCIIArt(str: String) {
    val arr: ArrayBuffer[ArrayBuffer[String]] = new ArrayBuffer[ArrayBuffer[String]]()

    if (str != null && !str.trim.equals("")) {
        str.split("[\r\n]+").foreach(
            line => {
                val s = new ArrayBuffer[String]()
                s += line
                arr += s
            }
        )
    }

    def this() {
        this("")
    }

    /**
      * 横向结合
      * @param other
      * @return
      */
    def +(other: ASCIIArt): ASCIIArt = {
        val art = new ASCIIArt()
        // 获取最大行数
        val length = if (this.arr.length >= other.arr.length) this.arr.length else other.arr.length
        for (i <- 0 until length) {
            val s = new ArrayBuffer[String]()
            // 获取this中的行数据, 行数不足,返回空行
            val thisArr: ArrayBuffer[String] = if (i < this.arr.length) this.arr(i) else new ArrayBuffer[String]()
            // 获取other中的行数据, 行数不足,返回空行
            val otherArr: ArrayBuffer[String] = if (i < other.arr.length) other.arr(i) else new ArrayBuffer[String]()
            // 连接this
            thisArr.foreach(s += _)
            // 连接other
            otherArr.foreach(s += _)
            art.arr += s
        }
        art
    }

    /**
      * 纵向结合
      * @param other
      * @return
      */
    def *(other: ASCIIArt): ASCIIArt = {
        val art = new ASCIIArt()
        this.arr.foreach(art.arr += _)
        other.arr.foreach(art.arr += _)
        art
    }

    override def toString = {
        var ss: String = ""
        arr.foreach(ss += _.mkString(" ") + "\n")
        ss
    }
}

object Six extends App {
    // stripMargin: "|"符号后面保持原样
    val a = new ASCIIArt(
        """ /\_/\
          |( ' ' )
          |(  -  )
          | | | |
          |(__|__)
          | """.stripMargin)
    val b = new ASCIIArt(
        """   -----
          | / Hello \
          |<  Scala |
          | \ Coder /
          |   -----
          | """.stripMargin)
    println(a + b * b)
    println((a + b) * b)
    println(a * b)
}


7. 实现一个BigSequence类,将64个bit的序列打包在一个Long值中。提供apply和update操作来获取和设置某个具体的bit。

class BigSequence(private var value: Long = 0) {

    def update(bit: Int, state: Int) = {
        if (state == 1) value |= (state & 1L) << bit % 64
        else value &= ~(1L << bit % 64)
    }

    def apply(bit: Int): Int = if (((value >> bit % 64) & 1L) > 0) 1 else 0

    override def toString = "%64s".format(value.toBinaryString).replace(" ", "0")
}

object Seven {
    def main(args: Array[String]) {
        val x = new BigSequence()
        x(5) = 1
        x(63) = 1
        x(64) = 1

        println(x(5))
        x(64) = 0
        println(x)
    }
}


8. 提供一个Matrix类——你可以选择需要的是一个2x2的矩阵,任意大小的正方形矩阵,或是m x n的矩阵。支持+和*操作。*操作应同样适用于单值,例如mat*2。单个元素可以通过mat(row, col)得到。

class Matrix(val m: Int, val n: Int) {
    private val value = Array.ofDim[Double](m, n)

    def update(x: Int, y: Int, v: Double) = value(x)(y) = v

    def apply(x: Int, y: Int) = value(x)(y)

    def +(other: Matrix): Matrix = {
        require(n == other.n)
        require(m == other.m)

        val res = new Matrix(m, n)
        for (i <- 0 until m; j <- 0 until n) {
            res(i, j) = this (i, j) + other(i, j)
        }
        res
    }

    def -(other: Matrix): Matrix = {
        this + other * (-1)
    }

    def *(factor: Double): Matrix = {
        val res = new Matrix(m, n)
        for (i <- 0 until m; j <- 0 until n) {
            res(i, j) = this (i, j) * factor
        }
        res
    }

    private def prod(other: Matrix, i: Int, j: Int) = {
        (for (k <- 0 until n) yield value(i)(k) * other.value(j)(k)).sum
    }

    def *(other: Matrix) = {
        require(n == other.m)

        val res = new Matrix(m, other.n)
        for (i <- 0 until m; j <- 0 until other.n) {
            res(i, j) = prod(other, i, j)
        }
        res
    }

    override def toString = value.map(_.mkString(" ")).mkString("\n")
}

object Eight extends App {
    val x = new Matrix(2, 2)
    x(0, 0) = 1
    x(0, 1) = 2
    x(1, 0) = 3
    x(1, 1) = 4
    println(x)
    println()
    println(x * 2)
    println()
    println(x * 2 - x)
    println()
    println((x * 2) * (x * 3))
}

// 结果
1.0 2.0
3.0 4.0

2.0 4.0
6.0 8.0

1.0 2.0
3.0 4.0

30.0 66.0
66.0 150.0


9. 为RichFile类定义unapply操作,提取文件路径、名称和扩展名。举例来说,文件/home/cay/readme.txt的路径为/home/cay,名称为readme,扩展名为txt。

class RichFile1(val path: String) {}

object RichFile1 {
    def apply(path: String): RichFile1 = {
        new RichFile1(path)
    }

    def unapply(richFile1: RichFile1) = {
        if (richFile1.path == null) {
            None
        } else {
            val reg = "([/\\w+]+)/(\\w+)\\.(\\w+)".r
            val reg(r1, r2, r3) = richFile1.path
            Some((r1, r2, r3))
        }
    }
}

object Nine {
    def main(args: Array[String]) {
        val richFile1 = RichFile1("/home/cay/readme.txt")
        val RichFile1(r1, r2, r3) = richFile1
        println(r1)
        println(r2)
        println(r3)

    }
}


10. 为RichFile类定义一个unapplySeq,提取所有路径段。举例来说,对于/home/cay/readme.txt,你应该产出三个路径段的序列:home、cay和readme.txt。

class RichFile2(val path: String) {}

object RichFile2 {
    def apply(path: String): RichFile2 = {
        new RichFile2(path)
    }

    def unapplySeq(richFile2: RichFile2): Option[Seq[String]] = {
        if (richFile2.path == null) {
            None
        } else {
            if (richFile2.path.startsWith("/")) {
                Some(richFile2.path.substring(1).split("/"))
            } else {
                Some(richFile2.path.split("/"))
            }
        }
    }
}

object Ten {
    def main(args: Array[String]) {
        val richFile2 = RichFile2("/home/cay/readme.txt")

        val RichFile2(r@_*) = richFile2
        println(r)
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值