一、前言

  前面學(xué)習(xí)了Scala的Methods,接著學(xué)習(xí)Scala中的Object

二、Object

  Object在Scala有兩種含義,在Java中,其代表一個(gè)類的實(shí)例,而在Scala中,其還是一個(gè)關(guān)鍵字,本篇首先將會(huì)把object當(dāng)成一個(gè)類的實(shí)例看待,展示如何將對(duì)象從一種類型轉(zhuǎn)化為另一種類型,之后將展示如何創(chuàng)建單例對(duì)象,Scala中還存在包對(duì)象,在Scala中,經(jīng)常有如下定義

type Throwable = java.lang.Throwable
type Exception = java.lang.Exception
type Error = java.lang.Error
type Seq[+A] = scala.collection.Seq[A]
val Seq = scala.collection.Seq

  使用type定義可以使得代碼更為簡潔,可使用伴生對(duì)象來創(chuàng)建靜態(tài)方法,并且伴生對(duì)象可以使得在創(chuàng)建類對(duì)象時(shí)不需要使用new關(guān)鍵字,如下所示

val siblings = List(Person("Kim"), Person("Julia"), Person("Kenny"))

  2.1 對(duì)象轉(zhuǎn)化

  1. 問題描述

  你需要將一個(gè)類的實(shí)例從一種類型轉(zhuǎn)化為另一種類型,如動(dòng)態(tài)創(chuàng)建對(duì)象

  2. 解決方案

  使用asInstanceOf方法進(jìn)行類型轉(zhuǎn)化,如下的lookup方法返回的對(duì)象將給轉(zhuǎn)化為Recognizer對(duì)象 

val recognizer = cm.lookup("recognizer").asInstanceOf[Recognizer]

  以上代碼在Java中如下 

Recognizer recognizer = (Recognizer)cm.lookup("recognizer");

  asInstanceOf方法是定義在Any類中的,所以任何類中都可以使用該方法

  3. 討論

  在動(dòng)態(tài)編程中,經(jīng)常需要從一個(gè)類轉(zhuǎn)化為另一個(gè)類,如在Spring框架中使用ApplicationContext文件來初始化Bean 

// open/read the application context fileval ctx = new ClassPathXmlApplicationContext("applicationContext.xml")// instantiate our dog and cat objects from the application contextval dog = ctx.getBean("dog").asInstanceOf[Animal]
val cat = ctx.getBean("cat").asInstanceOf[Animal]

  在進(jìn)行數(shù)字類型轉(zhuǎn)化時(shí),也可以使用asInstanceOf方法

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  當(dāng)需要與Java進(jìn)行交互時(shí),也可以使用asInstanceOf方法  

val objects = Array("a", 1)
val arrayOfObject = objects.asInstanceOf[Array[Object]]
AJavaClass.sendObjects(arrayOfObject)

  與Java類似,類型轉(zhuǎn)化可能會(huì)拋出ClassCastException異常

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  可以使用try/catch來解決此問題

  2.2 與Java的.class對(duì)應(yīng)的方法

  1. 問題描述

  當(dāng)一個(gè)API需要你傳遞Class對(duì)象,在Java中,你可以使用.class,但是在Scala則行不通

  2. 解決方案

  使用Scala的classOf方法,如下所示  

val info = new DataLine.Info(classOf[TargetDataLine], null)

  在Java中則使用如下  

info = new DataLine.Info(TargetDataLine.class, null);

  classOf方法定義在Predef對(duì)象中,因此可以在沒有import情況直接使用

  3. 討論

  該方法可以讓你開始學(xué)習(xí)反射,如下示例可以訪問String類的方法

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  2.3 確定對(duì)象的類

  1. 問題描述

  在Scala中,你不需要顯示的聲明類型,你偶爾也想要打印一個(gè)對(duì)象的類或類型以明白Scala的工作機(jī)制

  2. 解決方案

  你可以使用對(duì)象的getClass方法來確定Scala為你自動(dòng)賦值的類型,如當(dāng)需要了解可變參數(shù)的工作流程時(shí),可以調(diào)用getClass方法,得知不同情況,類型也不相同 

