Scala練習七包和引入
包和引入 |
摘要:
在本篇中,你將會了解到Scala中的包和引入語句是如何工作的。相比Java不論是包還是引入都更加符合常規,也更靈活一些。本篇的要點包括:
1. 包也可以像內部類那樣巢狀
2. 包路徑不是絕對路徑
3. 包宣告鏈x.y.z並不自動將中間包x和x.y變成可見
4. 位於檔案頂部不帶花括號的包宣告在整個檔案範圍內有效
5. 包物件可以持有函式和變數
6. 引入語句可以引入包、類和物件
7. 引入語句可以出現在任何位置
8. 引入語句可以重新命名和隱藏特定成員
9. java.lang、scala和Predef總是被引入
包 |
Scala中包含義
Scala的包和Java中的包或者C++中的名稱空間的目的是相同的:管理大型程式中的名稱
要增加條目到包中,你可以將其包含在包語句當中,比如:
package com {
package horstmann {
package impatient {
class Employee
…….
}
}
}
這樣一來類名Employee就可以在任意位置以com.horstmann.impatient.Employee訪問到了
Scala中包定義
與物件或類的定義不同,同一個包可以定義在多個檔案當中。前面這段程式碼可能出現在檔案Employee.scala中,而另一個名為Manager.scala的檔案可能會包含:
package com {
package horstmann {
package impatient {
class Manager
…….
}
}
}
這說明了,原始檔的目錄和包之間並沒有強制的關聯關係。你不需要將Employee.scala和Manager.scala放在com/horstmann/impatient目錄當中
換個角度講,你也可以在同一個檔案當中為多個包貢獻內容。Employee.scala檔案可以包含:
package com {
package horstmann {
package impatient {
class Employee
…….
}
}
}
package org {
package bigjava {
class Counter
…….
}
}
作用域規則 |
包巢狀
在Scala中,包的作用域比起java來更加前後一致。Scala的包和其他作用域一樣地支援巢狀,你可以訪問上層作用域中的名稱。例如:
package com {
package horstmann {
object Utils {
def percent of (value: Double, rate: Double) = value*rate/100
……….
}
package impatient {
class Employee
…….
def giveRaise( rate : Scala.Double) {
salary += Utils.percentOf( salary,rate )
}
}
}
}
注意Utils.percentOf修飾符。Utils類定義於父包,所有父包中的內容都在作用域內,因此沒必要使用com.horstmann.Utils.precentOf。
包衝突
不過,這裡有一個瑕疵。假定有如下程式碼:
package com {
package horstmann {
package impatient {
class Manager {
val suboardinates = new collection.mutable.ArrayBuffer[Employee]
…….
}
}
}
}
這裡我們利用到一個特性,那就是scala包總是被引入。因此,collection包實際上指向的是scala.collection。現在假定有人加入瞭如下的包,可能位於另一個檔案當中:
package com {
package horstmann {
package collection {
…….
}
}
}
這下Manager類將不再能通過編譯。編譯器嘗試在com.horstmann.collection包中查詢mutable成員未果。Manager類的本意是要使用頂級的scala包中的collection包,而不是隨便什麼存在於可訪問作用域中的子包
包衝突方案
在Java中,這個問題不會發生,因為包名總是絕對的,從包層級的最頂端開始。但是在Scala中,包名是相對的,就像內部類的名稱一樣。內部類通常不會遇到這個問題,因為所有程式碼都在同一個檔案當中,由負責該檔案的人直接控制。但是包不一樣,任何人都可以在任何時候向任何包新增內容。
解決方法之一是使用絕對包名,以_root_開始,例如:
val subordinates=new _root_.scala.collction.mutable.ArrayButfer[Employee]
另一種做法是使用"串聯式"包語句,在後面會詳細講到
串聯式包語句 |
包語句可以包含一個"串",或者說路徑區段,例如:
package com.horstmann.impatient { // com和com.horstmann的成員在這裡不可見
package people {
class Person
}
}
這樣的包語句限定了可見的成員。現在com.horstmann.collection包不再能夠以collection訪問到了
檔案頂部標記法 |
除了我們到目前為止看到的巢狀標記法外,你也可以在檔案頂部使用package語句,不帶花括號。例如:
package com.horstmann.impatient
package people
class Person
這等同於
package com.horstmann.impatient {
package people {
class Person
……
// 直到檔案末尾
}
}
如果檔案中的所有程式碼屬於同一個包的話:這也是通常的情形,這是更好的做法。還需注意是:在上面的示例當中,檔案的所有內容都屬於com.horstmann.impatient.people,但com.horstmann.impatient包的內容是可見的,可以被直接引用
包物件 |
包可以包含類、物件和特質,但不能包含函式或變數的定義。很不幸,這是Java虛擬機器的侷限。把工具函式或常量新增到包而不是某個Utils物件,這是更加合理的做法。包物件的出現正是為了解決這個侷限。每個包都可以有一個包物件。你需要在父包中定義它,且名稱與子包一樣。例如
package com.horstmann.impatient
package object people {
val defaultName="John Q. Public"
}
package people {
class Person {
var name=defaultName // 從包物件拿到的常置
}
…….
}
}
注意defaultName不需要加限定詞,因為它位於同一個包內。在其他地方,這個常量可以用com.horstmann.impatient.people.defaultName訪問到。在幕後,包物件被編譯成帶有靜態方法和欄位的JVM類,名為package.class,位於相應的包下。對應到本例中,就是com.horstmann.impatient.people.package,其中有一個靜態欄位defaultName。在JVM中,你可以使用package作為類名。對原始檔使用相同的命名規則是好習慣,可以把包物件放到檔案com/horstmann/impatient/people/package.scala。這樣一來,任何人想要對包增加函式或變數的話,都可以以很容易地找到對應的包物件
包可見性 |
在Java中,沒有被宣告為public、private或protected的類成員在包含該類的包中可見。在Scala中,你可以通過修飾符達到同樣的效果。以下方法在它自己的包中可見:
package com.horstmann.impatient.people
class Person {
private[people] def description="A person with name "+name
…….
}
你可以將可見度延展到上層包:
private[impatient] def description="A person with name "+name
引入 |
引入語句讓你可以使用更短的名稱而不是原來較長的名稱。寫法如下:
import java.awt.Color
這樣一來,你就可以在程式碼中寫Color而不是java.awt.Color了,這就是引入語句的唯一目的。如果你不介意長名稱,你完全不需要使用引入。你也可以引入某個包的全部成員:
import java.awt._
這和Java中的萬用字元*一樣。在Scala中,*是合法的識別符號。你完全可以定義com.horstmann.*.people這樣的包,但請別這樣做。你還可以引入類或物件的所有成員:
import java.awt.Color._
val c1 =RED // Color.RED
val c2=decode("#ff0000") // Color.decode
這就象Java中的import static。Java程式設計師似乎挺害怕這種寫法,但在Scala中這樣的弓很常見。一旦你引入了某個包,你就可以用較短的名稱訪問其子包。例如:
import java.awt._
def handler(evt: event.ActionEvent) { // java.awt.event.ActionEvent
……….
}
event包是java.awt包的成員,因此引入語句把它也帶進了作用域
任何地方都可以宣告引入 |
在Scala中,import語句可以出現在任何地方,並不僅限於檔案頂部。import語句的效果一直延伸到包含該語句的塊末尾。例如:
class Manager {
import scala.collection.mutable._
val subordinates = new ArrayBuffer[Employee]
}
這是個很有用的特性,尤其是對於通配引入而言。從多個源引入大量名稱總是讓人擔心。事實上,有些Java程式設計師特別不喜歡通配引入,以至於從不使用這個特性,而是讓IDE幫他們生成一長串引入語句。通過將引入放置在需要這些引入的地方,你可以大幅減少可能的名稱衝突。
重新命名和隱藏方法 |
重新命名
如果你想要引人包中的幾個成員,可以像這樣使用選取器( selector):
import java.awt.Color.{ Color,Font }
選取器語法還允許你重新命名選到的成員:
import java.util.{ HashMap=>JavaHashMap }
import scala.collection.mutable._
這樣一來,JavaHashMap就是java.utiI.HashMap,而HashMap則對應scala.collection.mutable.HashMap。
隱藏方法
選取器HashMap =>_將隱藏某個成員而不是重新命名它。這僅在你需要引入其他成員時有用:
import java.util.{HashMap=>_,_ }
import scala.collection.mutable._
現在,HashMap無二義地指向scala.collection.mutable.HashMap,因為java.util.HashMap被隱藏起來了
隱式引入 |
每個Scala程式都隱式地以如下程式碼開始:
import java.lang._
import scala._
import Predef._
和Java程式一樣,java.lang總是被引入。接下來,scala包也被引入,不過方式有些特殊。不像所有其他引入,這個引入被允許可以覆蓋之前的引入。舉例來說,scala.StringBuilder覆蓋java.lang.StringBuilder而不是與之衝突。最後,Predef物件被引入。它包含了相當多有用的函式,這些同樣可以被放置在scala包物件中,不過Predef在Scala還沒有加入包物件之前就存在了。
由於scala包預設被引入,對於那些以scala開頭的包,你完全不需要寫全這個字首。例如:
collection.mutable.HashMap
上述程式碼和以下寫法一樣好:
scala.collection.mutable. HashMap