Scala 使用 包 创建命名空间,让您可以模块化程序并帮助防止命名空间冲突。 Scala 支持 Java 使用的包命名样式,也支持 C++ 和 C# 等语言使用的“花括号”命名空间表示法。
Scala 导入成员的方法也类似于 Java,并且更灵活。 使用 Scala,您可以:
- 导入包、类、对象、traits 和方法
- 将导入语句放在任何地方
- 导入成员时隐藏和重命名成员
这些特性在以下示例中进行了演示。
创建一个包
通过在 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 部分
导入语句用于访问其他包中的实体。 导入语句分为两大类:
- 导入类、trait、对象、函数和方法
- 导入
given
子句
如果您习惯于 Java 之类的语言,则第一类 import 语句与 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 as 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
隐藏多个成员
要在导入过程中隐藏多个成员,请在使用最终通配符导入之前列出它们:
scala> 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:
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 {
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
实例
正如您将在 上下文抽象 一章中看到的,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, *}
讨论
通配符选择器 *
将除给定或扩展之外的所有定义带入范围,而 given
选择器将所有给定——包括那些由扩展产生的定义——带入范围。
这些规则有两个主要好处:
- 范围内的给定来自哪里更清楚。 特别是,不可能在一长串其他通配符导入中隐藏导入的给定。
- 它可以在不导入任何其他内容的情况下导入所有给定。 这一点特别重要,因为给定可以是匿名的,所以通常使用命名导入是不切实际的。
按类型导入
由于给定可以是匿名的,因此按名称导入它们并不总是可行的,通常使用通配符导入。 按类型导入 为通配符导入提供了更具体的替代方案,这使得导入的内容更加清晰:
import A.{given TC}
这会在 A
中导入任何具有符合 TC
的类型的 given
。
导入多种类型的给定 T1,...,Tn
由多个 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}
按类型导入可以与按名称导入混合。
如果两者都存在于导入子句中,则按类型导入排在最后。
例如,这个 import 子句导入了 im
、intOrd
和 listOrd
,但省略了 ec
:
import Instances.{im, given Ordering[?]}
一个例子
作为一个具体的例子,假设你有这个 MonthConversions
对象,它包含两个 given
定义:
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 ...
要将这些给定导入当前范围,请使用以下两个 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
如前所述, import given
语法的主要设计优势之一是明确范围内的给定来自何处,并且在这些 import
语句中,很清楚地表明给定是来自 MonthConversions
对象。
Contributors to this page:
Contents
- 导言
- Scala 3 特性
- 为什么是 Scala 3 ?
- Scala 的味道
- Hello, World!
- The REPL
- 变量和数据类型
- 控制结构
- 领域建模
- 方法
- 头等函数
- 单例对象
- 集合
- 上下文抽象
- 顶层定义
- 总结
- 类型初探
- 字符串插值
- 控制结构
- 领域建模
- 工具
- OOP 领域建模
- 函数式领域建模
- 方法
- 方法特性
- main 方法
- 总结
- 函数
- 匿名函数
- 函数变量
- Eta 扩展
- 高阶函数
- 自定义 map 函数
- 创建可以返回函数的方法
- 总结
- 打包和导入
- Scala 集合
- 集合类型
- 集合方法
- 总结
- 函数式编程
- 什么是函数式编程?
- 不可变值
- 纯函数
- 函数是值
- 函数式错误处理
- 总结
- 类型和类型系统
- 类型推断
- 泛型
- 相交类型
- 联合类型
- 代数数据类型
- 型变
- 不透明类型
- 结构化类型
- 依赖函数类型
- 其他类型
- 上下文抽象
- 扩展方法
- Given 实例和 Using 语句
- 上下文绑定
- Given 导入
- 实现类型类
- 多元相等性
- 隐式转换
- 总结
- 并发
- Scala 工具
- 使用 sbt 构建和测试 Scala 项目
- worksheet
- 与 Java 交互
- 向 Java 开发者介绍Scala
- Scala for JavaScript Developers
- Scala for Python Developers
- 下一步去哪