def printAll(numbers: Int*) {
    println("class: " + numbers.getClass)
}

  當(dāng)使用多個(gè)參數(shù)調(diào)用printAll方法和不使用參數(shù)調(diào)用printAll方法,其結(jié)果不相同

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  當(dāng)處理Scala的XML庫時(shí),該方法非常有效,可以知道在不同情形下所處理的類,如下,<p>標(biāo)簽下包含了一個(gè)子類

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  當(dāng)在<p>標(biāo)簽中添加<br/>標(biāo)簽后,其結(jié)果如下

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  3. 討論

  若在IDE中無法得知對(duì)象的類型,可以使用getClass方法來獲取對(duì)象類型

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  2.4 使用對(duì)象啟動(dòng)應(yīng)用

  1. 問題描述

  你想要使用main方法來啟動(dòng)一個(gè)應(yīng)用,或者為腳本提供一個(gè)入口

  2. 解決方案

  啟動(dòng)應(yīng)用有兩種方法,其一讓類繼承App,其二是定義一個(gè)對(duì)象并定義main方法

  對(duì)于第一種方法,其通用做法如下 

object Hello extends App {
    println("Hello, world")
}

  此時(shí)object內(nèi)的語句會(huì)自動(dòng)執(zhí)行,第二種方法是定義main方法

object Hello2 {
    def main(args: Array[String]) {
        println("Hello, world")
    }
}

  3. 討論

  上述兩種方法中,都是通過object來啟動(dòng)應(yīng)用的

  2.5 使用Object創(chuàng)建單例

  1. 問題描述

  你想要?jiǎng)?chuàng)建一個(gè)單例對(duì)象

  2. 解決方案

  使用object關(guān)鍵字來創(chuàng)建單例對(duì)象  

object CashRegister {
    def open { println("opened") }
    def close { println("closed") }
}

  由于CashRegister被定義為成object,因此僅僅只有一個(gè)實(shí)例,被調(diào)用的方法就相當(dāng)于Java中的靜態(tài)方法,調(diào)用如下 

object Main extends App {
    CashRegister.open
    CashRegister.close
}

  在創(chuàng)建工具方法時(shí),該方法同樣有效  

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

import java.util.Calendarimport java.text.SimpleDateFormat

object DateUtils {    // as "Thursday, November 29"
    def getCurrentDate: String = getCurrentDateTime("EEEE, MMMM d")    
    // as "6:20 p.m."
    def getCurrentTime: String = getCurrentDateTime("K:m aa")    
    // a common function used by other date/time functions
    private def getCurrentDateTime(dateTimeFormat: String): String = {
        val dateFormat = new SimpleDateFormat(dateTimeFormat)
        val cal = Calendar.getInstance()
        dateFormat.format(cal.getTime())
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  由于方法是定義在object中,因此可以直接使用DateUtils來調(diào)用這些方法,如同Java中調(diào)用靜態(tài)方法 

DateUtils.getCurrentDate
DateUtils.getCurrentTime

  在使用actors時(shí),單例對(duì)象可以產(chǎn)生可重用的消息,如果你有可以接受和發(fā)送消息的actor,你可以使用如下方法創(chuàng)建單例 

case object StartMessagecase object StopMessage

  這些對(duì)象將會(huì)被作為消息,并且可被傳遞到actor中  

inputValve ! StopMessage
outputValve ! StopMessage

  3. 討論

  當(dāng)使用伴生對(duì)象時(shí),一個(gè)類就既可以有非靜態(tài)方法又可以有靜態(tài)方法

  2.6 使用伴生對(duì)象創(chuàng)建靜態(tài)成員

  1. 問題描述

  你想要為一個(gè)類創(chuàng)建實(shí)例方法和類方法,但是Scala中沒有static關(guān)鍵字

  2. 解決方案

  在class中定義非靜態(tài)成員,在object中定義靜態(tài)成員,對(duì)象與類要有相同的名字并且位于同一個(gè)文件中,該對(duì)象稱為伴生對(duì)象

  使用該方法可以讓你創(chuàng)建靜態(tài)成員(字段和方法) 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

// Pizza classclass Pizza (var crustType: String) {
    override def toString = "Crust type is " + crustType
}// companion objectobject Pizza {
    val CRUST_TYPE_THIN = "thin"
    val CRUST_TYPE_THICK = "thick"
    def getFoo = "Foo"}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  Pizza類和Pizza對(duì)象在同一個(gè)文件中(Pizza.scala),Pizza對(duì)象中的成員等效于Java類中的靜態(tài)成員 

println(Pizza.CRUST_TYPE_THIN)
println(Pizza.getFoo)

  你也可按照常規(guī)方法創(chuàng)建Pizza對(duì)象

var p = new Pizza(Pizza.CRUST_TYPE_THICK)
println(p)

  3. 討論

  class和object具有相同的名稱并且在同一個(gè)文件中,class中定義的是非靜態(tài)成員,object中定義的是靜態(tài)成員

  class和其伴生對(duì)象可以互相訪問對(duì)方的私有成員,如下面object的double方法可以訪問class中的私有變量secret  

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

