Scala學(xué)習(xí)筆記(3)-面向?qū)ο缶幊躺掀?/h1>

本文是對(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é)文章中介紹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,986評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,204評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,964評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,354評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,554評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,918評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,093評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,342評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,755評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,009評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,839評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,107評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容