1. 程式人生 > >快學Scala學習筆記及習題解答(5-8類、物件、包和繼承)

快學Scala學習筆記及習題解答(5-8類、物件、包和繼承)

本文scala使用的版本是2.11.7

第五章 類

5.1 基本操作

class Person {

    // Scala會生成一個私有的final欄位和一個getter方法,但沒有setter
    val timeStamp = new java.util.Date

    // 必須初始化欄位
    private var privateAge = 0

    def increment() {
        privateAge += 1
    }

    // 方法預設是公有的
    def current() = privateAge

    // 自定義age的getter方法
def age = privateAge // 自定義age的setter方法 def age_= (newValue: Int) { if (newValue > privateAge) privateAge = newValue } } object Note1 extends App { val fred = new Person fred.age = 20 println(fred.age) fred.age = 12 println(fred.age) }

5.2 物件私有欄位

在Scala(Java和C++也一樣)中,方法可以訪問該類的所有物件的私有欄位,例如:

class Counter {
    private var value = 0
    // 可以訪問另一個物件的私有欄位
    def isLess(other: Counter) = value < other.value

    // 類似某個物件.value2這樣的訪問將不被允許
    // 此時Scala不會生成getter和setter方法
    private[this] var value2 = 0
}

5.3 Bean屬性

import scala.reflect.BeanProperty

class Person {
    @BeanProperty
var name: String = _ }
Scala欄位 生成的方法 何時使用
val/var name 公有的name
name_=(僅限var)
實現一個可以被公開訪問並背後是以欄位形式儲存的屬性
@BeanProperty val/var name 公有的name
getName()
name_=(僅限於var)
setName(…)(僅限於var)
與JavaBean互操作
private val/var name 私有的name
name_=(僅限於var)
用於將欄位訪問限制在本類的方法
private[this] val/var name 用於將欄位訪問限制在同一個物件上呼叫的方法
private[類名] val/var name 依賴於具體實現 將訪問權賦予外部類

5.4 構造器

輔助構造器

  1. 輔助構造器的名稱為this
  2. 每一個輔助構造器都必須以一個對先前已定義的其他輔助構造器或主構造器的呼叫開始
class Person {
    private var name = ""
    private var age = 0

    def this(name: String) {
        this  // 呼叫主構造器
        this.name = name
    }

    def this(name: String, age: Int) {
        this(name)
        this.age = age
    }
}

主構造器

  1. 主構造器的引數直接放置在類名之後

    class Person(val name: String, private var age: Int, addr: String)
    
    // 如果不帶val或var的引數(例如addr)至少被一個方法所使用,它將被升格為欄位。類似這樣的欄位等同於private[this] val欄位效果。
  2. 主構造器會執行類定義中的所有語句

    class Person(val name: String, val age: Int) {
      println("建立Person物件")
      def desc = name + " is " + age + " years old"
    }
    
    // 上面的println就會被執行

5.5 巢狀類

在Scala中,幾乎可以在任何語法結構中內嵌任何語法結構。

class Network {
    class Member {
    }
}

5.6 習題解答


1. 改進5.1節的Counter類,讓它不要在Int.MaxValue時變成負數

class Counter {
  private var value = 0
  def increment() { if (value < Int.MaxValue) value += 1 }
  def current = value
}

val myCounter = new Counter
myCounter.increment
println(myCounter.current)


2. 編寫一個BankAccount類,加入deposit和withdraw方法,和一個只讀的balance屬性

class BankAcount(val balance : Int = 0) {
  def deposit() {}
  def withdraw() {}
}


3. 編寫一個Time類,加入只讀屬性hours和minutes,和一個檢查某一時刻是否早於另一時刻的方法before(other:Time):Boolean。Time物件應該以new Time(hrs,min)方式構建。其中hrs以軍用時間格式呈現(介於0和23之間)

class Time(val hours : Int, val minutes : Int) {
  def before(other : Time) : Boolean = {
    this.hours < other.hours || (this.hours == other.hours && this.minutes < other.minutes)
  }
}

val fir = new Time(10, 30)
val sec = new Time(10, 50)
val thi = new Time(12, 10)

sec.before(fir)
sec.before(thi)


4. 重新實現前一個類中的Time類,將內部呈現改成午夜起的分鐘數(介於0到24*60-1之間)。不要改變公有介面。也就是說,客戶端程式碼不應因你的修改而受影響

class Time(val hours : Int, val minutes : Int) {
  private val innerValue = hours * 60 + minutes;