class Foo {    private val secret = 2}

object Foo {    // access the private class field 'secret'
    def double(foo: Foo) = foo.secret * 2}

object Driver extends App {
    val f = new Foo
    println(Foo.double(f)) // prints 4}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  如下的class類中的非靜態(tài)方法可以訪問伴生對(duì)象中的靜態(tài)私有變量 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

class Foo {    // access the private object field 'obj'
    def printObj { println(s"I can see ${Foo.obj}") }
}

object Foo {    private val obj = "Foo's object"}

object Driver extends App {
    val f = new Foo
    f.printObj
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  2.7 將常用代碼放在包對(duì)象中

  1. 問題描述

  你想要使方法、字段和其他代碼處于包級(jí)別,而不需要class或者object

  2. 解決方案

  將代碼放置在包對(duì)象下面,如將你的代碼放置在package.scala文件中,例如,如果你想要代碼被com.hust.grid.leesf.model包下所有類可用,那么創(chuàng)建一個(gè)位于com/hust/grid/leesf/model目錄下的package.scala文件,在package.scala中,在包聲明中移除model,并且以其作為名字來創(chuàng)建包,大致如下 

package com.hust.grid.leesfpackage object model {

  其他代碼放置在model中,如下所示

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

package com.hust.grid.leesfpackage object model {    // field
    val MAGIC_NUM = 42    // method    def echo(a: Any) { println(a) }    
    // enumeration
    object Margin extends Enumeration {
        type Margin = Value
        val TOP, BOTTOM, LEFT, RIGHT = Value
    }    
    // type definition
    type MutableMap[K, V] = scala.collection.mutable.Map[K, V]
    
    val MutableMap = scala.collection.mutable.Map
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  此時(shí),在com.hust.grid.leesf.model包下面類、對(duì)象、接口等可以隨意訪問上述定義的字段、方法等

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

package com.hust.grid.leesf.model

object MainDriver extends App {    // access our method, constant, and enumeration
    echo("Hello, world")
    echo(MAGIC_NUM)
    echo(Margin.LEFT)    // use our MutableMap type (scala.collection.mutable.Map)
    val mm = MutableMap("name" -> "Al")
    mm += ("password" -> "123")    for ((k,v) <- mm) printf("key: %s, value: %s\n", k, v)
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  3. 討論

  最疑惑的是將package對(duì)象放在哪里,其包名和對(duì)象名

  如果你想要讓你的代碼在com.hust.grid.leesf.model包中可見,那么將package.scala放在com/hust/grid/leesf/model目錄下,而在package.scala中,其包名應(yīng)該如下 

package com.hust.grid.leesf

  然后使用model作為對(duì)象名

package object model {

  最后大致如下  

package com.hust.grid.leesfpackage object model {

  包中可以存放枚舉類型、常量和隱式轉(zhuǎn)換

  2.8 不使用new關(guān)鍵字來創(chuàng)建對(duì)象實(shí)例

  1. 問題描述

  當(dāng)不使用new關(guān)鍵字來創(chuàng)建對(duì)象時(shí),Scala代碼會(huì)顯得相對(duì)簡潔,如下所示  

val a = Array(Person("John"), Person("Paul"))

  2. 解決方案

  有兩種方式

    · 為類創(chuàng)建伴生對(duì)象,然后定義apply方法,其簽名與構(gòu)造方法簽名相同

    · 將類定義為case類

  為Person對(duì)象定義了伴生對(duì)象,然后定義apply方法并接受參數(shù)

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

class Person {
    var name: String = _
}

object Person {
    def apply(name: String): Person = {
        var p = new Person
        p.name = name
        p
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  現(xiàn)在你可以不使用new關(guān)鍵字來創(chuàng)建Person對(duì)象了  

val dawn = Person("Dawn")
val a = Array(Person("Dan"), Person("Elijah"))

  Scala編譯器會(huì)對(duì)伴生對(duì)象中的apply進(jìn)行特殊處理,讓你不使用new關(guān)鍵字即可創(chuàng)建對(duì)象

  將類定義為case類,并且接受相應(yīng)參數(shù)  

case class Person (var name: String)

  現(xiàn)在可以采用如下方法創(chuàng)建對(duì)象

val p = Person("Fred Flinstone")

  Scala會(huì)為case類的伴生對(duì)象創(chuàng)建apply方法

  3. 討論

  編譯器會(huì)對(duì)伴生對(duì)象的apply做特殊處理,這是Scala的語法糖  

val p = Person("Fred Flinstone")

  上述代碼會(huì)被轉(zhuǎn)化為如下代碼 

val p = Person.apply("Fred Flinstone")

  apply方法是工廠方法,Scala的此語法糖讓你不用new關(guān)鍵字即可創(chuàng)建對(duì)象

  可以在伴生對(duì)象中創(chuàng)建多個(gè)apply方法,這樣相當(dāng)于多個(gè)構(gòu)造函數(shù)

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

class Person {
    var name = ""
    var age = 0}

object Person {    // a one-arg constructor
    def apply(name: String): Person = {
        var p = new Person
        p.name = name
        p
    }    // a two-arg constructor
    def apply(name: String, age: Int): Person = {
        var p = new Person
        p.name = name
        p.age = age
        p
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  可以使用如下方法創(chuàng)建對(duì)象  

val fred = Person("Fred")
val john = Person("John", 42)

  為了給case類創(chuàng)建多個(gè)構(gòu)造函數(shù),需要知道case類背后的邏輯

  當(dāng)使用scala編譯器編譯case類時(shí),你會(huì)發(fā)生其生成了兩個(gè)文件,Person$.class和Person.class文件,當(dāng)使用javap反編譯Person$.class文件時(shí),其輸出如下

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  其包含了一個(gè)返回Person對(duì)象的apply方法 

public Person apply(java.lang.String);

  String對(duì)應(yīng)的是case類中的name  

case class Person (var name: String)

  使用javap命令可以看到在Person.class中為name生成的getter和setter函數(shù)

  大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  在如下代碼中,存在case類和apply方法 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

// want accessor and mutator methods for the name and age fieldscase class Person (var name: String, var age: Int)// define two auxiliary constructorsobject Person {
    def apply() = new Person("<no name>", 0)
    def apply(name: String) = new Person(name, 0)
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  由于name和age都是var的,所以會(huì)生成getter和setter,在object中定義了兩個(gè)apply函數(shù),因此可以使用如下三種方式來生成Person對(duì)象 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

object Test extends App {
    val a = Person()
    val b = Person("Al")
    val c = Person("William Shatner", 82)
    println(a)
    println(b)
    println(c)    // test the mutator methods
    a.name = "Leonard Nimoy"
    a.age = 82
    println(a)
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  其結(jié)果如下  

Person(<no name>,0)
Person(Al,0)
Person(William Shatner,82)
Person(Leonard Nimoy,82)

  2.9 使用apply來實(shí)現(xiàn)工廠方法

  1. 問題描述

  為了讓子類聲明應(yīng)該創(chuàng)建哪種類型的對(duì)象,并且只在一處能夠創(chuàng)建對(duì)象,你想要實(shí)現(xiàn)工廠方法

  2. 解決方案

  可以使用伴生對(duì)象的apply方法來實(shí)現(xiàn)工廠方法,你可將工廠實(shí)現(xiàn)算法放置在apply方法中

  假設(shè)你想要?jiǎng)?chuàng)建一個(gè)Animal工廠,并且返回Cat和Dog,在Animal類的伴生對(duì)象中實(shí)現(xiàn)apply方法,你就可以使用如下方式創(chuàng)建不同對(duì)象 

val cat = Animal("cat") // creates a Catval dog = Animal("dog") // creates a Dog

  首先需要?jiǎng)?chuàng)建一個(gè)Animal的trait  

trait Animal {
    def speak
}

  然后在相同文件中創(chuàng)建伴生對(duì)象,創(chuàng)建實(shí)現(xiàn)Animal的類,一個(gè)合適的apply方法 

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

object Animal {    private class Dog extends Animal {
        override def speak { println("woof") }
    }    
    private class Cat extends Animal {
        override def speak { println("meow") }
    }    
    // the factory method    
    def apply(s: String): Animal = {        if (s == "dog") new Dog        else new Cat
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

  然后就可以使用如下語句創(chuàng)建不同對(duì)象

val cat = Animal("cat") // creates a Catval dog = Animal("dog") // creates a Dog

  3. 討論

  如果不使用apply方法來實(shí)現(xiàn)工廠方法,也可以使用如下的getAnimal方法來實(shí)現(xiàn)上述功能  

// an alternative factory method (use one or the other)def getAnimal(s: String): Animal = {    if (s == "dog") return new Dog    else return new Cat
}

  然后可以使用如下方法創(chuàng)建不同對(duì)象 

val cat = Animal.getAnimal("cat") // returns a Catval dog = Animal.getAnimal("dog") // returns a Dog

  以上兩種方法都是可行的

三、總結(jié)

  本篇學(xué)習(xí)了Scala中的object及其相應(yīng)的用法,其在Scala的實(shí)際編程中應(yīng)用也是非常廣泛,也謝謝各位園友觀看~