Scala類與對象

類簡介

簡介

類是對象的藍圖。一旦你定義了類,就能夠用關鍵字new根據類的藍圖建立對象。在類的定義裏,能夠放置字段和方法,這些被籠統地稱爲成員。對於字段,不論是val仍是var定義的,都是指向對象的變量。對於方法,用def定義,包含了可執行代碼。字段保留了對象的狀態或數據,而方法使用這些數據執行對象的運算工做。當類被實例化的時候,運行時環境會預留一些內存來保留對象的狀態映像——即變量的內容。html

示例

建立類示例:java

class SumAccumulator {
    var sum = 0
}

而後實例化兩次:程序員

val acc: SumAccumulator = new SumAccumulator
val csa: SumAccumulator = new SumAccumulator 

這時內存裏對象的狀態映像以下:緩存

 

因爲字段sum是var類型而非val,因此sum能夠被賦予新的Int類型的值:例如安全

acc.sum = 3

此時映像會變成:網絡

 

圖中同時存在兩個sum變量,一個在acc指向的對象裏,另外一個在csa指向的對象裏。字段的另外一種說法是實例變量(instance variable),由於每個實例都有本身的變量集。多線程

儘管acc和csa都是val類型,可是仍是能夠修改acc指向的對象的值(上面的sum)。val類型對象對acc(或csa)的限制僅限於不能夠把它們再次賦值給其餘對象。例如,下面的嘗試將會失敗:app

//編譯不過,由於acc是val
acc = new ChecksumAccumuulator

 

所以,咱們能夠得出結論,acc將始終指向初始化的Checksuumaccumulator對象,可是對象包含的字段能夠隨時改動。ide

Scala類的重要特性

訪問修飾符

  • Public是Scala默認的訪問級別

爲了保證對象的健壯性,能夠把類中字段變爲私有的(private)以阻止外界直接對它訪問。由於私有字段只能被定義成在同一類裏的方法訪問,全部跟新字段的代碼將鎖定在類裏。要聲明字段是私有的,能夠把訪問修飾符private放在字段的前面。函數

代碼示例:

class SumAccumulator {
    private var sum = 0
} 

使用private修飾後,任何從類外部對sum的訪問都將失敗

val acc: SumAccumulator = new SumAccumulator
acc.sum = 3 //編譯不經過,由於sum是私有的

 

方法

1. Scala方法參數類型

Scala方法參數中的類型都是val而不是var類型

def add(a: Int, b: Int): Int = {
    a = a + b //編譯錯誤:Reassignment to val (就是對val類型賦值的錯誤)
    a
}    

2. Scala方法中return

Scala方法中的return能夠去掉,從而簡化代碼

代碼示例:

def numSum(num:Int):Int ={
    val sum = num + 10
    sum //省略return關鍵字,簡化代碼
 
}

此時返回值就是sum

3. Scala方法中的「=」號

Scala方法中的「=」號很是重要:由於Scala編譯器能夠把任何類型轉換爲Unit若是定義的函數中」=」號忘記了添加,那麼Scala會默認它返回值爲Unit類型。若你原本想返回一個String類型的值,因爲沒有添加」=」,String類型返回值會被轉換爲Unit類型而丟棄掉。

代碼示例:

def printStr() {
    "這是String類型的返回值"
}

返回結果爲:   ()

正確代碼示例:

def printStr(): String = {
    "這是String類型的返回值"
}

 返回結果爲:    這是String類型的返回值

4. Scala方法表達式

假如某個方法僅計算單個結果的表達式,這能夠去掉花括號,若是結果表達式很短,甚至能夠把它放在def的同一行裏。

代碼示例:

def numSum(num:Int):Int = num + 10

5. Scala中分號推斷

scala程序中,語句末尾的分號一般是可選的,若一行僅有一個語句也能夠不加分號。不過,若是一行包含多條語句時,分號則是必須的。

不加分號:

if(x < 2)
println("too small")
else
println("ok")

必須加分號:

val x = 10; println(x)  //兩個語句,必須加分號

Scala一般的風格是把操做符放在行尾而不是行頭:

錯誤示例:

