Scala 使用封裝建立命名空間,讓您可以將程式模組化,並協助避免命名空間衝突。Scala 支援 Java 使用的封裝命名樣式,也支援 C++ 和 C# 等語言使用的「大括號」命名空間表示法。
Scala 匯入成員的方法也類似於 Java,但更靈活。使用 Scala,您可以
- 匯入封裝、類別、物件、特質和方法
- 將匯入陳述式放在任何地方
- 在匯入時隱藏和重新命名成員
下列範例示範這些功能。
建立封裝
封裝是透過在 Scala 檔案的頂端宣告一個或多個封裝名稱建立的。例如,當您的網域名稱是 acme.com,而且您正在應用程式 myapp 的 model 封裝中工作時,您的封裝宣告會如下所示
package com.acme.myapp.model
class Person ...
根據慣例,封裝名稱應全部使用小寫,正式的命名慣例為 <top-level-domain>.<domain-name>.<project-name>.<module-name>。
儘管並非必要,但套件名稱通常遵循目錄結構名稱,因此如果您遵循此慣例,此專案中的 Person
類別會出現在 MyApp/src/main/scala/com/acme/myapp/model/Person.scala 檔案中。
在同一個檔案中使用多個套件
上述語法套用於整個原始碼檔案:檔案 Person.scala
中的所有定義都屬於套件 com.acme.myapp.model
,根據檔案開頭的套件條款。
或者,可以撰寫僅套用於其包含的定義的套件條款
package users {
package administrators { // the full name of this package is users.administrators
class AdminUser // the full name of this class users.administrators.AdminUser
}
package normalusers { // the full name of this package is users.normalusers
class NormalUser // the full name of this class is users.normalusers.NormalUser
}
}
package users:
package administrators: // the full name of this package is users.administrators
class AdminUser // the full name of this class is users.administrators.AdminUser
package normalusers: // the full name of this package is users.normalusers
class NormalUser // the full name of this class is users.normalusers.NormalUser
請注意,套件名稱後接冒號,且套件內的定義會縮排。
此方法的優點在於它允許套件巢狀,並提供對範圍和封裝的更明顯控制,特別是在同一個檔案中。
匯入陳述,第 1 部分
匯入陳述用於存取其他套件中的實體。匯入陳述分為兩大類
- 匯入類別、特質、物件、函式和方法
- 匯入
given
條款
如果您習慣使用 Java 等語言,第一類的匯入陳述類似於 Java 使用的陳述,語法略有不同,允許更大的彈性。以下範例說明了其中一些彈性
import users._ // import everything from the `users` package
import users.User // import only the `User` class
import users.{User, UserPreferences} // import only two selected members
import users.{UserPreferences => UPrefs} // rename a member as you import it
import users.* // import everything from the `users` package
import users.User // import only the `User` class
import users.{User, UserPreferences} // import only two selected members
import users.{UserPreferences as UPrefs} // rename a member as you import it
這些範例旨在讓您了解第一類 import
陳述如何運作。它們在以下小節中會做更詳細的說明。
匯入陳述也用於將 given
執行個體匯入範圍。這些會在本章節的最後討論。
在繼續之前的一點說明
存取同一個套件的成員不需要匯入條款。
匯入一個或多個成員
在 Scala 中,您可以像這樣從套件匯入一個成員
import scala.concurrent.Future
並像這樣匯入多個成員
import scala.concurrent.Future
import scala.concurrent.Promise
import scala.concurrent.blocking
在匯入多個成員時,您可以像這樣更簡潔地匯入它們
import scala.concurrent.{Future, Promise, blocking}
當您想要從 scala.concurrent 套件匯入所有內容時,請使用此語法
import scala.concurrent._
import scala.concurrent.*
在匯入時重新命名成員
有時,在匯入實體時重新命名它們有助於避免名稱衝突。例如,如果您想要同時使用 Scala List
類別和 java.util.List 類別,您可以在匯入時重新命名 java.util.List 類別
import java.util.{List => JavaList}
import java.util.{List as JavaList}
現在,您使用名稱 JavaList
來參考該類別,並使用 List
來參考 Scala 清單類別。
您也可以使用此語法一次重新命名多個成員
import java.util.{Date => JDate, HashMap => JHashMap, _}
import java.util.{Date as JDate, HashMap as JHashMap, *}
那行程式碼表示:「重新命名 Date
和 HashMap
類別,如所示,並匯入 java.util 套件中的所有其他內容,而不重新命名任何其他成員。」
在匯入時隱藏成員
您也可以在匯入過程中隱藏成員。此 import
陳述式隱藏了 java.util.Random 類別,同時匯入 java.util 套件中的所有其他內容
import java.util.{Random => _, _}
import java.util.{Random as _, *}
如果您嘗試存取 Random
類別,它將無法運作,但您可以存取該套件中的所有其他成員
val r = new Random // won’t compile
new ArrayList // works
隱藏多個成員
要在匯入過程中隱藏多個成員,請在使用最後的萬用字元匯入之前列出它們
import java.util.{List => _, Map => _, Set => _, _}
scala> import java.util.{List as _, Map as _, Set as _, *}
這些類別再次被隱藏,但您可以在 java.util 中使用所有其他類別
scala> new ArrayList[String]
val res0: java.util.ArrayList[String] = []
由於這些 Java 類別被隱藏,因此您也可以使用 Scala List
、Set
和 Map
類別,而不會發生命名衝突
scala> val a = List(1, 2, 3)
val a: List[Int] = List(1, 2, 3)
scala> val b = Set(1, 2, 3)
val b: Set[Int] = Set(1, 2, 3)
scala> val c = Map(1 -> 1, 2 -> 2)
val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2)
在任何地方使用匯入
在 Scala 中,import
陳述式可以在任何地方。它們可以用於原始碼檔案的頂端
package foo
import scala.util.Random
class ClassA {
def printRandom(): Unit = {
val r = new Random // use the imported class
// more code here...
}
}
package foo
import scala.util.Random
class ClassA:
def printRandom(): Unit =
val r = new Random // use the imported class
// more code here...
如果您喜歡,您也可以在需要它們的地方附近使用 import
陳述式
package foo
class ClassA {
import scala.util.Random // inside ClassA
def printRandom(): Unit = {
val r = new Random
// more code here...
}
}
class ClassB {
// the Random class is not visible here
val r = new Random // this code will not compile
}
package foo
class ClassA:
import scala.util.Random // inside ClassA
def printRandom(): Unit =
val r = new Random
// more code here...
class ClassB:
// the Random class is not visible here
val r = new Random // this code will not compile
「靜態」匯入
當您想要以類似於 Java「靜態匯入」方法的方式匯入成員時,您可以直接參照成員名稱,而不需要加上它們的類別名稱,請使用以下方法。
使用此語法匯入 Java Math
類別的所有靜態成員
import java.lang.Math._
import java.lang.Math.*
現在,您可以存取靜態 Math
類別方法,例如 sin
和 cos
,而不需要在它們前面加上類別名稱
import java.lang.Math._
val a = sin(0) // 0.0
val b = cos(PI) // -1.0
import java.lang.Math.*
val a = sin(0) // 0.0
val b = cos(PI) // -1.0
預設匯入的套件
兩個套件會隱含匯入到您所有原始碼檔案的範圍中
- java.lang.*
- scala.*
Scala 物件 Predef
的成員也會預設匯入。
如果您曾經疑惑過為什麼可以使用
List
、Vector
、Map
等類別,而不用匯入它們,這是因為Predef
物件中的定義。
處理命名衝突
在罕見的命名衝突事件中,您需要從專案根目錄匯入某些內容,請在套件名稱前面加上 _root_
package accounts
import _root_.accounts._
package accounts
import _root_.accounts.*
匯入 given
實例
正如您在 Contextual Abstractions 章節中看到的,在 Scala 3 中,import
陳述式的特殊形式用於匯入 given
實例。基本形式顯示在這個範例中
object A:
class TC
given tc: TC
def f(using TC) = ???
object B:
import A.* // import all non-given members
import A.given // import the given instance
在此程式碼中,物件 B
的 import A.*
子句會匯入 A
的所有成員,除了 given
實例 tc
。相反地,第二個匯入 import A.given
,只 會匯入那個 given
實例。兩個 import
子句也可以合併成一個
object B:
import A.{given, *}
在 Scala 2 中,這種匯入樣式不存在。隱含定義總是透過萬用字元匯入。
討論
萬用字元選擇器 *
會將除 given 或擴充功能以外的所有定義納入範圍,而 given
選擇器會將所有 givens(包括由擴充功能產生的)納入範圍。
這些規則有兩個主要好處
- 範圍內的 givens 來自何處會更清楚。特別是,不可能在其他萬用字元匯入的長清單中隱藏匯入的 givens。
- 它可以在不匯入其他任何內容的情況下匯入所有 givens。這特別重要,因為 givens 可以是匿名的,因此通常使用命名匯入並不實際。
依類型匯入
由於 givens 可以是匿名的,因此透過它們的名稱匯入它們並不總是實際可行,而萬用字元匯入通常用於代替。依類型匯入 提供了比萬用字元匯入更具體的替代方案,這使得匯入的內容更清楚
import A.{given TC}
這會匯入任何在 A
中的 given
,其類型符合 TC
。匯入多種類型 T1,...,Tn
的 givens,會以多個 given
選擇器表示
import A.{given T1, ..., given Tn}
匯入參數化類型的所有 given
執行個體,會以萬用字元引數表示。例如,當您有這個 object
時
object Instances:
given intOrd: Ordering[Int]
given listOrd[T: Ordering]: Ordering[List[T]]
given ec: ExecutionContext = ...
given im: Monoid[Int]
這個匯入陳述匯入了 intOrd
、listOrd
和 ec
執行個體,但遺漏了 im
執行個體,因為它不符合任何指定的界線
import Instances.{given Ordering[?], given ExecutionContext}
依類型匯入可以與依名稱匯入混合。如果兩者都出現在匯入子句中,依類型匯入會排在最後。例如,這個匯入子句匯入了 im
、intOrd
和 listOrd
,但遺漏了 ec
import Instances.{im, given Ordering[?]}
一個範例
作為一個具體範例,假設您有這個包含兩個 given
定義的 MonthConversions
物件
object MonthConversions:
trait MonthConverter[A]:
def convert(a: A): String
given intMonthConverter: MonthConverter[Int] with
def convert(i: Int): String =
i match
case 1 => "January"
case 2 => "February"
// more cases here ...
given stringMonthConverter: MonthConverter[String] with
def convert(s: String): String =
s match
case "jan" => "January"
case "feb" => "February"
// more cases here ...
若要將那些 givens 匯入目前的範圍,請使用這兩個 import
陳述
import MonthConversions.*
import MonthConversions.{given MonthConverter[?]}
現在您可以建立一個使用那些 given
執行個體的方法
def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String =
monthConverter.convert(a)
然後您可以在應用程式中使用那個方法
@main def main =
println(genericMonthConverter(1)) // January
println(genericMonthConverter("jan")) // January
如前所述,「匯入 given」語法的關鍵設計優點之一,是讓範圍內的 givens 來源一目瞭然,而且在這些 import
陳述中,很明顯 givens 來自 MonthConversions
物件。