  def before(other : Time) : Boolean = {
    this.innerValue < other.innerValue
  }
}

val fir = new Time(10, 30)
val sec = new Time(10, 50)
val thi = new Time(12, 10)

sec.before(fir)
sec.before(thi)


5. 建立一個Student類,加入可讀寫的JavaBeans屬性name(型別為String)和id(型別為Long)。有哪些方法被生產?(用javap檢視。)你可以在Scala中呼叫JavaBeans的getter和setter方法嗎?應該這樣做嗎?

import scala.beans.BeanProperty

class Student{
    @BeanProperty var name:String = _
    @BeanProperty var id:Long = _
}


6. 在5.2節的Person類中提供一個主構造器,將負年齡轉換為0

class Person(var age:Int){  
  age = if(age < 0) 0 else age  
} 


7. 編寫一個Person類,其主構造器接受一個字串,該字串包含名字,空格和姓,如new Person(“Fred Smith”)。提供只讀屬性firstName和lastName。主構造器引數應該是var,val還是普通引數?為什麼?

必須為val。如果為var,則對應的此字串有getset方法,而Person中的firstName和lastName為只讀的,所以不能重複賦值。如果為var則會重複賦值而報錯 


8. 建立一個Car類,以只讀屬性對應制造商,型號名稱,型號年份以及一個可讀寫的屬性用於車牌。提供四組構造器。每個構造器fc都要求製造商和型號為必填。型號年份和車牌可選,如果未填,則型號年份為-1,車牌為空串。你會選擇哪一個作為你的主構造器?為什麼?

class Car(val maker : String, val typeName : String, val year : Int = -1, var carlice : String = "") {

  override def toString : String = {
    maker + " " + typeName + " " + year + " " + carlice
  }
} 

val car = new Car("中汽", "t-xx")


9. 在Java,C#或C++重做前一個練習。Scala相比之下精簡多少?

這個不寫了


10. 考慮如下的類

class Employ(val name:String,var salary:Double){
def this(){this(“John Q. Public”,0.0)}
}

重寫該類,使用顯示的欄位定義,和一個預設主構造器。你更傾向於使用哪種形式?為什麼?

class Employ{
    val name:String = "John Q. Public" 
    var salary:Double = 0.0
}

第六章 物件

6.1 基本概念

單例物件

Scala沒有靜態方法或靜態欄位,可以用object來達到同樣目的。

object Accounts {
    private var lastNumber = 0
    def newUniqueNumber() = { lastNumber += 1; lastNumber }
}

物件本質上可以擁有類的所有特性,只有一個例外:不能提供構造器引數。

可以用物件實現:

  1. 作為存放工具函式或常量的地方
  2. 高效地共享單個不可變例項
  3. 需要用單個例項來協調某個服務時

伴生物件

在Scala中,可以通過定義和類同名的(伴生)物件來提供其他語言(例如Java)中既有例項方法又有靜態方法的類。

類和它的伴生類可以互相訪問私有特性,但必須定義在同一個原始檔中。

擴充套件類或特質的物件

object可以擴充套件類以及一個或多個特質,結果是一個擴充套件了指定類以及特質的類的物件。

apply方法

舉例來說,Array物件定義了apply方法,可以用以下形式建立陣列

Array("Mary", "had", "a", "little", "lamb")

定義apply方法的示例

class Account private (val id: Int, initialBalance: Double) {
    private var balance = initialBalance

    override def toString = "id = " + id + " initialBalance = " + initialBalance
}

object Account {
    private var lastNumber = 0

    def newUniqueNumber() = { lastNumber += 1; lastNumber }

