前言
這裏介紹在Scala中,包和引入的相關操作。
會與Java作對比。
1. 將代碼放入包中
如果你看過前面的章節會知道,所有的示例代碼中都是從object 對象開始。
這裏,使用包的形式。
如:
package gyt.navigation
class Navigator
如Java一樣,如果你的包名是網站,建議倒着寫。
另外一種類型C#的命名空間的做法,稱爲打包。如:
package gyt.navigation {
class Navigator
}
打包是一個更加通用的表示法,讓我們可以在一個文件裏包含多個內容。如,將源代碼和測試代碼放在一起:
package gyt {
package navigation {
// 位於 gyt.navigation 中
class Navigator
package tests {
// 位於 gyt.navigation.tests 中
class Tests
}
}
}
2. 對相關代碼的精簡訪問
將代碼按照包層次劃分以後,不僅有助於瀏覽代碼,同時告訴編譯器,同一個包中的代碼之間存在某種相關性。在同一個包中訪問代碼時,允許使用簡短的,不帶限定前綴的名稱。
如:
package gyt {
package navigation {
class Navigator {
// 簡短
val map = new StarMap
}
class StarMap
}
class Ship {
// 加上包名
val nav = new navigation.Navigator
}
package fleets {
class Fleet {
// 屬於同一級包
def addShip() = {
new Ship
}
}
}
}
如果不在同一級包,不能使用簡短訪問,如:
package gyt {
class Ship
}
package gyt2.fleets {
class Fleet {
def addShip() = {
// 無法通過編譯
new Ship
}
}
}
不再同一級包,需要使用包名,如:
package gyt {
class Ship
}
package gyt2.fleets {
class Fleet {
def addShip() = {
// 能夠通過編譯
new gyt.Ship
}
}
}
位於不同文件,使用_root_,因爲,所有的頂層包都被當做_root_的成員,寫法如:
gyt 與 gyt3 都屬於各自文件的頂層包時:
3. 引入
引入關鍵字import,被引入的包和它的成員可以簡單使用,而不需要限定名稱。
準備一個待引入的包:
package gyt
abstract class Fruit(
val name: String,
val color: String
)
object Fruits {
object Apple extends Fruit("apple", "red")
object Orange extends Fruit("orange", "orange")
object Pear extends Fruit("pear", "yellowish")
val menu = List(Apple, Orange, Pear)
}
引入語句示例:
// 引入Fruit
import gyt.Fruit
// 引入gyt的所有成員
import gyt._
// 引入Fruits的所有成員
import gyt.Fruits._
另外,Scala的引入可以出現在代碼的任何地方。
Scala的靈活引入:
與Java相比,Scala的import 子句要靈活得多。主要的區別有三點:
在Scala中,引入可以:
- 出現在任何位置
- 引用對象(不論是單例還是常規對象),而不只是包
- 讓你重命名並隱藏某些被引入的成員
Scala引入可以重命名或隱藏指定的成員。做法是包括在花括號內的引入選擇器子句中。如:
// 只引入Fruits對象中的Apple和Orange
import Fruits.{Apple, Orange}
// 只引入Fruits對象中的Apple和Orange, 並對Apple重命名爲NewAp
import Fruits.{Apple=>NewAp, Orange}
// 引入Fruits的所有成員, 相當於import Fruits._
import Fruits.{_}
// 引入Fruits的所有成員, 並對Apple重命名爲NewAp
import Fruits.{Apple=>NewAp,_}
// 引入Fruits的所有成員, 除了Apple
import Fruits.{Apple=>_,_}
因此,可以總結引入選擇器:
- 一個簡答的x。
- 一個重命名x=>y。
- 一個隱藏子句x=>_。
- 捕獲所有x=>_。
4. 隱式引入
Scala對每個程序都隱式地添加了一些引入。每個".scala"源文件的頂部都添加了如下三個引入子句:
import java.lang._
import scala._
import Predef._
所以,能夠直接使用java.lang、scala、Predef 包中的對象。
注意,引入的順序,如果出現同名的對象,後面的引入會遮擋前面的引入。 就像內部的同名變量會遮擋外部的同名變量。
5. 訪問修飾符
Java中有四種訪問修飾符,而Scala中有三種:private、protected、公共的。Scala與Java大體保持一致,但也有一些重要區別。
- 私有成員
私有成員只在含定義的類或對象的內部可見,與Java相同。但是,有一個區別,如:
class Outer {
class Inner {
private def f() = println("hello")
class InnerMore {
f() // OK
}
}
(new Inner).f() //錯誤, 無法訪問
}
也就是說,Scala無法從外部類訪問內部類的私有成員,而Java可以。
- 受保護的成員
更Java相比,Scala對proteced成員的訪問也更加嚴格。在Scala中,protected的成員只能從定義該成員的子類訪問。而Java允許同一個包內的其他類訪問這個類的受保護的成員。
Scala提供了另一種方式到達這個效果,即:修飾符[限定詞]
如:
class father {
protected def f() = println("hello")
}
class sun extends father {
f() // OK
}
class other {
f() // 錯誤
}
- 公共成員
Scala沒有專門的修飾符修飾公共成員:任何沒有被標記爲private和protected的成員都是公共的。公共成員可以從任何位置訪問。
前面提到了,Scala可以使用修飾符[限定詞] 的格式更加細粒度的修飾成員。
例如:
源代碼:
解釋:
值得一提的是,private[this]的定義,只能在包含該定義的同一個對象訪問。該定義稱爲對象私有。
也就是說,對它的訪問必須來自對象內部,並且爲同一個實例。
如果不是同一個實例,則訪問不被允許,如:
// speed 的修飾符爲private[this]
val other = new Navigator
other.speed // 不能被編譯, 不是同一個實例
可見性和伴生對象
在Java中,靜態成員和實例成員同屬於一個類,因此訪問修飾符對它們的應用方式是統一的。
Scala沒有靜態成員,而使用伴生對象來承載只存在一次的成員。
伴生類和伴生對象無論各自的成員是公共的、protected、private都可以互相訪問。
不過,伴生對象中,protected 成員沒有意義,因爲伴生對象沒有子類。
6. 包對象
目前爲止,能夠往包中添加類、特質、孤立對象等。這些都是放在包內頂層最常見的定義。任何你能夠放在類級別的定義,都能夠放在包級別。如在包級別放一個函數,作爲整個包都可以使用的助手函數。
具體的做法是把定義放在包對象。每個包都允許一個包對象,任何放在包對象的定義都會被當作包本身的成員。
示例:
gyt/package.scala
注意:package + object
package object gyt {
def f() = {
println("hello")
}
}
gyt/Test.scala
package gyt
object Test {
def main(args: Array[String]): Unit = {
f()
}
}
包對象會被編譯成名爲package.class的類文件,所以,該文件位於它增強的包的對象的目錄下。習慣將包對象的源代碼放在包名目錄下的package.scala中。
運行:
需要構建一個Scala項目,這裏使用IDEA。