本文是對(duì)Scala語(yǔ)言面向?qū)ο缶幊痰膶W(xué)習(xí)總結(jié)(上篇),共包括如下章節(jié):
- 概述
- 類的定義
- 包(package)
- 成員訪問(wèn)控制
- 對(duì)象
- 構(gòu)造函數(shù)
- 小結(jié)
參考資料:
1、如果要了解scala開(kāi)發(fā)環(huán)境的搭建,可參考《Scala學(xué)習(xí)筆記(1)-快速起步》 。
2、如果要了解scala語(yǔ)言的基本語(yǔ)法,可參考《Scala學(xué)習(xí)筆記(2)-基礎(chǔ)語(yǔ)法》。
3、如果要了解scala語(yǔ)言的面向函數(shù)的編程知識(shí),可參考《Scala學(xué)習(xí)筆記(5)-函數(shù)式編程》。
一、概述
Scala是一種純面向?qū)ο蟮木幊陶Z(yǔ)言,其所有的值都是對(duì)象。Scala面向?qū)ο蟮臋C(jī)制和java,c++類似,一些通用的面向?qū)ο蟮奶攸c(diǎn)都支持,也有些自己特有的特性。下面章節(jié)會(huì)詳細(xì)介紹。
二、類的定義
(一)基本概念
類是面向?qū)ο缶幊讨械幕締卧愂莿?chuàng)建對(duì)象的藍(lán)圖(或模板)。同java一樣,scala中的類通過(guò)關(guān)鍵字class定義,對(duì)象使用new關(guān)鍵字創(chuàng)建。
定義一個(gè)最簡(jiǎn)單的類,只需要關(guān)鍵字class和類名。如下面例子:
class User
雖然scala不要求類名的首字母大寫,但建議大寫。定義了類之后,就可以創(chuàng)建對(duì)象,同java一樣,創(chuàng)建對(duì)象使用new關(guān)鍵字,如下面代碼:
var user = new User
println(user)
可以看出,User類沒(méi)有顯示的定義構(gòu)造函數(shù),有一個(gè)默認(rèn)的空的構(gòu)造函數(shù)。創(chuàng)建對(duì)象時(shí),可以省略()。也就是說(shuō),var user = new User() 也是合法的。
面向?qū)ο缶幊痰淖罨A(chǔ)特征就是封裝,也就是將數(shù)據(jù)和操作放到一個(gè)類中,我們?cè)诿嫦驅(qū)ο蟮脑O(shè)計(jì)中,最重要的工作就是根據(jù)業(yè)務(wù)模型來(lái)設(shè)計(jì)類,將數(shù)據(jù)和匹配的操作封裝到一個(gè)類中。其中的數(shù)據(jù)和操作都稱為類的成員,而數(shù)據(jù)一般稱為類的成員變量,操作稱為類的成員方法。
下面我們來(lái)看看scala中如何定義成員變量和方法。
(二)成員變量
我們來(lái)看一個(gè)簡(jiǎn)單例子:
class Person{
val name:String=""
var age:Int=20
}
上面定義了一個(gè)Person類,類中有2個(gè)成員變量,其中name變量用關(guān)鍵字val來(lái)定義,age變量用關(guān)鍵字var來(lái)定義的。對(duì)于scala類中的成員變量聲明,有如下特點(diǎn):
1、var和val區(qū)別是,val定義的成員變量的值后續(xù)不能再改變。
2、成員變量聲明需要要初始化(否則就要定義為抽象類,關(guān)于抽象類的概念后面章節(jié)會(huì)介紹),如上面例子。如果變量有初始值,就可以省去類型定義,因?yàn)閟cala可以自動(dòng)推斷。如下面定義類。
class Person{
val name=""
var age=20
}
3、對(duì)于var定義的成員變量,可以用占位符_來(lái)代替具體的值,如下面定義。
class Person{
val name=""
var age:Int = _
}
用占位符_來(lái)替換實(shí)際的值進(jìn)行初始化,則該變量的值就是該類型的默認(rèn)值,比如對(duì)于Int型的,就是0;對(duì)于引用型的(AnyRef子類的),如String,則默認(rèn)值為null。
除了在類中定義成員變量外,還可以通過(guò)構(gòu)造函數(shù)來(lái)定義成員變量,這個(gè)在后面章節(jié)會(huì)介紹。
(三)成員方法
在scala中,使用def關(guān)鍵字定義方法。 def后跟方法名,參數(shù)列表,返回值類型和方法體。語(yǔ)法格式如:
def 函數(shù)名(參數(shù)列表):返回值類型 = {
方法體代碼
}
下面看一個(gè)方法定義的例子:
def add(first:Int, second:Int):Int = {
val sum = first+second
return sum
}
上面定義了一個(gè)標(biāo)準(zhǔn)方法。
相比java中的方法,scala的方法定義比較靈活,有很多需要注意的細(xì)節(jié),下面一一介紹:
1、scala的參數(shù)是不可變的,即不能給它重新賦值。
2、return關(guān)鍵字可省略,在scala中,所有語(yǔ)句都是bu表達(dá)式,表達(dá)式總是會(huì)返回一個(gè)值。省略了result, 方法體內(nèi)最后一個(gè)被執(zhí)行的語(yǔ)句作為表達(dá)式所返回的值就是方法的返回值。如:
def add(first:Int, second:Int) :Int= {
val sum = first+second
sum*2
}
3、如果方法體中只有一個(gè)語(yǔ)句,則可省略{},如:
def add(first:Int, second:Int) :Int = first+second
4、如果可以通過(guò)方法體最后一個(gè)表達(dá)式推斷出返回值類型,則方法的返回值類型聲明可以省略。如:
def add(first:Int, second:Int) = first+second
需要注意的是:如果方法中出現(xiàn)了return語(yǔ)句,則方法聲明必須顯示的定義返回值類型,如下面的代碼會(huì)編譯報(bào)錯(cuò):
def add(first:Int, second:Int) = first+second
如果省略了返回值類型,則也可以 = ,但不能省略{}。如下面語(yǔ)法也是正確的:
def add(first:Int, second:Int) = {
if(first>second)
return first+second
else
return first-second
}
5、Unit類型
在c/c++及java中,如果方法沒(méi)有返回值,則方法的返回值聲明為void。我們看看同樣代碼在scala中是什么情況。如下面方法定義:
def hello() = {
println("hello,world")
}
如果按照java語(yǔ)言來(lái)看,這個(gè)方法是沒(méi)有返回值的。我們來(lái)測(cè)試下,在scala交互式程序中定義和調(diào)用該方法,執(zhí)行過(guò)程如下:
scala> def hello() = {
| println("hello,world")
| }
hello: ()Unit
scala> val re = hello()
hello,world
re: Unit = ()
scala> println(re)
()
對(duì)比前面例子的執(zhí)行輸出。根據(jù)上面的執(zhí)行輸出,可以看出hello方法是有返回值的,返回值類型為 Unit ,具體的值為()。Unit是scala中的一種數(shù)據(jù)類型,表示沒(méi)有值,其有唯一的實(shí)例()。在scala中,方法如果沒(méi)有返回具體的值,那返回的類型就設(shè)置為Unit類型。
(四)不帶()的方法定義
如果一個(gè)方法沒(méi)有參數(shù),則方法名后面的()可以省略,如下面是正確的代碼:
class Demo{
def name = "tom"
}
上面代碼中的Demo類中定義了一個(gè)方法name,注意是方法,不是變量。這時(shí)name方法沒(méi)有帶()。對(duì)于不帶()的方法,在調(diào)用時(shí)也不能帶(),如name()這樣調(diào)用是錯(cuò)誤的,只能name這樣調(diào)用。
如果 def name()= "tom"這樣定義時(shí)帶(),則調(diào)用時(shí)既可以帶(),也可以不帶()??吹竭@里,會(huì)覺(jué)得scala的語(yǔ)法棉花糖太多了。
不帶()的方法,還有一個(gè)特點(diǎn)時(shí),子類可以重寫該方法,不僅可以用方法去重寫,也可以用變量去重寫。如下面代碼:
class Child extends Demo{
override val name="jack"
}
上面Demo類的子類Child重寫了name方法,但重寫的時(shí)候定義的是變量,而不是方法(當(dāng)然重寫成方法也是沒(méi)問(wèn)題的)。
Scala作者建議,如果一個(gè)方法在邏輯上表達(dá)一種屬性的返回值,那么在定義方法時(shí)盡量使用不帶括號(hào)的寫法,因?yàn)檫@樣看上去更像一個(gè)類的屬性,而不像一個(gè)方法。需要注意的是,由于不帶括號(hào)的方法比帶括號(hào)的方法在使用上更嚴(yán)格,因此將來(lái)要把一個(gè)帶括號(hào)的方法定義改為不帶括號(hào)的方法定義就比較麻煩,因?yàn)樾枰葘⑺袔Юㄌ?hào)的方法調(diào)用,比如name(), 統(tǒng)統(tǒng)改為不帶括號(hào)的。
(五)方法參數(shù)的默認(rèn)值
scala的方法參數(shù)支持設(shè)置默認(rèn)值,對(duì)于有默認(rèn)值的參數(shù),調(diào)用方法時(shí)如果不傳入?yún)?shù)值,則使用默認(rèn)值。如下面例子:
object Hello {
def main(args: Array[String]){
test()
test("jack")
test("jack",20)
}
def test(name:String="tom",age:Int=3)={
println(name,age)
}
}
上面代碼中定義的test方法,有兩個(gè)缺省值的參數(shù)。main方法中的三種調(diào)用方式都是正確的。
如果只有部分參數(shù)有默認(rèn)值,則沒(méi)有有默認(rèn)值的參數(shù)建議放在參數(shù)列表的前面,否則在不設(shè)置有默認(rèn)值參數(shù)時(shí)需要以帶參數(shù)名的方式來(lái)調(diào)用方法。如下面例子:
object Hello {
def main(args: Array[String]){
test("jack",20)
test(age=20)
}
def test(name:String="tom",age:Int):Unit={
println(name,age)
}
}
scala的方法支持在調(diào)用時(shí)帶參數(shù)名調(diào)用,這樣傳入?yún)?shù)的順序就不必要按照方法定義參數(shù)的順序傳入。
(六)方法返回多個(gè)值
在Java/c++中我們知道,方法的返回值只能有一個(gè)值,如果我們需要返回多個(gè)值,只能返回一個(gè)對(duì)象(對(duì)象中有多個(gè)成員變量),或者返回一個(gè)數(shù)組,或集合對(duì)象。但在scala中,利用元組類型,yuanzu可以直接返回多個(gè)值,讓代碼編寫起來(lái)更加簡(jiǎn)單,我們先看一個(gè)例子:
object Hello {
def main(args: Array[String]){
var (name,age) = test
println(name)
println(age)
}
def test={
("tom",12)
}
}
上面例子代碼中,test方法返回一個(gè)元組類型的值,在main方法中通過(guò) var (name,age) = test 來(lái)提取test方法返回的元組中的所有元素。
(七)this關(guān)鍵字
方法是類的一部分,在方法的實(shí)現(xiàn)代碼中,可以直接訪問(wèn)本類中的其它成員。this關(guān)鍵字代表對(duì)象本身,可以通過(guò)this來(lái)引用成員,這與java中的this關(guān)鍵字作用是一樣的。
當(dāng)成員名和局部變量不沖突時(shí),可以省去this關(guān)鍵字。如下面例子:
class Person{
var name:String="tom"
def show={
println(name)
}
def setName(name:String)={
this.name = name;
}
}
上面代碼中show方法直接使用了成員變量name,省去了this關(guān)鍵字。而setName方法中因?yàn)榇嬖谕木植孔兞浚孕枰ㄟ^(guò)this關(guān)鍵字來(lái)引用成員變量。
(八)方法重載
同Java一樣,scala也支持方法重載,即一個(gè)類中存在多個(gè)同名的方法,但參數(shù)不同。調(diào)用時(shí),scala會(huì)根據(jù)傳入的參數(shù)不同調(diào)用匹配的方法。如下面例子:
class Person{
var name:String="tom"
var age:Int=10
def setData(name:String)={
this.name = name;
}
def setData(name:String,age:Int)={
this.name = name;
this.age = age;
}
}
上面代碼中有兩個(gè)同名的方法setData,但它們的參數(shù)列表不同。
三、對(duì)象
(一)單例對(duì)象
在java中,在類中可以通過(guò)static關(guān)鍵字定義靜態(tài)的成員變量和方法,靜態(tài)成員是歸屬類的,與對(duì)象無(wú)關(guān)??梢灾苯油ㄟ^(guò)類名來(lái)訪問(wèn)。
在scala中,并不支持靜態(tài)成員。但是scala可以通過(guò)定義單例對(duì)象來(lái)實(shí)現(xiàn)同樣的功能。這里的單例對(duì)象不是如java中是一種設(shè)計(jì)模式,在scala中是一種具體的語(yǔ)法。
定義單例對(duì)象不使用class關(guān)鍵字,而是使用object關(guān)鍵字。我們先看一個(gè)簡(jiǎn)單例子:
object User{
var name="tom"
}
val name = User.name
User.name = "jack"
上面代碼使用object關(guān)鍵字定義了一個(gè)單例對(duì)象,然后直接通過(guò)對(duì)象名即可訪問(wèn)對(duì)象中的成員,類似Java中的靜態(tài)變量。在scala中,單例對(duì)象不是一個(gè)類,它就是一個(gè)對(duì)象(類似在java中自己實(shí)現(xiàn)的一個(gè)單例),只不過(guò)不需要由自己創(chuàng)建,而是由scala運(yùn)行環(huán)境幫創(chuàng)建,并且在整個(gè)運(yùn)行過(guò)程中只有一份,我們也不能通過(guò)new關(guān)鍵字來(lái)針對(duì)單例對(duì)象再創(chuàng)建一個(gè)對(duì)象。
在scala中,一個(gè)單獨(dú)可執(zhí)行的scala程序,至少需要一個(gè)單例對(duì)象,且該單例對(duì)象中有一個(gè) def main(args: Array[String]) 這樣的main方法,main方法就是程序的入口,類似Java中的靜態(tài)main方法作為java程序的入口。
(二)伴生類與伴生對(duì)象
在Java中,一個(gè)類中即可以定義靜態(tài)成員,也可以有實(shí)例成員。但在scala中,把這兩個(gè)分開(kāi)了。靜態(tài)成員的功能由單例對(duì)象來(lái)實(shí)現(xiàn)。
在scala中,可以定義同名的單例對(duì)象和類。如果存在同名的單例對(duì)象和類,則該對(duì)象和類之間就會(huì)產(chǎn)生關(guān)系。在scala中,會(huì)把單例對(duì)象稱為同名類的伴生對(duì)象,會(huì)把類稱為同名單例對(duì)象的伴生類。如果一個(gè)單例對(duì)象沒(méi)有同名的伴生類,我們一般稱這個(gè)對(duì)象為孤立對(duì)象,反之稱為伴生對(duì)象。
舉個(gè)例子,假設(shè)我們定義了object(單例對(duì)象)Person和class(類)Person,則 object Person稱為class Person的伴生對(duì)象,而class Person則稱為object Person的伴生類。
伴生類和伴生對(duì)象有如下幾個(gè)注意的地方:
1、每個(gè)類都可以有伴生對(duì)象,伴生類與伴生對(duì)象寫在同一個(gè)文件中;
2、伴生類中和伴生對(duì)象可互相訪問(wèn)其private字段。關(guān)于成員訪問(wèn)控制,在下一章節(jié)會(huì)詳細(xì)介紹。
四、包(package)
(一)基本概念
scala中的package功能,同Java中的包、c++中的命名空間一樣,用于大型工程代碼的組織,同時(shí)解決命名沖突的問(wèn)題。
scala中的package與java中的package很類似,如下面例子:
package com.demo
class Test{
def show ={
println("test")
}
}
上面定義的Test類,位于com.demo包下,包路徑加類名才是類的唯一標(biāo)識(shí)。其它包中的類使用Test類時(shí),可以使用com.demo.Test來(lái)引用,如:
object Hello {
def main(args: Array[String]){
var test = new com.demo.Test
test.show
}
}
顯然,如果在一個(gè)文件中多次使用到Test類,com.demo.Test這樣的寫法比較臃腫。同Java一樣,scala支持import語(yǔ)句來(lái)簡(jiǎn)化代碼的編寫。如下面例子:
import com.demo.Test
object Hello {
def main(args: Array[String]){
var test = new Test
test.show
}
}
上面代碼通過(guò)import com.demo.Test導(dǎo)入了指定的類,這樣我們就可以使用Test類時(shí)不用帶包路徑。
如果我們希望將一個(gè)package中的所有類都導(dǎo)入,而不需要顯示的導(dǎo)入每個(gè)類,在java中用來(lái)代替具體的類名,如import com.demo. ,在scala中,是用_代替*。如下面例子:
import com.demo._
object Hello {
def main(args: Array[String]){
var test = new Test
test.show
}
}
對(duì)于單例對(duì)象,我們還可以將對(duì)象中的成員導(dǎo)入,這樣使用時(shí)可以直接使用成員名,不用寫單例對(duì)象名。類似java中的靜態(tài)導(dǎo)入。如下面例子:
package com.demo
object Test{
def show ={
println("test")
}
}
上面代碼定義了Test單例對(duì)象,我們希望直接使用其中的show方法,而不需要Test.show這樣使用,方法如下:
import com.demo.Test.show
object Hello {
def main(args: Array[String]){
show
}
}
同樣,我們可以使用import com.demo.Test._ 來(lái)導(dǎo)入Test對(duì)象中的所有成員,而不需要一個(gè)個(gè)的導(dǎo)入具體的成員。
(二)import的高級(jí)特性
import除了最常見(jiàn)的使用外(如上面例子),還有一些特殊的使用場(chǎng)景。
1、缺省導(dǎo)入
對(duì)于scala包中的所有類,不需要顯示的import,scala會(huì)自動(dòng)導(dǎo)入。比如我們用到的基礎(chǔ)數(shù)據(jù)類型 Boolean , Int類型都位于scala包中,但我們使用時(shí)并沒(méi)有顯示導(dǎo)入。
對(duì)于scala.Predef(是一個(gè)單例對(duì)象)中的所有成員,scala會(huì)自動(dòng)導(dǎo)入,不需要顯示導(dǎo)入。比如String類型,就是scala.Predef中的一個(gè)成員,我們可以直接使用。
2、重命名
如果同時(shí)需要使用兩個(gè)不同包中的同名的類,普通的import方式就會(huì)存在沖突。比如:
import java.util.HashMap
import scala.collection.mutable.HashMap
上面兩個(gè)語(yǔ)句都導(dǎo)入了相同名字的HashMap,如果我們代碼中直接使用HashMap,就會(huì)報(bào)錯(cuò)。這時(shí),我們當(dāng)然可以使用全路徑來(lái)區(qū)分。但scala提供了一種重命名的方式,舉例如下:
import java.util.{ HashMap => JavaHashMap }
import scala.collection.mutable.HashMap
object Hello {
def main(args: Array[String]){
var map1:HashMap[String,String]=null
var map2:JavaHashMap[String,String]=null
}
}
上面代碼中第一個(gè)import語(yǔ)句將 HashMap 重命名為JavaHashMap ,這樣使用時(shí)就不會(huì)沖突了。
五、成員訪問(wèn)控制
(一)回顧下Java中的方式
成員訪問(wèn)控制是指訪問(wèn)類成員時(shí)的權(quán)限控制。我們先回顧下Java中的成員訪問(wèn)控制,在java中,對(duì)類成員的訪問(wèn)有4種級(jí)別的控制,分別是:
private:私有成員,表示被private關(guān)鍵字修飾的成員只能被同一個(gè)類中的方法訪問(wèn)。
默認(rèn)方式:包內(nèi)成員,表示沒(méi)有加任何修飾符的成員可以被同一個(gè)包(package)中的任何類的任何方法訪問(wèn)。
protected:保護(hù)成員,表示被protected關(guān)鍵字修飾的成員可以被包內(nèi)訪問(wèn),也可以被子類訪問(wèn)。
public:公開(kāi)成員,表示被public關(guān)鍵字修飾的成員可以在任何地方被訪問(wèn)。
可以看出,java的上述4種級(jí)別的控制權(quán)限嚴(yán)厲程度從高到低,private最嚴(yán)格,只能被本類中方法訪問(wèn);public最寬松,可以在任何地方被訪問(wèn)。private和public也是最常用的兩種權(quán)限控制級(jí)別。
我們?cè)賮?lái)看scala的訪問(wèn)權(quán)限控制,與java類似,但也有些不同的地方。
(二)private方式
由private關(guān)鍵字修飾的類成員只能被該類的成員以及該類的伴生對(duì)象(后面會(huì)介紹)訪問(wèn),這點(diǎn)同java的prviate級(jí)別一樣。如下面例子:
class Demo{
private val value="hello"
def show{
println(value)
}
}
object Hello {
def main(args: Array[String]){
var demo = new Demo
demo.value=10 //會(huì)報(bào)編譯錯(cuò)誤
}
}
上面例子中,Demo類中定義了一個(gè)private的成員變量value,value可以被類中的show方法訪問(wèn)。但不能被外部類及非伴生單例對(duì)象訪問(wèn),如在Hello單例對(duì)象的main方法中訪問(wèn),會(huì)報(bào)編譯錯(cuò)誤。
(三)protected方式
由protected關(guān)鍵字修飾的成員只能在該類及其子類中訪問(wèn),外部不能訪問(wèn),類似java中的protected權(quán)限控制。
(四)默認(rèn)方式
沒(méi)有加任何關(guān)鍵字修飾的成員,可以在任何地方被使用,類似java中的public關(guān)鍵字,這點(diǎn)與java的默認(rèn)方式完全不同。我們前面的很多例子都是沒(méi)加任何修飾符。
(五)private[this]方式
在scala中還存在使用private[this]修飾的成員,如下面代碼:
class Demo{
private[this] val value="hello"
}
被private[this]修飾的成員只能在類的內(nèi)部使用??吹竭@里可能覺(jué)得奇怪,這與單獨(dú)的prviate關(guān)鍵字修飾有啥區(qū)別呢?
prvivate[this]修飾和prviate修飾非常類似,它們之間的最主要區(qū)別在于伴生對(duì)象的訪問(wèn)權(quán)限控制。
class Demo{
private var name="tom"
private[this] var value="hello"
}
object Demo{
def show{
val demo = new Demo
demo.name = "jack"
demo.value = "world" //會(huì)報(bào)編譯錯(cuò)誤
}
}
上面代碼中,Demo單例對(duì)象是Demo類的伴生對(duì)象,在單例對(duì)象中,可以訪問(wèn)Demo類(即其伴生類)的private成員,但不能訪問(wèn)private[this]成員,可以看出,prviate[this]比private的權(quán)限控制更嚴(yán)格。
同樣,我們看下伴生類中使用伴生對(duì)象的例子:
object Demo{
private var name="tom"
private[this] var value="hello"
}
class Demo{
def show{
Demo.name = "jack"
Demo.value = "world" //編譯報(bào)錯(cuò)
}
}
在伴生類中,訪問(wèn)伴生對(duì)象,可以直接訪問(wèn)private成員,但不能訪問(wèn)private[this]成員,會(huì)報(bào)編譯錯(cuò)誤。
說(shuō)明:scala還有包作用域的訪問(wèn)控制方式,但需要改變packge和類的編寫方式,不太常用,這里就不作介紹。
六、構(gòu)造函數(shù)
(一)基本概念
同java一樣,scala類也有構(gòu)造函數(shù),創(chuàng)建對(duì)象時(shí)會(huì)調(diào)用構(gòu)造函數(shù)。我們先回顧下java的構(gòu)造函數(shù)特點(diǎn),一個(gè)java類可以顯示的定義0個(gè)或多個(gè)構(gòu)造函數(shù),有如下特點(diǎn):
1)構(gòu)造函數(shù)名必須與類名一樣,無(wú)返回值
2)如果沒(méi)有顯示定義構(gòu)造函數(shù),則java會(huì)自動(dòng)幫創(chuàng)建一個(gè)默認(rèn)的構(gòu)造函數(shù)(沒(méi)有參數(shù))
3)如果定義了構(gòu)造函數(shù),則java不會(huì)幫創(chuàng)建默認(rèn)的構(gòu)造函數(shù)
4)如果定義了多個(gè)構(gòu)造函數(shù),這些構(gòu)造函數(shù)屬于重載,需要滿足方法重載的要求(即參數(shù)列表不能完全一致),這些構(gòu)造函數(shù)無(wú)主次之分
5)創(chuàng)建對(duì)象時(shí),java根據(jù)所調(diào)用的構(gòu)造函數(shù)傳入的參數(shù)來(lái)決定調(diào)用哪個(gè)構(gòu)造函數(shù)
scala的構(gòu)造函數(shù)與java類似,但也有些差別,最主要的差別是:scala的構(gòu)造函數(shù)分為主構(gòu)造函數(shù)和輔助構(gòu)造函數(shù),有且只有一個(gè)主構(gòu)造函數(shù),可以有0個(gè)或多個(gè)輔構(gòu)造函數(shù)。
(二)主構(gòu)造函數(shù)介紹
下面我們來(lái)看下主構(gòu)造函數(shù)的定義。在Scala中,每個(gè)類都有主構(gòu)造函數(shù)。但在前面的例子中,我們定義的Person類中沒(méi)有定義主構(gòu)造函數(shù)。這如java一樣,這時(shí)由scala自動(dòng)幫生成了一個(gè)默認(rèn)的不帶參數(shù)的主構(gòu)造函數(shù)。
在scala中顯示定義帶參數(shù)的主構(gòu)造函數(shù),不是去單獨(dú)定義一個(gè)構(gòu)造函數(shù)方法,而是和類的定義交織在一起。如下面例子:
class Person(var name:String,var age:Int){
}
下面我們來(lái)創(chuàng)建和使用Person對(duì)象,如下面例子:
scala> val per = new Person("tom",20)
per: Person = Person@57b33c29
scala> val name = per.name
name: String = tom
scala> per.name="jack"
per.name: String = jack
通過(guò)上面的例子代碼可以看出,我們要定義帶參數(shù)的主構(gòu)造函數(shù),只需在class后面的類名后面加上相應(yīng)的參數(shù),而且這些參數(shù)自動(dòng)會(huì)變成類的成員變量(我們不能再在類中定義同名的成員變量了)。這比在java中完成同樣的功能所需代碼簡(jiǎn)潔很多。
這里還有一個(gè)問(wèn)題,我們知道,在Java中我們可以在構(gòu)造函數(shù)中編寫代碼。但scala的主構(gòu)造函數(shù)沒(méi)有對(duì)應(yīng)的方法,如果我們希望在調(diào)用主構(gòu)造函數(shù)創(chuàng)建對(duì)象時(shí)也能執(zhí)行一些初始化操作,那代碼放在哪里呢? 在scala中,我們可以在類的主體(即{})中添加任意的代碼,這樣在創(chuàng)建對(duì)象時(shí),從開(kāi)始到結(jié)尾所有代碼都會(huì)被執(zhí)行。如下面例子:
class Person{
val name=""
var age:Int = _
println("i am created")
age=12
println("name="+name+",age="+age)
}
下面我們創(chuàng)建一個(gè)對(duì)象,在scala命令行中執(zhí)行會(huì)看到如下結(jié)果。
scala> var obj = new Person
i am created
name=,age=12
obj: Person = Person@2f2dc407
scala> val age = obj.age
age: Int = 12
可以看出,類中的這些代碼都被執(zhí)行了。
我們?cè)诼暶髦鳂?gòu)造函數(shù)時(shí),同普通的方法一樣,可以給參數(shù)設(shè)置默認(rèn)值,這樣在創(chuàng)建對(duì)象時(shí)可以不傳入?yún)?shù)值。如下面例子:
scala> class Person(var name:String="tom",var age:Int=20){ }
defined class Person
scala> val per1 = new Person
per1: Person = Person@503fa290
scala> println(per1.name+"="+per1.age)
tom=20
scala> val per2 = new Person("jack",10)
per2: Person = Person@7beba1a8
scala> println(per2.name+"="+per2.age)
jack=10
上面代碼給主構(gòu)造函數(shù)的參數(shù)設(shè)置了默認(rèn)值,這時(shí)我們?cè)趧?chuàng)建對(duì)象時(shí)可以不傳入?yún)?shù)值,成員變量的初始值就是設(shè)置的默認(rèn)值。當(dāng)然我們也可以在創(chuàng)建對(duì)象時(shí)傳入值,這樣成員變量的值就是創(chuàng)建對(duì)象時(shí)傳入的值。
為主構(gòu)造函數(shù)設(shè)置參數(shù)默認(rèn)值,如果有多個(gè)參數(shù),也可以只給部分參數(shù)設(shè)定默認(rèn)值。如:
class Person(var name:String,var age:Int=12){ }
這樣創(chuàng)建對(duì)象可以不傳入有默認(rèn)值的參數(shù),如
new Person("jack")
當(dāng)然,如果是前面參數(shù)有默認(rèn)值,則創(chuàng)建對(duì)象時(shí)需要指定參數(shù)名,如:
class Person(var name:String="tom",var age:Int){ }
new Person(age=12)
(三)輔助構(gòu)造函數(shù)
上面我們介紹了scala類的主構(gòu)造函數(shù),主構(gòu)造函數(shù)有且只有一個(gè)。對(duì)于scala類,還可以顯示的定義0個(gè)或多個(gè)輔助構(gòu)造函數(shù)。
對(duì)于輔助構(gòu)造函數(shù)有如下要求:
1)以this作為方法名定義(不同于java中的以類名定義)
2)每一個(gè)輔助構(gòu)造函數(shù)的第一行代碼必須是對(duì)主構(gòu)造函數(shù)或其它的輔助構(gòu)造函數(shù)的調(diào)用,通過(guò)this方法調(diào)用。這意味著主構(gòu)造函數(shù)一定會(huì)被輔構(gòu)造函數(shù)調(diào)用(直接或間接)。
3)輔助構(gòu)造函數(shù)的參數(shù)不能加var或val標(biāo)記。
4)輔助構(gòu)造函數(shù)的參數(shù)不會(huì)成為成員變量,其作用是用來(lái)給成員變量傳值的。
class Person{
var name=""
var age:Int =0
def this(name:String)={
this()
this.name = name
}
def this(name:String,age:Int)={
this(name)
this.age = age
}
}
創(chuàng)建對(duì)象時(shí),scala會(huì)自動(dòng)根據(jù)傳入的參數(shù)來(lái)調(diào)用相匹配度的主構(gòu)造函數(shù)或輔助構(gòu)造函數(shù)。所以scala的各個(gè)構(gòu)造函數(shù)之間要符合重載的要求。下面的創(chuàng)建對(duì)象都是正確的:
scala> var p = new Person
p: Person = Person@54972f9a
scala> var p = new Person("A")
p: Person = Person@525aadf2
scala> var p = new Person("A",12)
p: Person = Person@4a577b99
(四)主構(gòu)造函數(shù)中的成員訪問(wèn)控制
前面介紹主構(gòu)造函數(shù)我們已經(jīng)知道,可以通過(guò)主構(gòu)造函數(shù)方便的為類設(shè)置成員變量,如下面例子:
class Person( var name:String)
上面代碼定義了Person,通過(guò)主構(gòu)造函數(shù)聲明了一個(gè)成員變量name。這種方式下,name的權(quán)限都是默認(rèn)方式,即公開(kāi)方式。
如果我們希望設(shè)置name的訪問(wèn)權(quán)限為private或protected,則只需在var關(guān)鍵字前面加上相應(yīng)的關(guān)鍵字即可,如:
class Person( private var name:String)
如果我們希望name的訪問(wèn)權(quán)限為private[this]方式,即完全私有的,則一種方式是加上private[this]修飾符,還有一種更簡(jiǎn)單的方式是不使用var或val關(guān)鍵字定義,這樣聲明的成員變量就是private[this]的方式,如:
class Person(name:String){
def show{
println(name)
}
}
(五)構(gòu)造函數(shù)的私有化
默認(rèn)情況下,構(gòu)造函數(shù)都是公有化的,在創(chuàng)建對(duì)象時(shí)可以直接調(diào)用。如果我們希望某個(gè)構(gòu)造函數(shù)私有化,只在類的內(nèi)部使用而不對(duì)外開(kāi)放。這時(shí)只需加上private關(guān)鍵字修飾符。
1、對(duì)于主構(gòu)造函數(shù),private關(guān)鍵字加在類名和()之間。
2、對(duì)于輔助構(gòu)造函數(shù),private關(guān)鍵字加在def之前即可。
七、小結(jié)
本文對(duì)scala面向?qū)ο蟮木幊痰幕靖拍詈吞攸c(diǎn)進(jìn)行了總結(jié),涉及類的定義、單例對(duì)象、成員訪問(wèn)控制、構(gòu)造函數(shù)等內(nèi)容。面向?qū)ο蟮木幊踢€有很多重要特性,如繼承、多態(tài)等,這些會(huì)在下篇總結(jié)文章中介紹。