    def apply(initialBalance: Double) =
        new Account(newUniqueNumber(), initialBalance)
}

object Note1 extends App {
    val acct = Account(100.0)
    println(acct)
}

應用程式物件

每個Scala程式都必須從一個物件的main方法開始,型別為Array[String] => Unit

object Note1 {
    def main(args: Array[String]) {
        val acct = Account(100.0)
        println(acct)
    }
}

也可以擴充套件App特質,然後將程式程式碼放入構造器方法體內:

object Note1 extends App {
    // 可以通過args屬性得到命令列引數
    println(args.length)

    val acct = Account(100.0)
    println(acct)
}

如果呼叫應用程式時設定了scala.time選項,則程式在退出時會顯示逝去的時間。

6.2 列舉

Scala並沒有列舉型別,但是可以通過標準類庫的Enumeration助手類,產生列舉。

object TrafficLightColor extends Enumeration {
    type TrafficLightColor = Value
    val Red, Yellow, Green = Value
}

object TrafficLightColor2 extends Enumeration {
    val Red = Value(0, "Stop")
    val Yellow = Value(10)  // 預設名稱為欄位名
    val Green = Value("Go") // ID不指定時, 將在前一個列舉值基礎上加一, 從零開始
}

// 使用示例
import com.zw.demo.six.TrafficLightColor._

object Note2 {

    def doWhat(color: TrafficLightColor) = {
        if (color == Red) "stop"
        else if (color == Yellow) "hurry up"
        else "go"
    }

    def main(args: Array[String]) {
        // values方法獲得所有列舉值得集
        for (c <- TrafficLightColor2.values) println(c.id + ": " + c)

        // 通過列舉的ID或名稱來進行查詢定位
        println(TrafficLightColor(2))
        println(TrafficLightColor.withName("Red"))
    }
}

6.3 習題解答


1. 編寫一個Conversions物件,加入inchesToCentimeters、gallonsToLiters和milesToKilometers方法

object Conversions {
  def inchesToCentimeters() {
  }
  def gallonsToLiters() {
  }
  def milesToKilometers() {
  }
}


2. 前一個練習不是很面向物件。提供一個通用的超類UnitConversion並定義擴充套件該超類的InchesToCentimeters、GallonsToLiters和MilesToKilometers物件

abstract class UnitConversion {
  def inchesToCentimeters() {}
  def gallonsToLiters() {}
  def milesToKilometers() {}
}

object InchesToCentimeters extends UnitConversion{
  override def inchesToCentimeters() {}
}

object GallonsToLiters extends UnitConversion{
  override def gallonsToLiters() {}
}

object MilesToKilometers extends UnitConversion{
  override def milesToKilometers() {}
}


注:超類class中必須有方法體{},否則物件無法單獨實現方法。


3. 定義一個擴充套件自java.awt.Point的Origin物件。為什麼說這實際上不是個好主意?(仔細看Point類的方法)

注:Point中的getLocation方法返回的是Point物件,如果想返回Origin物件,需要Origin類才行

import java.awt.Point

object Origin extends Point with App {

  override def getLocation: Point = super.getLocation

  Origin.move(2,3)
  println(Origin.toString)
}


4. 定義一個Point類和一個伴生物件,使得我們可以不用new而直接用Point(3,4)來構造Point例項

class Point(var x : Float, var y : Float) {
  override def toString: String = "x = " + x + " y = " + y
}

object Point {
  def apply(x : Float, y : Float)={  
    new Point(x, y)  
  }
}

val p = Point(1.2f, 2.4f)


5. 編寫一個Scala應用程式, 使用App特質, 以反序列印命令列引數, 用空格隔開。舉例來說, scala Reverse Hello World應該列印World Hello

class Reverse extends App {
  args.reverse.foreach(arg => print(arg  + " ")) 
}


6. 編寫一個撲克牌4種花色的列舉, 讓其toString方法分別返回♣,♦,♥,♠

object Poker extends Enumeration {
  val M = Value("♣")
  val T = Value("♠")
  val H = Value("♥")
  val F = Value("♦")
}


7. 實現一個函式, 檢查某張牌的花色是否為紅色

object Poker extends Enumeration {
  val M = Value("♣")
  val T = Value("♠")
  val H = Value("♥")
  val F = Value("♦")

