使用Package(包)
in Scala with 0 comment

使用Package(包)

in Scala with 0 comment

介绍

内容

软件开发过程中,减小程序之间的“耦合性”至关重要。降低耦合性的一个方法是模块化。Scala 提供和 Java 类似的分包机制,但又稍有不同。因此,即使你了解 Java 语言,还是建议继续学习本节内容。

知识点

环境

适合人群

本课程难度为一般,属于初级级别课程,适合零基础或具有 Java 编程基础的用户。

开发准备

为了使用交互式 Scala 解释器,你可以在打开的终端中输入命令:

su -l hadoop #密码为 hadoop
scala

当出现 scala> 开始的命令行提示符时,就说明你已经成功进入解释器了。如下图所示。

image-1655707104670

实验步骤

将代码放入包中

我们之前的例子中,没有明确使用 package,因此它们存在于“未命名”的包中,或是默认包中。

在 Scala 将代码定义到某个包中有两种方式:

第一种方法和 Java 一样,在文件头定义包名,这种方法将后续所有代码都放在该包中。

比如:

package bobsrockets.navigation
class Navigator

第二种方法有些类似 C#,如:

package bobsrockets.navigation {
  class Navigator
}

第二种方法,可以在一个文件中定义多个包。

引用包中的代码

当我们把代码以层次关系放到包中时,它不仅仅可以帮助人们浏览代码,同时也说明了同一包中的代码具有某些相关性。Scala 可以利用这些相关性来简化代码引用,你可以使用短名称,而无需使用包的全路径来访问类定义。

下面我们给出三个简单的例子:

package bobsrockets{
  package navigation{
    class Navigator{
      var map = new StarMap
    }

    class StarMap
  }

  class Ship {
    val nav = new navigation.Navigator
  }

  class fleets{
    class Fleet{
      def addShip() {new Ship}
    }
  }
}

在第一个例子中,正如你可以预见的一样:访问同一包中定义的类型,无需使用前缀。直接使用类型的名称即可访问,也就是本例可以直接使用 new StarMap。类 StarMapNavigator 定义在同一个包中。

第二个例子,嵌套的 package 也可以在其父包中被同级别的其它类型直接访问,而无需使用全称。因此,第二个例子可以使用 navigation 直接访问 navigation 包,而无需添加 bobsrockets

第三个例子,使用包定义的 {} 语法结构时,内层的类型可以直接访问其外层定义的类型。因此,在类 Fleet 中,可以直接访问外层定义的类型 Ship

要注意的是,这种用法只适用于你明确嵌套包定义。如果你采用 Java 语言风格——一个文件定义一个包。那么你只能访问该包中定义的类型。

访问包定义的类型,还有一个技巧值得说明一下:比如你定义的一些类型之间可能存在相互隐藏的关系,也就是内层定义的同名类型可能会隐藏外层定义的同名类型。那么你怎么来访问外层定义的类型呢?请看下例:

package launch{
  class Booster3
}

package bobsrockets{
  package navigtion{
    package launch{
      class Booster1

  }

  class MissionControl{
    val booster1 = new launch.Booster1
    val booster2= new bobsrockets.launch.Booster2
    val booster3= new _root_.launch.Booster3

   }
  }


  package launch{
    class Booster2
  }
}

如何来访问 Booster1Booster2Booster3 呢?访问 Booster1 比较容易,Booster2 可以通过全称来访问。那么如何访问最外层的 Booster3 呢?内层的包 launch 隐藏了这个外部的同名包。为解决这种情况,Scala 提供了 _root_,也就是所有最外层的类型都可以当成定义在 _root_ 包中。因此,_root_.launch.Booster3 可以到访问最外层定义的类型。

使用 import

和 Java 一样,Scala 也是通过 import 语句引用其它包中定义的类型。类型引入后,可以使用短名称来引用该类型而无需使用全路径。要注意的 Scala 使用 “_” 而非 “*” 作为通配符。

// easy access to Fruit
import bobsdelights.Fruit

// easy access to all members of bobdelights
import bobsdelights._

// easy access to all member of Fruits
import bobsdelights.Fruits._

