返回介绍

数学基础

统计学习

深度学习

工具

Scala

五、包和导入

发布于 2023-07-17 23:38:23 字数 14624 浏览 0 评论 0 收藏 0

  1. 在大型程序中,通常以模块化的风格编写代码,将程序切分成若干个较小的模块,从而降低耦合。

5.1 包

  1. Scala 代码存在于Java 平台全局的包层次结构当中。在Scala 中,可以通过两种方式将代码放进带名字的包中:

    • 在文件顶部放置一个package 子句,让整个文件的内容放进指定的包中。

      
      
      xxxxxxxxxx
      package com.xx.yy ​ /* 现在整个文件的内容都在包中 */ class C
    • package 子句之后加上一段用花括号包裹起来的代码块,这个代码块包含了进入该包的定义。

      这个语法称作打包packaging,类似于C# 的命名空间。

      这是个更加通用的表示法,允许我们在一个文件中包含多个包的内容。如:将某个类的测试代码和原始代码放置在同一个文件里,不过分成不同的包。

      
      
      xxxxxxxxxx
      package com.xx.yy{ /* 只有花括号中的内容在包中 */ package model_a { // 原始 package class C // 原始代码 } package tests { // 测试 package class C_Test // 测试代码 } }
  2. 由于Scala 代码是 Java 生态的一部分,因此建议采用Java 的包名习惯:将域名倒过来。

  3. 采用包层次结构划分代码之后,不仅有助于人们浏览代码,同时也告诉编译器:同一个包中的代码之间存在某种相关性。

  4. 在访问同一个包内的代码时,Scala 允许我们采用简短的、不带限定前缀的名称。

    • 一个类不需要前缀就可以在自己的包内被访问。

      
      
      xxxxxxxxxx
      package com.xx.yy { package model_a { class C1 // 在 model_a 内, C1 不需要前缀就可以访问 class C2{ val c = new C1 // 不需要 com.xx.yy.model_a.C1 } } }
    • 包自身也可以从包含它的包里不带前缀的访问到。

      
      
      xxxxxxxxxx
      package com.xx.yy { package model_a { // 在 com.xx.yy 内,model_a 不需要前缀就可以访问 class C1 } class C3 { val c = new model_a.C1 // 不需要 com.xx.yy.model_a } }
    • 使用花括号打包语法时,所有在包外的作用域内可以被访问的名称,在包内也可以访问到。

      
      
      xxxxxxxxxx
      package com.xx.yy { class C3 package model_b { // model_b 可以直接访问 C3 class C4 { // 由于外层 model_b 可以直接访问 C3,这里不需要 com.xx.yy.C3 val c = new C3 } } }
    • 这些访问规则只有当你显式的嵌套打包时才有效。如果你采用每个文件只有一个包的做法,则只有那些在当前包内定义的名称才直接可用。

      
      
      xxxxxxxxxx
      package com.xx.yy { class C1 } ​ package com.xx.yy.model_a { // 现在 com.xx.yy.model_a 位于文件顶层 class C2 { val c = new C1 // C1 不再可见,编译错误 } }
  5. 如果花括号嵌套让你的代码缩进太多不方便阅读,则你可以用多个 package 子句但是不采用花括号。这称作链式包子句。

    
    
    xxxxxxxxxx
    package com.xx.yy // 省略了花括号 class C1 ​ package com.xx.yy // 省略了花括号 package model_a // 省略了花括号 class C2{ val c = new C1 // C1 可见 }
  6. 有时候可能 package 名字相互遮挡。

    
    
    xxxxxxxxxx
    // 位于文件 launch.scala 中 package launch { class Booster3 } ​ // 位于文件 mypackage.scala 中 package mypackage { package navigation { package launch { class Booster1 } class MissionControl { val booster1 = new launch.Booster1 val booster2 = new mypackage.launch.Booster2 val booster3 = new _root_.launch.Booster3 } } package launch { class Booster2 } }
    • MissionControlBooster1 的包launch 位于同一个包,因此可以直接访问 launch.Booster1
    • navigationBooster2 的包launch 位于同一个包,因此可以通过mypackage.launch.Booster2 来访问。
    • Scala 提供了一个名为_root_ 的包,这个包不会跟任何用户编写的包冲突。每个你编写的顶层包都被当作时_root_ 包的成员。因此_root_.launch.Booster3 可以访问Booster3

5.2 导入

  1. Scala 中,可以通过import 子句导入包和包的成员。被导入的项可以通过File 这样的简单名字访问,而不需要在前面加上包名或者对象名,如:java.io.File

    
    
    xxxxxxxxxx
    package model_a ​ abstract class Fruit( val name: String, val color: String) ​ object Fruits { object Apple extends Fruit("apple","red") object Orange extends Fruit("orange","orange") val menu = List(Apple,Orange) }

    使用时可以通过四种方式导入:

    • 导入包本身:

      
      
      xxxxxxxxxx
      import java.util.regrex // 导入 regrex 包 // 现在可以直接使用 regrex 这个名字了 ​ class C { val pattern = regrex.Pattern.compile("[0-9]*abc") }
    • 导入单个类型:

      
      
      xxxxxxxxxx
      import model_a.Fruit // 现在可以直接使用 Fruit 这个名字了
    • 导入包内的所有成员:

      
      
      xxxxxxxxxx
      import model_a._ // 现在可以直接使用 Fruit, Fruits 等名字了,因为它们都是 model_a 的成员

      这种方式与java 稍有不同。java 中是星号* ,而scala 中是下划线_ 。因为scala* 是个合法的标识符。

    • 导入对象的所有成员:

      
      
      xxxxxxxxxx
      import model_a.Fruits._ // 现在可以直接使用 Apple,Orange,menu 等名字了,因为它们都是 Fruits 的成员
  2. Scala 的导入比java 的导入更加通用:

    • 导入可以出现在任何地方,不仅仅是在某个编译单元的最开始。

    • 可以导入任意对象或者包,而不仅仅是导入包。

      
      
      xxxxxxxxxx
      def printFruit(fruit: Fruit) = { import fruit._ // 导入可以出现在任何地方,可以导入对象 println(name + "s are " + color) }

      这里导入了对象fruit,而不是包。它导入了对象fruit 的所有成员,因此接下来的name,color 等价于fruit.namefruit.color

    • 可以让你重命名并隐藏某些被导入的成员。做法是:在import 中引入括在花括号中的导入选择器子句,这个子句跟在那个我们要导入成员的对象的后面。

      • 导入限定的成员:

        
        
        xxxxxxxxxx
        import Fruits.{Apple,Orange} // 这里只会从 Fruits 对象导入 Apple 和 Orange 这两个成员
      • 重命名被导入的成员:通过格式 原名 => 新名

        
        
        xxxxxxxxxx
        import Fruits.{Apple => XXX,Orange} // 这里只会从 Fruits 对象导入 Apple 和 Orange 这两个成员,但是 Apple 重命名为 XXX
      • 如果花括号里只有下划线,则等价于导入所有成员:

        
        
        xxxxxxxxxx
        import Fruits.{_} // 等价于 import Fruits._
      • 可以配合 _=>,此时表示:引入所有成员,但是将某些被导入成员重命名。

        
        
        xxxxxxxxxx
        import Fruits.{Apple => XXX, _} // 导入 Fruits 对象的所有成员,但是将 Apple 重命名为 XXX
      • 可以通过格式 原名 => _ 来排除某个成员的导入。

        
        
        xxxxxxxxxx
        import Fruits.{Apple => _, _} //除了 Apple 之外, 导入 Fruits 的所有成员
  3. Scala 导入选择器拥有巨大灵活性,可以包含:

    • 一个简单的名字x,这将把 x 包含在导入的名字集合里。

    • 一个重命名子句 x => y ,这将让名字为x 的成员以名字 y 可见。

    • 一个隐藏子句 x => _,这将会从导入的名字集合里排除 x

    • 一个捕获所有的 _ 。这将会导入除了之前规则中提到的成员之外的所有成员。

      如果希望捕获所有,则它必须出现在导入选择器列表的末尾。

    前面给出的简单导入子句可以看作是带有选择器子句的导入子句的缩写。

    如:import p._ 等价于 import p.{_}; 而 import p.n 等价于 import p.{n}

  4. Scala 对每个程序都隐式添加了一些导入。本质上,这就好像是为每个.scala 源码文件的顶部都添加了三行导入子句:

    
    
    xxxxxxxxxx
    import java.lang._ // java.lang 包的全部内容 import scala._ // scala 包的全部内容 import Predef._ // Predef 对象的全部内容
    • java.lang 包包含了标准的Java 类。由于 java.lang 是隐式导入的,因此可以直接写 Thread,而不是 java.lang.Thread

    • scala 包包含了Scala 的标准类库。由于scala 是隐式导入的,因此可以直接写 List,而不是 scala.List

    • Predef 对象包含了许多类型、方法和隐式转换的定义,这些定义在scala 程序中经常被用到。

      由于Predef 是隐式导入的,因此可以直接写 assert ,而不是 Predef.assert

    • Scala 对这三个子句进行了一些特殊处理,后导入的会遮挡前面的。

      如:scala 包和 java 1.5 版本以后的 java.lang 包都定义了 StringBuilder类。由于 scala 的导入遮挡了 java.lang 的导入,因此 StringBuilder 这个名字会引用到 scala.StringBuilder,而不是 java.lang.StringBuilder

5.3 包对象

  1. 可以添加到包里的代码有类、特质、孤立对象之外,还可以添加其它代码:任何能够放在class level 的定义,都能够放在package level

  2. 如果你有某个希望在整个包都能用的helper 方法,可以将其放在包的顶层。具体做法是:将其定义放在包对象package object中 。

    每个包都允许有一个包对象,任何被放在包对象里的定义都被当作这个包本身的成员。

  3. 包对象的语法和花括号”打包“很像,区别是包对象包含了一个object 关键字。包对象的花括号括起来的部分可以包含任何你希望添加的定义。

    • 任何包的任何其他代码都可以像引入类一样引入这些方法。
    • 包对象经常用于包级别的类型别名和隐式转换。
    • 顶层scala 包也有一个包对象,其中的定义对所有Scala 代码都可用。
    
    
    xxxxxxxxxx
    // 位于文件 p1/package.scala 中 package object p1 { def printFruit(fruit: Fruit) = { import fruit._ println(name + "s are " + color) } } ​ // 位于文件 p2/package.scala 中 package p2 import p1.printFruit /* 其它代码 */
  4. 包对象会被编译成名为package.class 的类文件,该文件位于它增强的包的对应目录下。

    源文件最好能保持相同的习惯,即:包对象的源码放在包名目录下的一个叫package.scala 的文件中。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文