  def color(kind : Poker.Value) : Boolean = {
    if (kind == Poker.H || kind == Poker.F) true else false
  }
}

Poker.color(Poker.H)


8. 編寫一個列舉, 描述RGB立方體的8個角。ID使用顏色值(例如:紅色是0xff0000)

object RGB extends Enumeration {
  val RED = Value(0xff0000, "Red")
  val BLACK = Value(0x000000, "Black")
  val GREEN = Value(0x00ff00, "Green")
  val CYAN = Value(0x00ffff, "Cyan")
  val YELLOW = Value(0xffff00, "Yellow")
  val WHITE = Value(0xffffff, "White")
  val BLUE = Value(0x0000ff, "Blue")
  val MAGENTA = Value(0xff00ff, "Magenta")
}

for (c <- RGB.values) println(c.id + ":" + c)

第七章 包和引入

7.1 包

Scala的包和其他作用域一樣地支援巢狀。通過下面形式的定義,可以訪問上層作用域中的名稱。

package com {
    package horstmann {
        package impatient {
            class Employee
            ....
        }
    }
}

串聯式包語句,限定了可見的成員。

package com.horstmann.impatient {
    // com和com.horstmann的成員在這裡不可見
    package people {
        class Person
        ...
    }
}

使用檔案頂部標記法,可以不帶花括號。

7.2 包物件

用package object定義包物件:

package com.horstmann.impatient

package object people {
    val defaultName = "影夜life"
}

package people {
    class Person {
        var name = defaultName // 從包物件拿到的常量
        ...
    }
}


注意defaultName不需要加限定詞,因為它位於同一個包內。在其他地方,這個常量可以用com.horstmann.impatient.people.defaultName

7.3 包可見性

在Java中,沒有被宣告為public、private或protected的類成員在包含該類的包中可見。在Scala中,可以通過修飾符達到同樣的效果。

package com.horstmann.impatient.people

class Person {
    // 在包impatient中可見
    private[impatient] def desc = "A person with name " + name
}

7.4 引入

import java.awt.Color

// 引入包下所有成員
import java.awt._

// 引入類或物件的所有成員
import java.awt.Color._

而且import語句可以出現在任何地方。效果一直延伸到包含該語句的塊末尾

class Manager {
    import scala.collection.mutable._
    val sub = new ArrayBuffer[Employee]
}

引入包中多個成員時,可以使用選取器(selector)

import java.awt.{Color, Font}

還可以重新命名選到的成員

import java.util.{HashMap => JavaHashMap}

選取器HashMap => _將隱藏某個成員

import java.util.{HashMap => _, _}
import scala.collection.mutable._

// 這樣HashMap無二義地指向scala.collection.mutable.HashMap

每個scala程式都隱式地以如下程式碼開始

import java.lang._
import scala._
import Predef._

// 這裡的引入,允許覆蓋之前的引入(例如scala.StringBuilder會覆蓋java.lang.StringBuilder而不是與之衝突。

7.5 習題解答


1. 編寫示例程式,展示為什麼
package com.horstmann.impatient
不同於
package com
package horstmann
package impatient

package com {
  class T1() {}

  package horstmann {
    class T2(t: T1) {}

    package impatient {
      class T3(t1: T1, t2: T2) {}
    }
  }
}

package com.horstmann.impatient{
  class T4(t1:T1,t3:T3)      //無法找到T1
}


注:scala的REPL無法執行


2. 編寫一段讓你的Scala朋友們感到困惑的程式碼,使用一個不在頂部的com包

package com {
  class T1() {}

  package horstmann {
    class T2(t: T1) {}

    package impatient {
      class T3(t1: T1, t2: T2) {}
    }
  }
}

import com._

class TT(t1:T1){

}


3. 編寫一個包random,加入函式nextInt():Int、nextDouble():Double、setSeed(seed:Int):Unit。生成隨機數的演算法採用線性同餘生成器:
後值 = (前值 * a + b)mod 2^n
其中,a = 1664525, b=1013904223, n = 32, 前值的初始值為seed

package random {

  package object random {

    var seed: Int = _
    val a = BigDecimal(1664525)
    val b = BigDecimal(1013904223)
    val n = 32

    def nextInt(): Int = {
      val temp = (seed * a + b) % BigDecimal(2).pow(n)
      seed = temp.toInt
      seed
    }

    def nextDouble(): Double = {
      val temp = (seed * a + b) % BigDecimal(2).pow(n)
      seed = temp.toInt
      temp.toDouble
    }
  }

}

package test {

  import random.random

  object Test extends App {
    random.seed = 4
    println(random.nextDouble())
    println(random.nextDouble())
    println(random.nextDouble())
    println(random.nextDouble())
    println(random.nextInt())
    println(random.nextInt())
    println(random.nextInt())
    println(random.nextInt())
  }
}


4. 在你看來Scala的設計者為什麼要提供package object語法而不是簡單的讓你將函式和變數新增到包中呢?

JVM不支援。。。


5. private[com] def giveRaise(rate:Double)的含義是什麼?有用嗎?

除了com包可訪問,其他包都不能訪問。


6. 編寫一段程式,將Java雜湊對映中的所有元素拷貝到Scala雜湊對映。用引入語句重新命名這兩個類。

import java.util.{HashMap => JavaHashMap}

import scala.collection.mutable.HashMap

object Test extends App {