所定义的类型在包 bobsdelights 中:

package bobsdelights

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)
}

第一个为引用单个类型,第二个为按需引用。和 Java 不同的是,使用 “_” 代替 “*”,第三个类似于 Java 中的静态引用,可以直接使用 Fruits 中定义的对象。

此外,Scala 中的 import 语句的使用比较灵活,可以用在代码的任意部分,而不一定需要在文件开头定义。比如下面的 import 定义在函数内部:

import bobsdelights.Fruit

def showFruit(fruit:Fruit){
  import fruit._

  println(name + "s are" + color)
}

方法 showFruit 引入 fruit 对象(非类型)的所有成员,fruit 的类型为 Fruit。因此,可以在函数直接使用 fruit 的成员变量,而无需使用 fruit 限定符。这个方法和下面代码是等价的:

import bobsdelights.Fruit

def showFruit(fruit:Fruit){
   println(fruit.name + "s are" + fruit.color)
}

和 Java 相比,Scala 的 import 的使用更加灵活:

下面的例子直接引入包名称,而非包中成员。引入包后,可以使用相对名称来指代某个类型。

import java.util.regex

class AStarB {
  val pat = regex.Pattern.compile("a*b")
}

import 也可以用来重命名或者隐藏某些类型,比如:

import Fruits.{Apple,Orange}

仅仅引用 Fruits 中的 AppleOrangle 类型。

下面的例子使用 => 重命名类型:

import Fruits.{Apple => MaIntosh,Orange}

同样重命名也可以重新定义包名称,比如:

import java.{sql => S}

将引入的包 java.sql 改名为 java.S,因此可以使用 S.Date 来代替 sql.Date

如果需要隐藏某个类型,可以使用 Type => _ 将某个类型改名为_,就可以达到隐藏某个类型的效果。比如:

import Fruits.{Apple => _,_}

这个引用中,引入了 Fruits 中除 Apple 之外的其它类型。

隐含的 import

Scala 默认为每个文件添加如下几个 package。这几个包无需明确指明。

import java.lang._   // everything in the java.lang package
import scala._       // everything in the scala package
import Predef._      // everything in the Predef object

因此在写 Scala 应用之前,先了解下这些缺省包定义了哪些类和功能。

此外这三个包的顺序也需要了解一下,比如:StringBuilder 类定义在包 scala 和包 java.lang 中,后定义的 import 会覆盖前面的定义。因此,如果不明确指明,StringBuilderscala.StringBuilder 而非 java.lang.StringBuilder

注意这里的 scala._ 指所有 scala 下的包,包括子包。你可以在 Scala 的官方文档中查看到它们的全部信息,如下图所示。

image-1655707134018

Predef 为一对象(非包名),因此可以直接使用 Predef 对象定义的方法(静态引用)。在写代码之前了解下 scala 包和 Predef 定义的功能尤其重要。

包对象

到目前为止,我们看到的添加到包的都是类型、Trait 和单例对象(Object)。这些都是指包的定级层次定义的类型。

Scala 的定级层次除了可以定义类、TraitObject之外,其它的可以在类、TraitObject 内部定义的类型,也都可以直接定义在包中。比如一些通用的函数、变量,你都可以直接定义在包中。

在 Scala 中,可以把这些函数或方法放在一个称为“包对象”中。每个包只有一个包对象,任何放在包对象的类型都可以认为是包自身的成员。例如:

// in file bobsdelights/package.scala

package object bobsdelights{
  def showFruit(fruit: Fruit){
    import fruit._
    println(name + "s are " + color)
  }
}

// in file PrintMenu.scala

package printmenu
import bobsdelights.Fruits
import bobsdelights.showFruit

object PrintMenu{
  def main(args:Array[String]){
    for(fruit <- Fruits.menu){
      showFruit(fruit)
    }
  }
}

本例中,对象 PrintMenu 可以引入包对象中定义的函数 showFruit,方法和引入类定义一样,也是通过 import 语句。

包对象通常被编译为 package.class,其包名为定义的包。所以按照惯例一般包对象定义放在 package.scala 中。比如,上面的包对象可以放在 bobsdelights 目录下的 package.scala 中。