包和引入 |
摘要:
在本篇中,你將會了解到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++中的命名空間的目的是相同的:管理大型程序中的名稱。舉例來說,Map這個名稱可以同時出現在scala.collection.immutable和scala. collection.mutable包而不會沖突。要訪問它們中的任何一個,你可以使用完全限定的名稱scala.collection.immutable.Map或scala.collection.mutable.Map,也可以使用引入語句來提供一個更短小的別名
要增加條目到包中,你可以將其包含在包語句當中,比如:
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
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】。
如果,您希望更容易地發現我的新博客,不妨點擊一下左下角的【關注我】。
如果,您對我的博客所講述的內容有興趣,請繼續關注我的后續博客,我是【Sunddenly】。本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。