  val map = new JavaHashMap[String, String]()
  map.put("1", "a")
  map.put("2", "b")
  map.put("3", "c")

  val smap = new HashMap[String, String]()

  for (key <- map.keySet().toArray) {
    smap += (key.toString -> map.get(key))
  }

  println(smap.mkString)
}


7. 在前一個練習中,將所有引入語句移動到儘可能小的作用域裡

object Test extends App {

  import java.util.{HashMap => JavaHashMap}

  val map = new JavaHashMap[String, String]()
  map.put("1", "a")
  map.put("2", "b")
  map.put("3", "c")

  import scala.collection.mutable.HashMap

  val smap = new HashMap[String, String]()

  for (key <- map.keySet().toArray) {
    smap += (key.toString -> map.get(key))
  }

  println(smap.mkString)
}


8. 以下程式碼的作用是什麼?這是個好主意嗎?
import java._
import javax._

匯入java和javax下的所有類。而java和javax下是沒有類的。所以此程式碼無用


9. 編寫一段程式,引入java.lang.System類,從user.name系統屬性讀取使用者名稱,從Console物件讀取一個密碼,如果密碼不是”secret”,則在標準錯誤流中列印一個訊息;如果密碼是”secret”,則在標準輸出流中列印一個問候訊息。不要使用任何其他引入,也不要使用任何限定詞(帶句點的那種)

import java.lang.System

object Test extends App {
  var password = Console.readLine()

  if (password equals "secret") System.out.println("Hello " + System.getProperty("user.name"))
  else System.err.println("password error!")
}


10. 除了StringBuilder,還有哪些java.lang的成員是被scala包覆蓋的?

直接比對java.lang下的類和scala包下的類即可

第八章 繼承

8.1 基本概念

擴充套件類

class Employee extends Person

// 可以用final修飾類、方法或欄位,表明它們不能被重寫。

重寫方法

重寫非抽象方法必須使用override修飾符。

呼叫超類方法,使用super關鍵字。

型別檢查和轉換

if (p.isInstanceOf[Employee]) { // 型別檢查
    val s = p.asInstanceOf[Employee]  // 型別轉換
}
  1. 如果p為null,p.isInstanceOf[Employee]為false,且p.asInstanceOf[Employee]返回null;
  2. 如果p不是Employee,則p.asInstanceOf[Employee]將丟擲異常

如果測試p指向的是一個Employee物件但又不是其子類的話,可以用

if (p.getClass == classOf[Employee])

使用模式匹配也可以

p match {
    case s: Employee => ... // 將s作為Employee處理
    case _ => // p不是Employee
}

受保護欄位和方法

和Java一樣,protected的欄位與方法可以被任何子類訪問,但不能從其他位置看到。

與Java不同,protected的成員對於類所屬的包不可見。

protected[this]將訪問許可權定在當前的物件,類似private[this]。

超類的構造

class Employee(name: String, age: Int, val salary: Double) extends Person(name, age)

// 擴充套件Java類時,主構造器必須呼叫Java超類的某一個構造器
class Square(x: Int, y: Int, width: Int) extends java.awt.Rectangle(x, y, width, width)

重寫欄位

abstract class Person {
    def id: Int
}

class Student(override val id: Int) extends Person


注:

  1. def只能重寫另一個def
  2. val只能重寫另一個val或不帶引數的def
  3. var只能重寫另一個抽象的var

匿名子類

val alien = new Person("Fred") {
    def greeting = "Hello"
}

def meet(p: Person{def greeting: String}) {
    println(p.name + "says: " + p.greeting)
}

抽象類

用abstract關鍵字標識。抽象方法不需要abstract標識,只需省去方法體。

子類重寫超類的抽象方法時,不需要使用override關鍵字。

抽象欄位

抽象欄位就是沒有初始值的欄位。和方法一樣,子類重寫抽象欄位,不需要override關鍵字。

8.2 構造順序和提前定義

構造順序

超類的構造器呼叫子類的方法時,由於構造順序,可能會產生非預期的結果。如下:

class Creature {
    val range: Int = 10
    val env: Array[Int] = new Array[Int](range)
}

class Ant extends Creature {
    override val range = 2
}

有如下解決方式:

  1. 將val生命為final
  2. 在超類中將val生命為lazy
  3. 在子類中使用提前定義語法

提前定義

將val欄位放在位於extends之後的一個塊中。

class Ant extends {
    override val range = 2
} with Creature

8.3 Scala繼承層級

基本型別和Unit型別擴充套件自AnyVal;所有其他類都是AnyRef(相當於Java的Object)的子類。

AnyVal和AnyRef擴充套件自Any類。

Any類定義了isInstanceOf、asInstanceOf、以及相等性判斷和雜湊碼的方法。

AnyVal沒有任何方法,只是一個標記。

AnyRef追加了Object類的監視方法wait和notify、notifyAll。同時提供了一個帶函式引數的方法synchronized。等同於Java中的同步塊。

所有Scala類都實現ScalaObject這個標記介面。

Null型別的唯一例項是null值。可以將null賦值給任何引用,但不能賦值給值型別。

Nothing型別沒有例項。它對泛型結構時常有用。例如,空列表Nil的型別是List[Nothing],它是List[T]的子型別,T可以是任何類。

這裡寫圖片描述

8.4 物件相等性

AnyRef的eq方法檢查兩個引用是否指向同一個物件。AnyRef的equals方法呼叫eq。

當自定義equals時,同時也要自定義hashCode。在計算雜湊碼時,只應使用那些用來做相等性判斷的欄位。

在應用程式中,只要使用==操作符就好,對於引用型別,它會做完必要的null檢查後呼叫equals方法。

8.5 習題解答


1. 擴充套件如下的BankAccount類,新類CheckingAccount對每次存款和取款都收取1美元的手續費

class BankAccount(initialBalance:Double) { 
    private var balance = initialBalance 
    def deposit(amount:Double) = { balance += amount; balance} 
    def withdraw(amount:Double) = {balance -= amount; balance} 
} 
class CheckingAccount(initialBalance:Double) extends BankAccount(initialBalance) {
  override def deposit(amount: Double): Double = super.deposit(amount - 1)
  override def withdraw(amount: Double): Double = super.withdraw(amount + 1)
}


2. 擴充套件前一個練習的BankAccount類,新類SavingsAccount每個月都有利息產生(earnMonthlyInterest方法被呼叫),並且有每月三次免手續費的存款或取款。在earnMonthlyInterest方法中重置交易計數。

class SavingsAccount(initialBalance:Double) extends BankAccount(initialBalance){
  private var num:Int = _

  def earnMonthlyInterest()={
    num = 3
    super.deposit(1)
  }