val x = 10;
val y = 3
val z = x //它會被編譯器識別爲z = x ; +y 兩個語句
+y

打印結果:10

正確示例:

val x = 10;
val y = 3
val z = x +
y
println(z)

打印結果:13

Scala分號推斷規則:

  1. 疑問行由一個不能合法做爲語句結尾的字結束,如句點或中綴操做符。
  2. 下一行開始於不能做爲語句開始的詞。
  3. 行結束於括號(...)或方框[...]內部,由於這些符號不能容納多個語句。

6. Scala無參方法

調用Scala類中無參方法時,能夠寫上括號,也能夠不寫。對於類中的取值器來講,去掉()是不錯的風格

代碼示例:

object exam1 {
def main(args: Array[String]): Unit = {
val per: Person = new Person
per.talk() //ok
per.talk //一樣ok
}
 
}
 
class Person {
def talk(): Unit = println("Talking")
}

若是你想強制使用這種風格,能夠在聲明方法時不帶()

代碼示例:

per.talk() //此時這種寫法是錯誤的
per.talk //OK的
class Person {
def talk = println("Talking")
}

Singleton對象

Scala比Java更爲面向對象的特色之一是Scala不能定義靜態成員,而是代之以定義單例對象(singleton Object)。除了用object關鍵字替換了class關鍵字之外,單例對象的定義看上去與類定義一致。

例子:ChecksumAccumulator.scala源碼:

class ChecksumAccumulator {
    private var sum = 0
    def add(b:Byte){sum += b}
    def checksum():Int = ~(sum & 0xFF) + 1
}
import scala.collection.mutable.Map
object ChecksumAccumulator {
    private val cache = Map[String, Int]()
    def calculate(s:String):Int=
        if(cache.contains(s))
            cache(s)
        else {
            val acc = new ChecksumAccumulator
            for(c <- s)
                acc.add(c.toByte)
            val cs = acc.checksum()
            cache += (s -> cs)
            cs
        }
}

上面源碼中的單例對象叫作ChecksumAccumulator,與前一個例子裏的類同名。當單例對象與某個類共享同一個名稱時,它就被稱爲是這個類的伴生對象(companion object)。類和它的伴生對象必須定義在一個源文件裏。類被稱爲是這個單例對象的伴生類(companion class)。類和它的伴生對象能夠相互訪問其私有成員。

這段緩存代碼的說明以下:

類和單例對象間的差異是,單例對象不帶參數,而類能夠。由於單例對象不是用new關鍵字實例化的,因此沒有機會傳遞給它實例化參數。每一個單例對象都被實現爲虛擬類(synthetic class)的實例,並指向靜態的變量,由於它們與Java靜態類有着相同的初始化語義。特別要指出的是,單例對象在第一次被訪問的時候纔會被初始化。

不與伴生類共享名稱的單例對象被稱爲獨立對象(standalone object)。它能夠用在不少地方,例如做爲相關功能方法的工具類,或者定義Scala應用的入口點。

Scala程序

  想要編寫可以獨立運行的Scala程序,就必須建立有main方法(僅帶一個參數Array[String],且結果類型爲Unit)的單例對象。任何擁有合適簽名的main方法的單例對象均可以用來做爲程序的入口點。

Summer.scala文件源碼:

import ChecksumAccumulator.calculate

object Summer {
    def main(args:Array[String]) {
        for(arg <- args)
            println(arg + ": " + calculate(arg))
    }
}

要執行Summer應用程序,須要把以上的代碼寫入文件Summer.scala中,由於Summer使用了ChecksumAccumulator,因此還要把ChecksumAccumulator的代碼,上面的源碼(類及它的伴生對象),放在文件ChecksumAccumulator.scala中。

Scala和Java之間有一點不一樣,Java須要類名稱與源碼文件名同名,而在Scala對於源文件的命名沒有硬性規定。然而一般狀況下若是不是腳本,推薦的風格是像在Java裏那樣按照所包含的類名來命名文件,這樣程序員就能夠比較容易地根據文件名找到類。

