天天看點

Scala學習筆記3 - 類和對象===類(class)和對象(object)===抽象類和抽象成員

===類(class)和對象(object)

類(class)和構造器:

      類的定義形式如下:

              class MyClass(a: Int, b: Int) {

                     println(a.toString)

               }

      在scala中,類也可以帶有類參數,類參數可以直接在類的主體中使用,沒必要定義字段然後把構造器的參數指派到字段裡,但需要注意的是:類參數僅僅是個參數而已,不是字段,如果你需要在别的地方使用,就必須定義字段。不過還有一種稱為參數化字段的定義形式,可以台灣字段的定義,如下:

               class MyClass(val a: Int, val b: Int) {

                     println(a.toString)

               }

      以上代碼中多了val聲明,作用是在定義類參數的同時定義類字段,不過它們使用相同的名字罷了。類參數同樣可以使用var作字首,還可以使用private、protected、override修飾等等。scala編譯器會收集類參數并創造出帶同樣的參數的類的主構造器,并将類内部任何既不是字段也不是方法定義的代碼編譯至主構造器中。除了主構造器,scala也可以有輔助構造器,輔助構造器的定義形式為def this(…)。每個輔助構造器都以“this(…)”的形式開頭以調用本類中的其他構造器,被調用的構造器可以是主構造器,也可以是源檔案中早于調用構造器定義的其他輔助構造器。其結果是對scala構造器的調用終将導緻對主構造器的調用,是以主構造器是類的唯一入口點。在scala中,隻有主構造器可以調用超類的構造器。

      你可以在類參數清單之前加上private關鍵字,使類的主構造器私有,私有的主構造器隻能被類本身以及伴生對象通路。

      可以使用require方法來為構造器的參數加上先決條件,如果不滿足要求的話,require會抛出異常,阻止對象的建立。

      如果類的主體為空,那麼可以省略花括号。

通路級别控制:

      公有是scala的預設通路級别,是以如果你想使成員公有,就不要指定任何通路修飾符。公有的成員可以在任何地方被通路。

      私有類似于java,即在之前加上private。不同的是,在scala中外部類不可以通路内部類的私有成員。

      保護類似于java,即在之前加上protected。不同的是,在scala中同一個包中的其他類不能通路被保護的成員。

      scala裡的通路修飾符可以通過使用限定詞強調。格式為private[X]或protected[X]的修飾符表示“直到X”的私有或保護,這裡X指代某個所屬的包、類或單例對象。

      scala還有一種比private更嚴格的通路修飾符,即private[this]。被private[this]标記的定義僅能在包含了定義的同一個對象中被通路,這種限制被稱為對象私有。這可以保證成員不被同一個類中的其他對象通路。

      對于私有或者保護通路來說,scala的通路規則給予了伴生對象和類一些特權,伴生對象可以通路所有它的伴生類的私有成員、保護成員,反過來也成立。

成員(類型、字段和方法):

      scala中也可以定義類型成員,類型成員以關鍵字type聲明。通過使用類型成員,你可以為類型定義别名。

      scala裡字段和方法屬于相同的命名空間,scala禁止在同一個類裡用同樣的名稱定義字段和方法,盡管java允許這樣做。

getter和setter:

      在scala中,類的每個非私有的var成員變量都隐含定義了getter和setter方法,但是它們的命名并沒有沿襲java的約定,var變量x的getter方法命名為“x”,它的setter方法命名為“x_=”。你也可以在需要的時候,自行定義相應的getter和setter方法,此時你還可以不定義關聯的字段,自行定義setter的好處之一就是你可以進行指派的合法性檢查。

      如果你将scala字段标注為@BeanProperty時,scala編譯器會自動額外添加符合JavaBeans規範的形如getXxx/setXxx的getter和setter方法。這樣的話,就友善了java與scala的互操作。

樣本類:

      帶有case修飾符的類稱為樣本類(case class),這種修飾符可以讓scala編譯器自動為你的類添加一些句法上的便捷設定,以便用于模式比對,scala編譯器自動添加的句法如下:

      ①  幫你實作一個該類的伴生對象,并在伴生對象中提供apply方法,讓你不用new關鍵字就能構造出相應的對象;

      ②  在伴生對象中提供unapply方法讓模式比對可以工作;

      ③  樣本類參數清單中的所有參數隐式地獲得了val字首,是以它們被當作字段維護;

      ④  添加toString、hashCode、equals、copy的“自然”實作。

封閉類:

      帶有sealed修飾符的類稱為封閉類(sealed class),封閉類除了類定義所在的檔案之外不能再添加任何新的子類。這對于模式比對來說是非常有用的,因為這意味着你僅需要關心你已經知道的子類即可。這還意味你可以獲得更好的編譯器幫助。