  override def deposit(amount: Double): Double = {
    num -= 1
    if(num < 0) super.deposit(amount - 1) else super.deposit(amount)
  }

  override def withdraw(amount: Double): Double = {
    num -= 1
    if (num < 0) super.withdraw(amount + 1) else super.withdraw(amount)
  }
}


3. 翻開你喜歡的Java或C++教科書,一定會找到用來講解繼承層級的例項,可能是員工,寵物,圖形或類似的東西。用Scala來實現這個示例。

java程式碼

class Art{
    Art(){System.out.println("Art constructor");}
}

class Drawing extends Art{
    Drawing() {System.out.println("Drawing constructor");}
}

public class Cartoon extends Drawing{
    public Cartoon() { System.out.println("Cartoon constructor");}
}

scala程式碼

class Art{
  println("Art constructor")
}

class Drawing extends Art{
  println("Drawing constructor")
}

class Cartoon extends Drawing{
  println("Cartoon constructor")
}


4. 定義一個抽象類Item,加入方法price和description。SimpleItem是一個在構造器中給出價格和描述的物件。利用val可以重寫def這個事實。Bundle是一個可以包含其他物件的物件。其價格是打包中所有物件的價格之和。同時提供一個將物件新增到打包當中的機制,以及一個適合的description方法

import collection.mutable.ArrayBuffer

abstract class Item{
  def price():Double
  def description():String

  override def toString():String={
    "description:" + description() + "  price:" + price()
  }
}

class SimpleItem(val price:Double,val description:String) extends Item{

}

class Bundle extends Item{

  val items = new ArrayBuffer[Item]()

  def addItem(item:Item){
    items += item
  }

  def price(): Double = {
    var total = 0d
    items.foreach(total += _.price())
    total
  }

  def description(): String = {
    items.mkString(" ")
  }
}


5. 設計一個Point類,其x和y座標可以通過構造器提供。提供一個子類LabeledPoint,其構造器接受一個標籤值和x,y座標,比如:new LabeledPoint(“Black Thursday”,1929,230.07)

class Point(val x : Int, val y : Int) {
}

class LabeledPoint(val label : String, x : Int, y : Int) extends Point(x, y) {
}


6. 定義一個抽象類Shape,一個抽象方法centerPoint,以及該抽象類的子類Rectangle和Circle。為子類提供合適的構造器,並重寫centerPoint方法

abstract class Shape{
  def centerPoint()
}

class Rectangle(startX:Int,startY:Int,endX:Int,endY:Int) extends Shape{
  def centerPoint() {}
}

class Circle(x:Int,y:Int,radius:Double) extends Shape{
  def centerPoint() {}
}


7. 提供一個Square類,擴充套件自java.awt.Rectangle並且是三個構造器:一個以給定的端點和寬度構造正方形,一個以(0,0)為端點和給定的寬度構造正方形,一個以(0,0)為端點,0為寬度構造正方形

import java.awt.{Point, Rectangle}


class Square(point:Point, width:Int) extends Rectangle(point.x,point.y,width,width){

  def this(){
    this(new Point(0,0),0)
  }

  def this(width:Int){
    this(new Point(0,0),width)
  }
}


8. 編譯8.6節中的Person和SecretAgent類並使用javap分析類檔案。總共有多少name的getter方法?它們分別取什麼值?(提示:可以使用-c和-private選項)

總共兩個。Person中取得的是傳入的name,而SecretAgent中取得的是預設的”secret”


9. 在8.10節的Creature類中,將val range替換成一個def。如果你在Ant子類中也用def的話會有什麼效果?如果在子類中使用val又會有什麼效果?為什麼?

在Ant中使用def沒有問題。但是如果使用val則無法編譯。因為val只能重寫不帶引數的def。這裡的def是帶引數的


10. 檔案scala/collection/immutable/Stack.scala包含如下定義:
class Stack[A] protected (protected val elems: List[A])
請解釋protected關鍵字的含義。(提示:回顧我們在第5章中關於私有構造器的討論)

此構造方法只能被其子類來呼叫,而不能被外界直接呼叫