您好,登錄后才能下訂單哦!
這篇文章主要講解了“Scala面向對象編程的方法是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Scala面向對象編程的方法是什么”吧!
Scala 和Java, Python, Ruby, Smalltalk 以及其它類似語言一樣,是一種面向對象語言。如果你來自Java 的世界,你會發(fā)現(xiàn)對Java 對象模型限制的一些顯著改進。
我們假設你先前有過面向對象編程(OOP)的經驗,所以我們不會討論那些最基本的原理,盡管有一些公用術語和概念會在詞匯表中提及。
類和對象的基礎
讓我們來回顧一下Scala OOP 的術語。
注意
我們在前面看到Scala 有聲明對象的概念,我們會在“類和對象:狀態(tài)哪里去了?”章節(jié)來討論它們。我們會使用術語實例來稱呼一個類的實例,意思是類的對象或者實例,用來避免兩者之間的混淆。
類可以用關鍵字class 來聲明。我們會在后面看到也可以加上一些其它的關鍵字,例如用final 來防止創(chuàng)建繼承類,以及用abstract 表示這個類不能被實例化,這通常是因為它包含或者繼承了沒有具體定義的成員聲明。
一個實例可以用this 關鍵字來引用自己,這一點和Java 及其類似語言一樣。
遵循Scala 的約定,我們使用術語方法(method)來指代實例的函數(function)。有一些面向對象語言使用術語成員函數(member function)。方法定義由def 關鍵字開始。
和Java 一樣,但是和Ruby,Python 有所區(qū)別,Scala 允許重載方法。兩個或以上的方法可以有同樣的名字,只要它們的完整簽名是唯一的。簽名包含了類型名字,參數列表及其類型,以及方法的返回值。
不過,這里有一個由類型消除引起的例外,這是一個JVM 的特性,但是被Scala 在JVM 和.NET 平臺上所利用從而最小化兼容問題。假設兩個方法其它方面都一樣,只是其中一個接受List[String] 參數,而另外一個接受List[Int] 參數,如下所示。
// code-examples/BasicOOP/type-erasure-wont-compile.scala // WON'T COMPILE object Foo { def bar(list: List[String]) = list.toString def bar(list: List[Int]) = list.size.toString }
你會在第二個方法處得到一個編譯錯誤,因為這兩個方法在類型消除后擁有一樣的簽名。
警告
Scala 解釋器會讓你輸入這兩個方法。它簡單地拋棄了第一個版本。然而,如果你嘗試用:load 文件命令去加載上面的那個例子,你會得到一樣的錯誤。
同樣是約定,我們使用術語字段(field)來指代實例的變量。其它語言則通常使用術語屬性(attribute),例如Ruby。注意,一個實例的狀態(tài)就是該實例的字段所呈現(xiàn)的值的聯(lián)合。
正如我們在《Scala編程指南 更少的字更多的事》中的“變量聲明”章節(jié)中所討論的,只讀的(“值”)字段用val 關鍵字來聲明,可讀寫字段則用var 關鍵字來聲明。
Scala 也允許在類中聲明類型,正如我們在《Scala編程指南 更少的字更多的事》中的“抽象類型和參數化類型”章節(jié)中所見。
我們一般使用術語成員(member)來指代字段,方法或者類型。注意,字段和方法成員(除開類型成員)共享一樣的名稱空間,這一點和Java 不一樣。我們會在《Scala 高級面向對象編程》的“當方法和字段存取器無法區(qū)分時:唯一存取的原則”章節(jié)來更多的討論這一點。
最后,引用類型的實例可以用new 關鍵字創(chuàng)建,和Java,C# 一樣。注意,你在使用默認構造函數時可以不用寫括號(例如,沒有參數的構造函數)。你某些情況下,字面值可以被用來替代new。例如val name = "Programming Scala" 等效于val name = new String("Programming Scala")。
值類型的實例(例如Int,Double 等),和Java 這樣的語言中的元類型相對應,永遠都用字面值來創(chuàng)建。例如1,3.14 等。實際上,這些類型沒有公有構造函數,所以像val i = new Int(1) 這樣的表達式是不能編譯的。
我們會在“Scala 類型結構”章節(jié)討論引用類型和值類型的區(qū)別。
父類
Scala 支持單繼承,不支持多繼承。一個子(或繼承的)類只可以有一個父類(基類)。唯一的例外是Scala 類層級結構中的根,Any,沒有父類。
我們已經見過幾個父類和子類的例子了。這里是我們在《Scala編程指南 更少的字更多的事》中的“抽象類型和參數化類型”章節(jié)里看到的第一個例子的片段。
// code-examples/TypeLessDoMore/abstract-types-script.scala import java.io._ abstract class BulkReader { // ... } class StringBulkReader(val source: String) extends BulkReader { // ... } class FileBulkReader(val source: File) extends BulkReader { // ... }
和在Java 一樣,關鍵字extends 指明了父類,在這里就是BulkReader。在Scala 中,extends 也會在一個類把一個trait 作為父親繼承的時候使用(即使當它用with 關鍵字混入其它traits 的時候也是一樣)。而且,extends 也在一個trait 是另外一個trait 或類的繼承者的時候使用。是的,traits 可以繼承自類。
如果你不繼承任何父類,默認的父親是AnyRef,Any 的一個直接子類。(我們會在“Scala 類型層級結構”章節(jié)中討論Any 和AnyRef 的區(qū)別。)
Scala 構造函數
Scala 可以區(qū)分主構造函數和0個或多個輔助構造函數。在Scala 里,類的整個主體就是主構造函數。構造函數所需要的任何參數被列于類名之后。我們已經看到過很多例子了,比如我們在《第4章 - Traits》中使用的ButtonWithCallbacks 例子。
// code-examples/Traits/ui/button-callbacks.scala package ui class ButtonWithCallbacks(val label: String, val clickedCallbacks: List[() => Unit]) extends Widget { require(clickedCallbacks != null, "Callback list can't be null!") def this(label: String, clickedCallback: () => Unit) = this(label, List(clickedCallback)) def this(label: String) = { this(label, Nil) println("Warning: button has no click callbacks!") } def click() = { // ... logic to give the appearance of clicking a physical button ... clickedCallbacks.foreach(f => f()) } }
類ButtonWithCallbacks 表示了圖形用戶界面上的一個按鈕。它有一個標簽和一個回調函數的列表,這些函數會在按鈕被點擊的時候被調用。每一個回調函數都不接受參數,并且返回Unit。方法click 會遍歷回調函數的列表,然后一個個地調用它們。
ButtonWithCallbacks 定義了3個構造函數。主構造函數,類的主題,有一個參數列表來接受標簽字符串和回調函數的列表。因為每一個參數都被聲明為val, 編譯器為每一個參數都生成一個私有字段(會使用一個不同的內部名稱),以及名字和參數一致的公有讀取方法。“私有”和“公有”在這里的意思和在大多數面向對象語言里一樣。我們會在下面的“可見性規(guī)則”章節(jié)討論不同的可見性規(guī)則和控制它們的關鍵字。
如果參數有一個var 關鍵字,一個公有的寫方法會被自動生成,并且名字為參數名加下劃線等號(_=)。例如,如果label 被聲明為var, 對應的寫方法則為label_=,而且它會接受一個字符串作為參數。
有時候你可能不希望自動生成這些訪問器方法。換句話說,你希望字段是私有的。在val 或者var 之前加上private 關鍵字,訪問器方法就不會被生成。(參見“可見性規(guī)則”章節(jié)獲取更多細節(jié)信息。)
注意
對于Java 程序員,Scala 沒有遵循s [JavaBeanSpec] 約定 - 字段讀取、寫方法分別對應get 和set 的前綴,緊接著是第一個字母大寫的字段名。我們會在“當方法和字段存取器無法區(qū)分時:唯一存取的原則”章節(jié)中討論唯一存取原則時看到原因。不過,你可以在需要時通過scala.reflect.BeanProperty 來獲得JavaBeans 風格的訪問器,我們會在《第14章 - Scala 工具,庫和IDE 支持》中的“JavaBean 屬性”章節(jié)來討論這個問題。
當類的一個實例被創(chuàng)建時,每一個參數對應的字段都會被參數自動初始化。初始化這些字段不需要邏輯上的構造函數,這和很多面向對象語言不同。
ButtonWithCallbacks 類主體(換言之,構造函數)的第一個指令是一個保證被傳入構造函數的參數列表是一個非空列表的測試。(不過它確實允許一個空的Nil 列表。)它使用了方便的require 函數,這個函數是被自動導入到當前的作用域中的(正如我們將在《第7章 - Scala 對象系統(tǒng)》的“預定義對象”章節(jié)所要討論的)。如果這個列表是null, require 會拋出一個異常。require 函數和它對應的假設對于設計契約式程序非常有用,我們會在《第13章 - 應用程序設計》的“用契約式設計方式構造更佳的設計”章節(jié)中討論這個問題。
這里是ButtonWithCallbacks 的完整Specification(規(guī)格)的一部分,它展示了require 指令的作用。
// code-examples/Traits/ui/button-callbacks-spec.scala package ui import org.specs._ object ButtonWithCallbacksSpec extends Specification { "A ButtonWithCallbacks" should { // ... "not be constructable with a null callback list" in { val nullList:List[() => Unit] = null val errorMessage = "requirement failed: Callback list can't be null!" (new ButtonWithCallbacks("button1", nullList)) must throwA( new IllegalArgumentException(errorMessage)) } } }
Scala 甚至使得把null 作為第二個參數傳給構造函數變得很困難;它不會再編譯時做類型檢查。然而,你向上面那樣可以把null 賦給一個value。如果我們沒有must throwA(...) 子句,我們會看到下面的異常被拋出。
java.lang.IllegalArgumentException: requirement failed: Callback list can't be null! at scala.Predef$.require(Predef.scala:112) at ui.ButtonWithCallbacks.(button-callbacks.scala:7) ....
ButtonWithCallbacks 定義了兩個方便用戶使用的輔助構造函數。第一個輔助構造函數接受一個標簽和一個單獨的回調函數。它調用主構造函數,并且傳遞給它標簽和包含了回調函數的新列表。
第二個輔助構造函數只接受一個標簽。它調用主構造函數,并且傳入Nil(Nil 表示了一個空的List 對象)。然后構造函數打印出一條警告消息指明沒有回調函數,因為列表是不可變的,所以我們沒有機會用一個新的值來替代現(xiàn)有的回調函數列表。
為了避免無限遞歸,Scala 要求每一個輔助構造函數調用在它之前定義的構造函數[ScalaSpec2009]。被調用的構造函數可以是另外一個輔助構造函數或者主構造函數,而且它必須出現(xiàn)在輔助構造函數主體的第一句。額外的過程可以在這個調用之后出現(xiàn),比如我們例子中的打印出一個警告消息。
注意
因為所有的輔助構造函數最終都會調用主構造函數,它主體中進行的邏輯檢查和其它初始化工作會在所有實例被創(chuàng)建的時候執(zhí)行。
Scala 對構造函數的約束有一些好處。
消除重復
因為輔助構造函數會調用主構造函數,潛在的重復構造邏輯就被大大地消除了。
代碼體積的減少
正如例子中所示,當一個或更多的主構造函數參數被聲明為val 或者var,Scala 會自動產生一個字段,合適的存取方法(除非它們被定義為private,私有的),以及實例被創(chuàng)建時的初始化邏輯。
不過,這樣也有至少一個缺點。
缺少彈性
有時候,迫使所有構造函數都是用同一個構造函數體并不方便。然而,我們發(fā)現(xiàn)這樣的情況只是極少數。在這種情況下,可能是因為這個類負責了太多東西,而且應該被重構為更小的類。
調用父類構造函數
子類的主構造函數必須調用父類的一個構造函數,無論是主構造函數或者是輔助構造函數。在下面的例子里,類RadioButtonWithCallbacks 會繼承ButtonWithCallbacks,并且調用ButtonWithCallbacks 的主構造函數?!癛adio”按鈕可以被設置為開或者關。
// code-examples/BasicOOP/ui/radio-button-callbacks.scala package ui /** * Button with two states, on or off, like an old-style, * channel-selection button on a radio. */ class RadioButtonWithCallbacks( var on: Boolean, label: String, clickedCallbacks: List[() => Unit]) extends ButtonWithCallbacks(label, clickedCallbacks) { def this(on: Boolean, label: String, clickedCallback: () => Unit) = this(on, label, List(clickedCallback)) def this(on: Boolean, label: String) = this(on, label, Nil) }
RadioButtonWithCallbacks 的主構造函數接受3個參數,一個開關狀態(tài)(真或假),一個標簽,以及一個回調函數例表。它把標簽和回調函數列表傳給父類ButtonWithCallbacks。開關狀態(tài)參數(on)被聲明為var,所以是可變的。on 也是每一個單選按鈕的私有屬性。 為了和父類保持統(tǒng)一,RadioButtonWithCallbacks 還定義了兩個輔助構造函數。注意它們必須調用一個之前定義的構造函數,和之前一樣。它們不能直接調用ButtonWithCallbacks 的構造函數。為所有類聲明這些構造函數可能是乏味的,但是我們在《第4章 - Traits》中探索的技巧可以幫助我們減少這樣的重復。
注意
雖然和Java 一樣,super 關鍵字通常被用來調用重寫的方法,但是它不能被用作調用父類的構造函數。
嵌套類
Scala 和許多面向對象語言一樣,允許你嵌套聲明類。假設我們希望所有的部件都有一系列的屬性。這些屬性可以是大小,顏色,是否可見等。我們可以使用一個簡單的map 來保存這些屬性,但是我們假設還希望能夠控制對這些屬性的存取,并且當它們改變時能進行一些其它的操作。
下面的例子展示了我們如何利用從《第4章 - Traits》中的“混合Traits”章節(jié)學到的特性來擴展我們原來的Widget 例子。
// code-examples/BasicOOP/ui/widget.scala package ui abstract class Widget { class Properties { import scala.collection.immutable.HashMap private var values: Map[String, Any] = new HashMap def size = values.size def get(key: String) = values.get(key) def update(key: String, value: Any) = { // Do some preprocessing, e.g., filtering. valuesvalues = values.update(key, value) // Do some postprocessing. } } val properties = new Properties }
我們添加了一個Properties 類,包含了一個私有的,可變的HashMap (HashMap 本身不可變)引用。我們同時加入了3個公有方法來獲取大小(例如,定義的屬性個數),獲取map 中的元素,以及更新map 中對應的元素等。我們可能需要在update 方法上做更多的工作,已經用注釋標明。
注意
你可以從上面的例子中看到,Scala 允許在一個類中定義另外一個,或者成為“嵌套”。當你有足夠多的功能需要歸并到一個類里,并且這個類在僅會被外層類所使用時,一個嵌套類就非常有用。
到這里為止,我們學習了如何聲明一個類,如何初始化它們,以及繼承的一些基礎。在下一個章節(jié),我們會討論類和對象內部的可見性規(guī)則。
注意
為了方便,我們會使用通用的“類型” 這一詞語來指代類和Trait,對應的還有成員類型。除非特別聲明,否則我們在使用通用術語“成員” 時會包含這些定義。
大多數面向對象語言都有控制類型或者類型成員可見性(作用域)聲明的結構。這些結構支持面向對象式的封裝,即本質上只有類或者Trait 的公共抽象會被暴露出來,內部實現(xiàn)則被隱藏于視界之下。
對于你的類、對象的用戶所希望看到和使用的任何地方你都會想用公共可見性。但是記住,公共可見成員的集合構成了類型暴露出的抽象接口,包括類型的名字。
面向對象設計世界的傳統(tǒng)智慧是,字段應該為私有(private)或者受保護的(protected)。如果有存取需求,也應該通過方法來完成,而不是使得所有東西都默認可存取。統(tǒng)一訪問原則(參見章節(jié)“當存取方法和字段無法區(qū)分時:統(tǒng)一訪問原則”)歸根結底是說我們可以通過方法或字段的直接存取給予用戶公共(public)字段的訪問語意,只要它對于任務來說是合適的即可。
提示
好的面向對象設計的藝術在于定義最小的,清晰的,有凝聚力的公共抽象層。
類型有兩種“用戶”: 繼承類型,以及使用類型實例的代碼。繼承類型通常比實例用戶需要更多地存取父類型的成員。
Scala 的可見性規(guī)則和Java 類似,但是傾向于更統(tǒng)一和靈活。例如,在Java 中,如果一個內部類有一個私有成員,則包含它的外部類是能看到的。在Scala 里,則不能看到,但是Scala 提供了另外一種方式來聲明它對于包含它的外部類可見。
和Java,C# 一樣,修改可見性的關鍵字,比如private 和protected,在聲明的最開始出現(xiàn)。你會在class,trait 關鍵字前,val 或者var 前,以及方法的def 前發(fā)現(xiàn)它們。
注意
你也可以在類的主構造函數前使用一個可見性修飾符。如果有,把它放在類型名稱和類型參數后,參數列表之前。像這樣:
class Restricted[+A] private (name: String) {...}
表格 5.1,“可見域” 總結了可見性的范圍。
名稱 | 關鍵字 | 描述 |
public | 沒有 | public 成員在任何地方都可見,跨越所有邊界 |
protected | protected | protected 成員對于定義它的類型,繼承類型以及嵌套類型可見,protected 類型僅在同一個包,子包中可見。 |
private | private | private 成員對于定義它的類型和嵌套類型可見,private 類型僅在同一個包可見。 |
scoped protected | protected[scoped] | 可見性被限制在域scoped 中,它可以是包,類型,或者this(對于成員來說就是該實例,對于類型來說就是它存在的包。參見下面的文字獲取更多信息。 |
scoped private | private[scoped] | 和scoped protected 一樣,除了繼承的時候。(下面會討論) |
讓我們來仔細探索一下這些可見性選項。為了簡單,我們會使用字段來作為成員的例子。方法,類型聲明的行為和字段是一致的。
注意
不幸的是,你不能對包做任何可見性修飾。因此,一個包永遠都是public,即使它沒有包含任何public 類型。
Public 可見性
任何沒有顯式可見性關鍵字的聲明都是“public”,意味著它在任何地方都可見。在Scala 里沒有public 關鍵字。這和Java 恰恰相反,Java 的默認行為是只在當前包里默認是public 可見性(也就是包私有的-“package private”)。其它面向對象語言,比如Ruby,也是默認public 可見性。
// code-examples/BasicOOP/scoping/public.scala package scopeA { class PublicClass1 { val publicField = 1 class Nested { val nestedField = 1 } val nested = new Nested } class PublicClass2 extends PublicClass1 { val field2 = publicField + 1 val nField2 = new Nested().nestedField } } package scopeB { class PublicClass1B extends scopeA.PublicClass1 class UsingClass(val publicClass: scopeA.PublicClass1) { def method = "UsingClass:" + " field: " + publicClass.publicField + " nested field: " + publicClass.nested.nestedField } }
你可以用scalac 編譯這個文件,應該不會遇到編譯錯誤。
這些包和類的任何成員都是public 的。主意,scopeB.UsingClass 可以訪問scopeA.PublicClass1 和它的成員,包括嵌套類的實例以及它的public 字段。
Protected 可見性
Protected 可見性為實現(xiàn)繼承的類型提供了一些好處,因為它需要對其父類型有更多的一些存取權限。任何用protected 關鍵字聲明的成員只對定義它的類型,包括其實例和任何繼承類型可見。當應用于類型時,protected 限制其可見性于包含它的package 中。
J對比之下,Java 使得protected 成員對于整個包都可見。Scala 則用scoped (區(qū)域的)private 和protected 來控制這樣的情況。
// code-examples/BasicOOP/scoping/protected-wont-compile.scala // WON'T COMPILE package scopeA { class ProtectedClass1(protected val protectedField1: Int) { protected val protectedField2 = 1 def equalFields(other: ProtectedClass1) = (protectedField1 == other.protectedField1) && (protectedField1 == other.protectedField1) && (nested == other.nested) class Nested { protected val nestedField = 1 } protected val nested = new Nested } class ProtectedClass2 extends ProtectedClass1(1) { val field1 = protectedField1 val field2 = protectedField2 val nField = new Nested().nestedField // ERROR } class ProtectedClass3 { val protectedClass1 = new ProtectedClass1(1) val protectedField1 = protectedClass1.protectedField1 // ERROR val protectedField2 = protectedClass1.protectedField2 // ERROR val protectedNField = protectedClass1.nested.nestedField // ERROR } protected class ProtectedClass4 class ProtectedClass5 extends ProtectedClass4 protected class ProtectedClass6 extends ProtectedClass4 } package scopeB { class ProtectedClass4B extends scopeA.ProtectedClass4 // ERROR }
當你用scalac 編譯這個文件的時候,你會得到下列輸出。(為了配合排版,在N: 行號之前的文件名已經被移除。)
16: error: value nestedField cannot be accessed in ProtectedClass2.this.Nested val nField = new Nested().nestedField ^ 20: error: value protectedField1 cannot be accessed in scopeA.ProtectedClass1 val protectedField1 = protectedClass1.protectedField1 ^ 21: error: value protectedField2 cannot be accessed in scopeA.ProtectedClass1 val protectedField2 = protectedClass1.protectedField2 ^ 22: error: value nested cannot be accessed in scopeA.ProtectedClass1 val protectedNField = protectedClass1.nested.nestedField ^ 32: error: class ProtectedClass4 cannot be accessed in package scopeA class ProtectedClass4B extends scopeA.ProtectedClass4 ^ 5 errors found
列表中的//ERROR 注釋標識了無法解析的行。
ProtectedClass2 可以存取ProtectedClass1 的protected 成員,因為它們是繼承關系。然而,它不能存取protectedClass1.nested 的protected nestedField 字段。而且,ProtectedClass3 不能存取它使用的ProtectedClass1 實例的protected 成員。
最終,因為ProtectedClass4 被聲明為protected,它對于scopeB 包來說不可見。
Private 可見性
Private 可見性完全隱藏了實現(xiàn)的細節(jié),即使對于繼承類的實現(xiàn)也一樣。任何用private 關鍵字聲明的成員只對定義它的類型可見,包括它的實例。當應用于類型時,private 限制其可見性為包含它的package。
// code-examples/BasicOOP/scoping/private-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1(private val privateField1: Int) { private val privateField2 = 1 def equalFields(other: PrivateClass1) = (privateField1 == other.privateField1) && (privateField2 == other.privateField2) && (nested == other.nested) class Nested { private val nestedField = 1 } private val nested = new Nested } class PrivateClass2 extends PrivateClass1(1) { val field1 = privateField1 // ERROR val field2 = privateField2 // ERROR val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1(1) val privateField1 = privateClass1.privateField1 // ERROR val privateField2 = privateClass1.privateField2 // ERROR val privateNField = privateClass1.nested.nestedField // ERROR } private class PrivateClass4 class PrivateClass5 extends PrivateClass4 // ERROR protected class PrivateClass6 extends PrivateClass4 // ERROR private class PrivateClass7 extends PrivateClass4 } package scopeB { class PrivateClass4B extends scopeA.PrivateClass4 // ERROR }
編譯這個文件會產生如下輸出。
14: error: not found: value privateField1 val field1 = privateField1 ^ 15: error: not found: value privateField2 val field2 = privateField2 ^ 16: error: value nestedField cannot be accessed in PrivateClass2.this.Nested val nField = new Nested().nestedField ^ 20: error: value privateField1 cannot be accessed in scopeA.PrivateClass1 val privateField1 = privateClass1.privateField1 ^ 21: error: value privateField2 cannot be accessed in scopeA.PrivateClass1 val privateField2 = privateClass1.privateField2 ^ 22: error: value nested cannot be accessed in scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ 27: error: private class PrivateClass4 escapes its defining scope as part of type scopeA.PrivateClass4 class PrivateClass5 extends PrivateClass4 ^ 28: error: private class PrivateClass4 escapes its defining scope as part of type scopeA.PrivateClass4 protected class PrivateClass6 extends PrivateClass4 ^ 33: error: class PrivateClass4 cannot be accessed in package scopeA class PrivateClass4B extends scopeA.PrivateClass4 ^ 9 errors found
現(xiàn)在,PrivateClass2 不能訪問它的父類PrivateClass1 的private 成員。正如錯誤消息指出的,它們對于子類來說完全不可見。它們也不能存取嵌套類的private 字段。
正如protected 訪問的例子一樣,PrivateClass3 不能訪問它使用的PrivateClass1 實例的private 成員。不過注意,equalFields 方法可以訪問其它實例的private 成員。
PrivateClass5 和PrivateClass6 的聲明失敗了,因為如果允許的話,它們等于允許PrivateClass4 “跳出它的定義域”。然而,PrivateClass7 的聲明成功了,因為它同時被定義為了private。令人好奇的是,我們上一個例子中能夠正確地定義一個繼承自protected 類的public 類。
最后,和protected 類型聲明一樣,private 類型不能在包含它的package 之外被繼承。
局部 Private 和Protected 可見性
Scala 允許你用scoped private 和protected 可見性聲明來更精細地調整可見性的范圍。注意,在局部聲明中使用proviate 或者protected 是可互換的,因為它們除了應用到繼承的成員上的可見性之外,其它都是一樣的。
提示
雖然在大多數情況下選擇任何一個都能活動相同的效果,在代碼中使用private 還是比protected 更常見一些。在Scala 的核心庫里,這個比利大概是5:1。
讓我們從scoped private 和scoped protected 之間的唯一區(qū)別入手,來看看當成員有這些局部性聲明的時候,它們在繼承機制下是如何工作的。
// code-examples/BasicOOP/scoping/scope-inheritance-wont-compile.scala // WON'T COMPILE package scopeA { class Class1 { private[scopeA] val scopeA_privateField = 1 protected[scopeA] val scopeA_protectedField = 2 private[Class1] val class1_privateField = 3 protected[Class1] val class1_protectedField = 4 private[this] val this_privateField = 5 protected[this] val this_protectedField = 6 } class Class2 extends Class1 { val field1 = scopeA_privateField val field2 = scopeA_protectedField val field3 = class1_privateField // ERROR val field4 = class1_protectedField val field5 = this_privateField // ERROR val field6 = this_protectedField } } package scopeB { class Class2B extends scopeA.Class1 { val field1 = scopeA_privateField // ERROR val field2 = scopeA_protectedField val field3 = class1_privateField // ERROR val field4 = class1_protectedField val field5 = this_privateField // ERROR val field6 = this_protectedField } }
編譯這個文件會產生如下輸出。
17: error: not found: value class1_privateField val field3 = class1_privateField // ERROR ^ 19: error: not found: value this_privateField val field5 = this_privateField // ERROR ^ 26: error: not found: value scopeA_privateField val field1 = scopeA_privateField // ERROR ^ 28: error: not found: value class1_privateField val field3 = class1_privateField // ERROR ^ 30: error: not found: value this_privateField val field5 = this_privateField // ERROR ^ 5 errors found
Class2 里的前兩個錯誤說明,在同一個package 內的繼承類,不能引用父類或者this 的scoped private 成員,但是它可以引用包含Class1 和Class2 的package (或者類型)的private 成員。
相比之下,對于package 之外的繼承類,它無法訪問Class1 的任何一個scoped private 成員。
然而,所有的scoped protected 成員對于兩個繼承類來說都是可以見的。
我們會在后面剩下的例子和討論中使用scoped private 聲明,因為在Scala 庫中,scoped private 比scoped protected 更加常見一些,前面的繼承情況并不是其中一個因素。
首先,讓我們從最嚴格的可見性開始,private[this],它也對類型成員起作用。
// code-examples/BasicOOP/scoping/private-this-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1(private[this] val privateField1: Int) { private[this] val privateField2 = 1 def equalFields(other: PrivateClass1) = (privateField1 == other.privateField1) && // ERROR (privateField2 == other.privateField2) && (nested == other.nested) class Nested { private[this] val nestedField = 1 } private[this] val nested = new Nested } class PrivateClass2 extends PrivateClass1(1) { val field1 = privateField1 // ERROR val field2 = privateField2 // ERROR val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1(1) val privateField1 = privateClass1.privateField1 // ERROR val privateField2 = privateClass1.privateField2 // ERROR val privateNField = privateClass1.nested.nestedField // ERROR } }
編譯這個文件會產生如下輸出。
5: error: value privateField1 is not a member of scopeA.PrivateClass1 (privateField1 == other.privateField1) && ^ 14: error: not found: value privateField1 val field1 = privateField1 ^ 15: error: not found: value privateField2 val field2 = privateField2 ^ 16: error: value nestedField is not a member of PrivateClass2.this.Nested val nField = new Nested().nestedField ^ 20: error: value privateField1 is not a member of scopeA.PrivateClass1 val privateField1 = privateClass1.privateField1 ^ 21: error: value privateField2 is not a member of scopeA.PrivateClass1 val privateField2 = privateClass1.privateField2 ^ 22: error: value nested is not a member of scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ 7 errors found
注意
第6 到8 行無法解析。因為它們是第5 行開始的表達式的一部分,編譯器在遇到第一個錯誤之后就會停住。
這些private[this] 成員僅對同一個實例內的成員可見。同一個類的不同實例之間無法訪問對方的private[this] 成員,所以equalFields 方法無法通過解析。
否則,類成員的可見性就和沒有域限定符的private 一樣了。
當用private[this] 聲明一個類型時,this 的使用被有效的綁定到了包含它的package 里,正如這里所展示的。
// code-examples/BasicOOP/scoping/private-this-pkg-wont-compile.scala // WON'T COMPILE package scopeA { private[this] class PrivateClass1 package scopeA2 { private[this] class PrivateClass2 } class PrivateClass3 extends PrivateClass1 // ERROR protected class PrivateClass4 extends PrivateClass1 // ERROR private class PrivateClass5 extends PrivateClass1 private[this] class PrivateClass6 extends PrivateClass1 private[this] class PrivateClass7 extends scopeA2.PrivateClass2 // ERROR } package scopeB { class PrivateClass1B extends scopeA.PrivateClass1 // ERROR }
編譯這個文件會產生如下輸出。
8: error: private class PrivateClass1 escapes its defining scope as part of type scopeA.PrivateClass1 class PrivateClass3 extends PrivateClass1 ^ 9: error: private class PrivateClass1 escapes its defining scope as part of type scopeA.PrivateClass1 protected class PrivateClass4 extends PrivateClass1 ^ 13: error: type PrivateClass2 is not a member of package scopeA.scopeA2 private[this] class PrivateClass7 extends scopeA2.PrivateClass2 ^ 17: error: type PrivateClass1 is not a member of package scopeA class PrivateClass1B extends scopeA.PrivateClass1 ^ four errors found
在同一個package 中,嘗試聲明一個public 或者protected 子類會失敗。只有private 和private[this] 子類是允許的。而且,PrivateClass2 在scopeA2 里,所以你不能在scopeA2 之外聲明它。簡單地嘗試在無關的scopeB 中聲明一個使用PrivateClass1 的類也失敗了。
因此,當應用到類型時,private[this] 和Java 的package private 可見性一致。
下面,讓我們來檢查類型級別的可見性,private[T],T 是一個類型。
// code-examples/BasicOOP/scoping/private-type-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1(private[PrivateClass1] val privateField1: Int) { private[PrivateClass1] val privateField2 = 1 def equalFields(other: PrivateClass1) = (privateField1 == other.privateField1) && (privateField2 == other.privateField2) && (nested == other.nested) class Nested { private[Nested] val nestedField = 1 } private[PrivateClass1] val nested = new Nested val nestednestedNested = nested.nestedField // ERROR } class PrivateClass2 extends PrivateClass1(1) { val field1 = privateField1 // ERROR val field2 = privateField2 // ERROR val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1(1) val privateField1 = privateClass1.privateField1 // ERROR val privateField2 = privateClass1.privateField2 // ERROR val privateNField = privateClass1.nested.nestedField // ERROR } }
編譯這個文件會產生如下輸出。
12: error: value nestedField cannot be accessed in PrivateClass1.this.Nested val nestednestedNested = nested.nestedField ^ 15: error: not found: value privateField1 val field1 = privateField1 ^ 16: error: not found: value privateField2 val field2 = privateField2 ^ 17: error: value nestedField cannot be accessed in PrivateClass2.this.Nested val nField = new Nested().nestedField ^ 21: error: value privateField1 cannot be accessed in scopeA.PrivateClass1 val privateField1 = privateClass1.privateField1 ^ 22: error: value privateField2 cannot be accessed in scopeA.PrivateClass1 val privateField2 = privateClass1.privateField2 ^ 23: error: value nested cannot be accessed in scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ 7 errors found
一個private[PrivateClass1] 的成員對于其它實例來說也可見,所以equalFields 方法可以通過編譯。因此,private[T] 不如private[this] 來得嚴格。注意,PrivateClass1 不能訪問Nested.nestedField,因為那個字段被聲明為private[Nested]。
提示
當T 的成員被聲明為private[T] 的時候,其行為等同于private。但是它不同于private[this],后者更加嚴格。
如果我們修改Nested.nestedField 的范圍,變成private[PrivateClass1] 會發(fā)生什么呢?讓我們來看看private[T] 如何影響嵌套的類型。
// code-examples/BasicOOP/scoping/private-type-nested-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1 { class Nested { private[PrivateClass1] val nestedField = 1 } private[PrivateClass1] val nested = new Nested val nestednestedNested = nested.nestedField } class PrivateClass2 extends PrivateClass1 { val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1 val privateNField = privateClass1.nested.nestedField // ERROR } }
編譯這個文件會獲得如下輸出。
10: error: value nestedField cannot be accessed in PrivateClass2.this.Nested def nField = new Nested().nestedField ^ 14: error: value nested cannot be accessed in scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ two errors found
現(xiàn)在nestedField 對PrivateClass1 來說可見,但是它對于PrivateClass1 之外來說仍然不可見。這就是private 在Java 中的工作。
讓我們來用一個package 名字檢查其作用范圍。
// code-examples/BasicOOP/scoping/private-pkg-type-wont-compile.scala // WON'T COMPILE package scopeA { private[scopeA] class PrivateClass1 package scopeA2 { private [scopeA2] class PrivateClass2 private [scopeA] class PrivateClass3 } class PrivateClass4 extends PrivateClass1 protected class PrivateClass5 extends PrivateClass1 private class PrivateClass6 extends PrivateClass1 private[this] class PrivateClass7 extends PrivateClass1 private[this] class PrivateClass8 extends scopeA2.PrivateClass2 // ERROR private[this] class PrivateClass9 extends scopeA2.PrivateClass3 } package scopeB { class PrivateClass1B extends scopeA.PrivateClass1 // ERROR }
編譯這個文件會產生如下輸出。
14: error: class PrivateClass2 cannot be accessed in package scopeA.scopeA2 private[this] class PrivateClass8 extends scopeA2.PrivateClass2 ^ 19: error: class PrivateClass1 cannot be accessed in package scopeA class PrivateClass1B extends scopeA.PrivateClass1 ^ two errors found
注意PrivateClass2 無法在scopeA2 之外被繼承,但是PrivateClass3 可以在scopeA 中被繼承,因為它被聲明為private[ScopeA]。
最后,讓我們來看一下package 級別的類型成員作用域效果。
// code-examples/BasicOOP/scoping/private-pkg-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1 { private[scopeA] val privateField = 1 class Nested { private[scopeA] val nestedField = 1 } private[scopeA] val nested = new Nested } class PrivateClass2 extends PrivateClass1 { val field = privateField val nField = new Nested().nestedField } class PrivateClass3 { val privateClass1 = new PrivateClass1 val privateField = privateClass1.privateField val privateNField = privateClass1.nested.nestedField } package scopeA2 { class PrivateClass4 { private[scopeA2] val field1 = 1 private[scopeA] val field2 = 2 } } class PrivateClass5 { val privateClass4 = new scopeA2.PrivateClass4 val field1 = privateClass4.field1 // ERROR val field2 = privateClass4.field2 } } package scopeB { class PrivateClass1B extends scopeA.PrivateClass1 { val field1 = privateField // ERROR val privateClass1 = new scopeA.PrivateClass1 val field2 = privateClass1.privateField // ERROR } }
編譯這個文件會獲得如下輸出。
28: error: value field1 cannot be accessed in scopeA.scopeA2.PrivateClass4 val field1 = privateClass4.field1 ^ 35: error: not found: value privateField val field1 = privateField ^ 37: error: value privateField cannot be accessed in scopeA.PrivateClass1 val field2 = privateClass1.privateField ^ three errors found
唯一的錯誤是嘗試從無關的package scopeB 訪問scopeA 的成員,或者嘗試訪問屬于嵌套的package scopeA2 的成員。
提示
當一個類型或成員被聲明為private[P],P是一個包含它們的package, 那么這就等同于Java 的package private 可見性。
對于可見性的總結
Scala 可見性聲明非常靈活,并且行為準則一致。它們?yōu)樗锌赡艿淖饔糜蛱峁┝思氈碌目梢娦钥刂?,從實例級別(private[this])到package 級別(private[P])。例如,它們使得創(chuàng)建在頂層package 之外暴露類型的組件更加容易,而且很好得在組件package 內部隱藏了類型和類型成員的實現(xiàn)。
最后,我們觀察到了一個潛在trait 隱藏成員的“問題”。
提示
在選擇trait 成員的名字時必須小心。如果兩個trait 有同樣名字的成員,并且這個trait 被同一個實例使用,那么即使兩個成員都是private 的也會產生命名沖突。
幸運的是,編譯器會抓住這種問題。
感謝各位的閱讀,以上就是“Scala面向對象編程的方法是什么”的內容了,經過本文的學習后,相信大家對Scala面向對象編程的方法是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。