Scala的腳步必須以結果表達式介紹。所以若是你嘗試以腳本方式執行Summer.scala,Scala解釋器將會報錯說Summer.scala不是以結果表達式結束的。正確作法是:須要用Scala編譯器真正的編譯這些文件,而後執行輸出的類文件,方式之一使用Scala的基本編譯器,scalac。

D:\work\workspace\scala>scalac ChecksumAccumulator.scala Summer.scala

D:\work\workspace\scala>

D:\work\workspace\scala>fsc ChecksumAccumulator.scala Summer.scala

D:\work\workspace\scala>

 

D:\work\workspace\scala>scala Summer of love
of: -213
love: -182

D:\work\workspace\scala>

Scala中的Application

爲了使代碼更簡潔,Scala還提供了另一種運行Scala程序的方式,那就是直接繼承scala.Application接口(Trait)。
直接繼承自Application的運行方式:
object RunAppWithoutMain extends Application {  
    println("runing scala app without main")  
}
之因此這裏無須定義main方法,那是 由於在Application這個接口中定義了一個main方法,main方法在執行時會初始化RunAppWithoutMain這個對象,並執行它的主構造方法,而全部直接寫在對象中的代碼都會被scala編譯器收集到主構造方法中,因而就被運行了。
extends Application雖然比編寫main方法要方便,可是也有一些反作用。直接繼承自Application致使的反作用:
1. 沒法接受命令行參數。由於args參數不會被傳入
2. 在Scala中,若是一個程序是多線程的,那麼這個程序必須具備一個main方法。因此第二種寫法只能適用於單線程的程序
3. Application這個接口在執行一個程序的代碼以前,須要進行一些初始化。而某些JVM不會對這些初始化代碼進行優化。

因此第二種方法只適用於一些很是簡單的場合,大部分狀況不推薦使用。
 

Scala類中getter和setter

getter和setter

Scala類中使用公有字段的話,任何人均可以修改這個字段,使得安全性大大下降。因此咱們更傾向於使用getter和setter方法:

Scala對類中每一個字段都提供了getter和setter方法,分別叫作age和age_=,

代碼示例:

建立一個exam1.scala文件,文件內容以下

class Person {
var age = 0
}

若是想查看這些方法,能夠先編譯Person類,用scalac命令編譯,而後用javap查看字節碼:

D:\work\workspace\scala>scalac exam1.scala

D:\work\workspace\scala>javap Person
Compiled from "exam1.scala"
public class Person {
  public int age();
  public void age_$eq(int);
  public Person();
}

D:\work\workspace\scala>

 

 

你能夠本身從新定義getter和setter方法。

代碼示例:

object exam1 {
 
    def main(args: Array[String]): Unit = {
        val per: Person = new Person
        per.age = 18
        per.age = 16 //因爲在setter裏面控制了age不能變小,因此執行結果age不會變
        println(per.age)
        per.age = 19 //使用setter,賦予大於原來的age
        println(per.age)
        }
    }
 
    class Person {
        private var privateAge = 0 //變成私有變量並更名
        def age = privateAge
        def age_=(newAge: Int) { // age_= 不能有空格
        if (newAge > privateAge) privateAge = newAge //使得年齡不能變小
    }
}

打印結果爲:

18
19

Scala中每一個字段生成getter和setter的限制:

  1. 若是字段是私有的,則getter和setter方法也是私有的。
  2. 若是字段是val,則只有getter方法被生成。
  3. 若是你不須要任何getter或setter,能夠將字段聲明爲private[this]

Scala在實現類中屬性時的四個選擇:

  1. 自動生成一個getter和setter。
  2. 自動生成一個getter。
  3. 自定義foo和foo_=方法。
  4. 自定義foo方法。

Bean屬性

JavaBean規範把Java屬性定義爲一堆getFoo和setFoo方法。相似於Java,當你將Scala字段標註爲 @BeanProperty時,getter和setter方法會自動生成。

代碼示例:

import scala.beans.BeanProperty
 
object exam1 {
    def main(args: Array[String]): Unit = {
        val per: Person = new Person
        per.name = "zhagnsan"
        per.setName("lisi") //BeanProperty生成的setName方法
        println(per.getName) //BeanProperty生成的getName方法
    }
}
 
 
class Person {
    @BeanProperty var name:String = _
}

