關於 data member,Scala 編譯出來的 bytecode,遵循 OO 傳統的 encapsulation 觀念,會將所有 data member 使用 private modifier 隱藏起來,並產生 access method 作為真正要 access 這些 data member 的方式。
在 Scala 宣告 data_member,會產生以下的東西:
1. 產生一個 private data member,若是常數,則會加上 final modifier
2. 產生 access method
若是常數,只會有一個 reader 的 access method
若是變數,會有 reader 與 writer 的 access methods
access method 的 visibility 將會與該變數的 visibility 有關。
讓我們依序說明 data member 常數與 data member 變數。
範例:宣告一個 data member 常數
class S {
val val_s: String = "abc"
}
使用 javap 查看,得到
可以瞭解,在 Scala 中的一個 data_member 常數 val_s,會產生
1. 一個 Java 的 data member:final private val_s
2. 一個 Java 的 access method:public val_s()
說明:
由於是 data member 常數,所以會變成 final 的 Java data member
由於 Scala 中的 member,default 是 public,所以 access method 的 visibility 是 public
讓我們將 data member 換成 private,看會產生何種變化
範例:宣告一個 private 的 data member 常數
class S {
private val val_s: String = "abc"
}
我們可以看到,與上一個例子相同,只是 method val_s() 變成 private,可見得 access method 的 visibility 與 Scala data member 的 visibilty 相同。
接下來我們看 data member 變數,會產生什麼?
class S {
private var var_s: String = "abc"
}
在 Scala 中的一個 data_member 變數 var_s,會產生
1. 一個 Java 的 data member:private var_s
2. 一個 Java 的 reader access method:public var_s()
3. 一個 Java 的 writer access method:public var_s_$eq()
說明:
由於是 data member 變數,所以該 Java data member 沒有加上 final
由於 Scala 中的 member,default 是 public,所以 access methods 是 public
由上面的例子,很清楚我們發現兩個事實
1.Scala 的 data member 常數,會編譯成 final private data member,以及 reader methods。
2.Scala 的 data member 變數,會編譯成 private data member,以及 reader / writer 兩個 methods。
該 data member 的 reader method,就是與 data member 同名的 method。
該 data member 的 writer method,就是 data member 名稱,後面再加上「_$eq」的 method。
Scala 中,有以下的概念「data member 使用原則」
- Scala 中,只要有 reader method,就可以把它當成 read 的 data member 來處理。
- 所以只要有一個 method 叫 x(),我們可以把它視為有一個可讀的 x data member。
- Scala 中,只要有 reader / writer method,就可以把它當成 write 的 data member 來處理。
- 所以只要有兩個 method 叫 x 與 x_$eq(),我們可以把它視為有一個可寫的 x data member,所以我們可以直接使用 obj.x = ... 的格式。
- 但是,若只有一個 writer method,則無法把它當成 data member 來使用
請注意:「name_$eq()」這種格式的 method 名稱,是在 JVM 層次所看到的,若你想要在 Scala 中產生這種格式的 method,你可以宣告一個 method 叫「name_=()」。
範例:有 reader method x(),當成 read 的 data member 來看待
class S {
def x() = 100 // S 有 x() method,可以將它視為 read only 的 x
}
class Test {
def m1() = {
val s = new S
val n = s.x // 將 x 視為 S 的 data member 來處理
}
}
請注意,上例使用 s.x 是合法,雖然 S 裡面沒有一個 data member x。
其實,Scala 中更有一個關於 method call 的小括號省略原則。小括號省略原則讓你可以省略小括號,所以可以使用 s.x,這裡的 「data member 使用原則」也讓你可以使用 s.x,兩者是相通的。
範例:有 reader method x 與 writer method x_$eq(),當成 write 的 data member 來看待
class S {
var x1 = 0
def x = x1
def x_=(n: Int) = x1 = n // S 有 x_=() method,可以將它視為 write 的 x data member
}
class Test {
def m1() = {
val s = new S
s.x = 10 // 將 x 視為 S 的 data member 來處理
println(s.x) // 將 x 視為 S 的 data member 來處理,這時候將會印出 10
}
請注意:需要有 x 與 x_$ 兩個 method 才會被視為 data field
注意:x_$=() method 會編譯成 x_$eq() method。
上例中,「s.x = 10」可以看成是 syntax sugar,會被 compile 改成呼叫「s.x_(10)」
其實,我們應該更精確的講,Scala 中沒有所謂的 data field 的 assignment,所有的 data field assignment,都會被改變成 call obj.name_=(value)。
範例:有 writer method x_$eq(),但沒有 reader method x,此時不可當成 data member 來看待
class S {
var x1 = 0
def x_=(n: Int) = x1 = n // S 有 x_=() method,但沒有 def x method
}
class Test {
def m1() = {
val s = new S
s.x = 10 // 這裡會出錯,有 writer 但 沒有 reader 仍不行
}
範例:有 writer method x_$eq(),有 reader method x(),此時不可當成 data member 來看待,因為 reader method 需為 method x
class S {
var x1 = 0
def x() = x1
def x_=(n: Int) = x1 = n // S 有 x_=() method,但沒有 def x method
}
class Test {
def m1() = {
val s = new S
s.x = 10 // 這裡會出錯,有 writer x_=() 但 沒有 reader x 仍不行
}
請注意,空括號的 method x() 與無括號的 method x,在 Scala 中是視為不同的,若想要當成 data assignment 的 method,所需要的是無括號的 method x()。
將上例改為如下例
class S {
var x1 = 0
def x = x1 // 無括號的 method
def x_=(n: Int) = x1 = n // S 有 x_=() method,但沒有 def x method
}
class Test {
def m1() = {
val s = new S
s.x = 10 // 這裡就會成功了
}
這裡我們需要重複之前的一句話,我們應該更精確的講,Scala 中沒有所謂的 data field 的 assignment,所有的 data field assignment,都會被改變成 call obj.name_=(value),但前提是 name 需要有兩個 method 一個是 name_=(value) method ,另一個就是 name 這個無括號的 method。
另外,當我們使用 obj.name 來 access 該 obj 的 name data field 時,其實會被轉變成呼叫 obj.name 這個 method 或是 obj.name() 這個 method (這兩個 method 沒辦法不會同時存在)。
綜合上述兩點,我們可以看出 Scala 對於 data field 的操作其實與 Java 完全不同,Java 會直接操作到該 data field,但 Scala 會將所有的 data field 都轉換成 method call,所以外部程式其實是無法直接碰觸到該 data field 的,這樣保留了一點點安全性。
Scala 沒有像一些語言為保留這樣的安全性而直接取消掉 data field 的操作方式,保留這樣的 data field 的使用方式給 programmer 帶來相當大的方便性,Scala 將 data field 的操作直接在語言直接轉會成 method call,我們可以看成這是 syntax sugar,但需要時時提醒自己,其實這只是呼叫到相對應的 method。