單例對象(singleton object):

      scala沒有靜态方法,不過它有類似的特性,叫做單例對象,以object關鍵字定義(注:main函數也應該在object中定義,任何擁有合适簽名的main方法的單例對象都可以用來作為程式的入口點)。定義單例對象并不代表定義了類,是以你不可以使用它來new對象。當單例對象與某個類共享同一個名稱時,它就被稱為這個類的伴生對象(companion object)。類和它的伴生對象必須定義在同一個源檔案裡。類被稱為這個單例對象的伴生類。類和它的伴生對象可以互相通路其私有成員。不與伴生類共享名稱的單例對象被稱為獨立對象(standalone object)。

      apply與update:在scala中,通常使用類似函數調用的文法。當使用小括号傳遞變量給對象時,scala都将其轉換為apply方法的調用,當然前提是這個類型實際定義過apply方法。比如s是一個字元串,那麼s(i)就相當于c++中的s[i]以及java中的s.charAt(i),實際上 s(i) 是 s.apply(i) 的簡寫形式。類似地,BigInt(“123”) 就是 BigInt.apply(“123”) 的簡寫形式,這個語句使用伴生對象BigInt的apply方法産生一個新的BigInt對象,不需要使用new。與此相似的是,當對帶有括号并包含一到若幹參數的變量指派時,編譯器将使用對象的update方法對括号裡的參數(索引值)和等号右邊的對象執行調用,如arr(0) = “hello”将轉換為arr.update(0, “hello”)。

      類和單例對象之間的差别是,單例對象不帶參數,而類可以。因為單例對象不是用new關鍵字執行個體化的,是以沒機會傳遞給它執行個體化參數。單例對象在第一次被通路的時候才會被初始化。當你執行個體化一個對象時,如果使用了new則是用類執行個體化對象,無new則是用伴生對象生成新對象。同時要注意的是:我們可以在類或(單例)對象中嵌套定義其他的類和(單例)對象。

對象相等性:

      與java不同的是,在scala中,“==”和“!=”可以直接用來比較對象的相等性,“==”和“!=”方法會去調用equals方法,是以一般情況下你需要覆寫equals方法。如果要判斷引用是否相等,可以使用eq和ne。

在使用具有哈希結構的容器類庫時,我們需要同時覆寫hashCode和equals方法,但是實作一個正确的hashCode和equals方法是比較困難的一件事情,你需要考慮的問題和細節很多,可以參見java總結中的相應部分。另外,正如樣本類部分所講的那樣,一旦一個類被聲明為樣本類,那麼scala編譯器就會自動添加正确的符合要求的hashCode和equals方法。

===抽象類和抽象成員

      與java相似,scala中abstract聲明的類是抽象類,抽象類不可以被執行個體化。

      在scala中,抽象類和特質中的方法、字段和類型都可以是抽象的。示例如下:

                   trait MyAbstract {

                            type T                                     // 抽象類型

                            def transform(x: T):T            // 抽象方法

                            val initial: T                            // 抽象val

                            var current: T                         // 抽象var

                   }

      抽象方法:抽象方法不需要(也不允許)有abstract修飾符,一個方法隻要是沒有實作(沒有等号或方法體),它就是抽象的。

      抽象類型:scala中的類型成員也可以是抽象的。抽象類型并不是說某個類或特質是抽象的(特質本身就是抽象的),抽象類型永遠都是某個類或特質的成員。

      抽象字段:沒有初始化的val或var成員是抽象的,此時你需要指定其類型。抽象字段有時會扮演類似于超類的參數這樣的角色,這對于特質來說尤其重要,因為特質缺少能夠用來傳遞參數的構造器。是以參數化特質的方式就是通過在子類中實作抽象字段完成。如對于以下特質:

                   trait MyAbstract {

                            valtest: Int

                            println(test)

                            defshow() {

                                      println(test)

                            }

                   }

      你可以使用如下匿名類文法建立繼承自該特質的匿名類的執行個體,如下:

                   new MyAbstract {

                            val test = 1

                   }.show()

      你可以通過以上方式參數化特質,但是你會發現這和“new 類名(參數清單)”參數化一個類執行個體還是有差別的,因為你看到了對于test變量的兩次println(第一次在特質主體中,第二次是由于調用了方法show),輸出了兩個不同的值(第一次是0,第二次是1)。這主要是由于超類會在子類之前進行初始化,而超類抽象成員在子類中的具體實作的初始化是在子類中進行的。為了解決這個問題,你可以使用預初始化字段和懶值。

預初始化字段:

      預初始化字段,可以讓你在初始化超類之前初始化子類的字段。預初始化字段用于對象或有名稱的子類時,形式如下:

                   class B extends {

                            val a = 1

                   } with A

      預初始化字段用于匿名類時,形式如下:

                   new {

                            val a = 1

                   } with A

      需要注意的是:由于預初始化的字段在超類構造器調用之前被初始化,是以它們的初始化器不能引用正在被構造的對象。

懶值:

      加上lazy修飾符的val變量稱為懶值,懶值右側的表達式将直到該懶值第一次被使用的時候才計算。如果懶值的初始化不會産生副作用,那麼懶值定義的順序就不用多加考慮,因為初始化是按需的。

繼續閱讀