打印結果爲:

Lisi

上述類Person中由@BeanProperty生成了四個方法:

name: String
name_= (newValue: String): Unit
getName(): String
setName (newValue: String): Unit

圖示爲針對字段生成的方法:

 

Scala類中構造器

和java或C++同樣,Scala也能夠有任意多的構造器。不過,Scala類有一個構造器比其餘全部構造器都更爲重要,它就是主構造器。除了主構造器外,Scala類還能夠有任意多的輔助構造器。

輔助構造器

Scala中輔助構造器和Java或C++十分類似,只有兩處不一樣:

  1. 輔助構造器的名稱爲this。
  2. 每個輔助構造器都必須以一個對先前已定義的其餘輔助構造器或主構造器的調用開始

這裏有一個帶有兩個輔助構造器的類。

代碼示例:

object exam1 {
    def main(args: Array[String]): Unit = {
        val per1: Person = new Person //主構造器
        val per2: Person = new Person("Bob") //第一個輔助構造器
        val per3: Person = new Person("Bob",18) //第二個輔助構造器
    }
}
 
    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
        }
    }    

主構造器

在Scala中,每一個類都有主構造器。主構造器並不以this方法定義,而是與類定義交織在一塊兒

主構造器的參數直接放在類名以後

代碼示例:

object exam1 {
def main(args: Array[String]): Unit = {
val per: Person = new Person("Bob", 18) //使用主構造器實例化對象
println(per.name + " : " + per.age)
}
}
 
class Person(val name: String, val age: Int) {
//產生私有的name和age,沒有setter
//.....
}

打印結果:

Bob : 18

上述簡短的Person類定義極大簡化了相同功能的Java代碼:

與上例相同功能的Java代碼示例:

class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
 
public String name() {
return this.name;
}
 
public int age() {
return this.age;
}
}

 

Scala主構造器定義的全部語句都會執行

代碼示例:

class Person(val name: String, val age: Int) {
//產生私有的name和age,沒有setter
println("主構造器定義的全部語句都會執行") //每次使用主構造器時都會執行
def description = name + "is" + age + "years old"
}

不一樣類型的主構造器參數對應會生成的字段和方法:

 

若是想讓主構造器變成私有的,能夠像這樣放置private關鍵字:

class Person private (val name: String, val age: Int) {
//......
}

這樣一來,類用戶就必須經過輔助構造器來構造Person對象了.

Scala嵌套類

在Scala中,你幾乎能夠在任何語法結構中內嵌任何語法構造。你能夠在函數中定義函數,在類中定義類。

代碼示例:

class Network {
 
//在Network類中定義類Member
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
    m
}
}

 

考慮有以下兩個網絡:

al chatter = new Network
val myFace = new Network
 

在Scala中,每一個實例都有它本身的Member類(內部類),就和它們有本身的members(內部類)字段同樣。也就是說chatter.Member和myFace.Member是不一樣的兩個類

做用:拿網絡示例來講,你能夠在各自的網絡中添加成員,可是不能跨網添加成員。

代碼示例:

val chatter = new Network
val myFace = new Network
val fred = chatter.join("Fred")
val wilma = chatter.join("wilma")
fred.contacts += wilma //OK
val barney = myFace.join("Barney")
 
//錯誤:不能將一個myFace.Member添加到chatter.Member元素緩衝中
fred.contacts += barney

在嵌套類中,你能夠經過 外部類.this 的方式來訪問外部類的this引用,就像Java那樣。

class Network(val name: String) {
//在Network類中定義類Member
class Member(val name: String) {
//....
def description = name + "inside" + Network.this.name
}
}

若是你以爲須要,也可使用以下語法創建一個指向該引用的別名:

class Network(val name: String) { outer =>
//在Network類中定義類Member
class Member(val name: String) {
//....
def description = name + "inside" + outer.name
}
}

上述語法使得outer變量指向Network.this。對這個變量,你可使用任何合法的名稱。

轉自:https://blog.csdn.net/u011204847/article/details/51105362

相關文章
相關標籤/搜索