1. 程式人生 > >Scala練習七包和引入

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++中的名稱空間的目的是相同的:管理大型程式中的名稱

。舉例來說,Map這個名稱可以同時出現在scala.collection.immutablescala. 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