Jade Dungeon

包与访问限制

包和引用

定义包

package语句

通过把package子句放在文件顶端的方式把整个文件内容放进包里:

  package bobsrockets.navigation
  class Navigator 

同一个包可以定义在不同的多个文件中。

嵌套包

另一种方式很像C#的命名空间。在package子句之后用大括号包起来一段要放到包里去的 定义。除此之外,这种语法还能把同一个文件内的不同部分放在不同的包里。

  package bobsrockets {
    package navigation {

      // In package bobsrockets.navigation
      class Navigator

      package tests {

        // In package bobsrockets.navigation.tests
        class NavigatorSuite
      }
    }
  } 

这种嵌套方式会把路径上所有的包的内容都相入进来。

类似于Java的语法实际上只是括号嵌入风格的语法糖。原理是:如果除了签入另一个包之外 对包不作任何事,你可以下面的方式省去一个缩进:

  package bobsrockets.navigation {
  
		// 这样就跳过了bobsrockets包,它的内容不会被引入

    // In package bobsrockets.navigation
    class Navigator

    package tests {

      // In package bobsrockets.navigation.tests
      class NavigatorSuite
    }
  } 

包路径是相对的

Java包尽管是分级的,却不是嵌套的。在Java里,在你命名一个包的时候,你必须从包层级 的根开始。

Scala为了简化,采用包风格类似于是相对路径:

  package bobsrockets {
    package navigation {
      class Navigator
    }
    package launch {
      class Booster {
        // No need to say bobsrockets.navigation.Navigator
        val nav = new navigation.Navigator
      }
    }
  } 

为了避免绝对路径与相对路径之间的歧义,可以用_root_表示顶层包,这样就一定是绝对 路径:

val aa = new _root_.scala.collection.mutable.ArrayBuffer[Employee] 

包层级的作用域

而且内部区域的包可以隐匿被定义在外部区域的同名包。还提供了_root_表示顶层包:

  // In file launch.scala
  package launch {
    class Booster3
  }

  // In file bobsrockets.scala
  package bobsrockets {
    package navigation {
      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
    }
  } 

上面的代码中,为了访问Booster3,Scala提供了所有用户可创建的包之外的名为 _root_的包。换句话就是,任何你写的顶层包都被当作是_root_包的成员。

因此,_root_.launch让你能访问顶层的launch包,_root_.launch.Booster3指向的 就是最外面的booster类。

包对象

由于JVM的局限,包里只能放类、对象、特质,不能把变量、常量与函数放在包里。

解决方案是在上级包是定义一个与子包名称一样的包对象,包对象的成员用起来就像是在 子包中一样:

package com.horstmann.impatient

package object people {
	val defaultName = "John Q. Public"
}

package people {
	class Person {
		var name = defaultName // 从包对象取得常量
	}
}

因为在同一个包中,所以defaultName不需要加限制。在其他的地方访问方式可能为:

com.horstmann.impatient.people.defaultName

在幕后包对象被编译成带有静态方法和字段的JVM类,名为package.class,位于相应的 包下。以这个例子来说就是com.horstmann.impatient.people.package,它包含一个静态 字段defaultName

导入包与成员

Scala里用import子句来导入包和其成员, 如果在REPL环境中,还可通过输入:reset来重置会话:

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

不止是包,实例的成员和单例对象的成员也可以用import来引用:

  // easy access to Fruit
  import bobsdelights.Fruit

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

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

引入实例的成员的例子:

scala> case class Receipt(id: Int, amount: Double, who: String, title: String)
defined class Receipt

scala> import lat._
import lat._

scala> s"Sold a $title for $amount to $who"
res5: String = Sold a Medium Latte for 4.12 to Adda

差别是Scala的按需引用写作尾下划线_而不是星号*(毕竟*是合法的Scala标识符! )。上面的第三个引用子句与Java的静态类字段引用一致。

cala引用可以出现在任何地方,而不是仅仅在编译单元的开始处。同样,它们可以指向 任意值。

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

引用语句的作用范围从引入开始到当前代码块结束。

方法showFruit引用了它的参数,Fruit类型的fruit,的所有成员。之后的 println语句就可以直接使用namecolor了。这两个索引等价于fruit.namefruit.color

Scala的引用很灵活的另一个方面是它们可以引用包自身,而不只是非包成员。这只有你把 内嵌包想象成包含在外围包之内才是自然的。

例如,下面的代码里包java.util.regex被引用。这使得regex可以用作简单名。要访问 java.util.regex包的Pattern单例对象,你可以只是写成regex.Pattern

  import java.util.regex

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

重命名与隐藏

Scala的引用同样可以重命名或隐藏成员。可以用跟在引用的成员对象之后的包含在括号里 的引用选择子句(import selector clause)做到:

  • 重命名子句的格式是<原始名> => <新命名>
  • <原始名> => _格式的子句从被引用的名字中排除了<原始名>
// 只引用了对象Fruits的Apple和Orange成员。 
import Fruits.{Apple, Orange} 

// Apple对象重命名为McIntosh。
import Fruits.{Apple => McIntosh, Orange}

// 以SDate的名字引用了SQL的日期类,因此你可以在同时引用普通的Java日期类Date。
import java.sql.{Date => SDate}

// 以名称S引用了java.sql包,这样你就可以写成S.Date。
import java.{sql => S}

// 引用了对象Fruits的所有成员。这与import Fruits._同义。
import Fruits.{_}

// 从Fruits对象引用所有成员,不过重命名Apple为McIntosh。
import Fuites.{Apple => McIntosh, _}

// 引用了除Pear之外的所有Fruits成员。
import Fuits.{Pear => _, _}
//

把某样东西重命名为_就是表示把它隐藏掉。这对避免出现混淆的局面有所帮助。比方说 你有两个包,FruitsNotebooks,它们都定义了类Apple。如果你想只是得到名为 Apple的笔记本而不是水果,就需要引用所有的Notebooks和除了Apple之外所有的 水果:

  import Notebooks._
  import Fruits.{Apple => _, _} 
//

总而言之,引用选择可以包括下列模式:

  • 简单名x。把x包含进引用名集。
  • 重命名子句x => y。让名为x的成员以名称y出现。
  • 隐藏子句x => _。把x排除在引用名集之外。
  • 全包括_。引用除了前面子句提到的之外的全体成员。如果存在全包括,那么必须是

引用选择的最后一个。

本节最初展示的比较简单的引用子句可以被视为带有选择子句的简写。

例如,import p._等价于import p.{_}:并且import p.n等价于import p.{n}

隐式引用

Scala隐式地添加了一些引用到每个程序中。本质上,就好象下列的三个引用子句已经被 加载了:

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

java.lang

java.lang包囊括了标准Java类。它永远被隐式包含在Scala的JVM实现中。.NET实现将 代以引用system包,它是java.lang的.NET模拟。

scala

scala包含有标准的Scala库,包括许多通用的类和对象。因为scala被隐式引用,你可以 直接用List而不是scala.Listimport math._等同于import scala.math._math.sqrt(2)等同于scala.math.sqrt(2)

Predef

Predef对象包含了许多Scala程序中常用到的类型,方法和隐式转换的定义。比如,因为 Predef是隐式引用,你可以直接写assert而不是Predef.assert

上面的这三个引用子句与其它的稍有不同,靠后的引用将遮盖靠前的。

例如,StringBuilder类被定义在scala包里以及Java版本1.5以后的java.lang包中 都有。因为scala引用遮盖了java.lang引用,所以StringBuilder简单名将被看作是 scala.StringBuilder,而不是java.lang.StringBuilder

访问修饰符

Scala与Java对访问修饰符的对待方式有一些重要的差异。

public

Scala中没有任何标记默认是public的就是公开的。与Java中默认是protected不同,

private

  • Scala的private只在当前的实例中可见。与Java的同一类可见不同。
  • Scala里这个规则同样应用到了内部类上。这种方式更一致。这点与Java不同, Java会允许这两种访问因为它允许外部类访问其内部类的私有成员。
class Outer {
	class Inner {
		private def f() { println("f") }   // Only accessable in class "Inner"
		class InnerMost {
			f()                              // OK: Inside Class "Inner"
		}
	}
	(new Inner).f()                      // error: f is not accessible
} 
  • (new Inner).f()访问非法,因为fInner中被声明为private所以类Inner 外面不能被访问。
  • 相反,类InnerMost里访问f没有问题,因为这个访问包含在Inner类之内。

protected

Scala里protected只能被子类访问,和Java不一样,Java中可以被子类或同一个包中的类 访问。

package p {

	class Super {
		protected def f() { println("f") }  // Only accessable in sub-class
	}

	class Sub extends Super {
		f()                                 // OK, in sub-class
	}

	class Other {
		(new Super).f()                     // error:  not sub-class, same packet
	}
} 

保护的范围

Scala里的访问修饰符可以通过使用修饰词增加。格式为private[X]protected[X]的 修饰符表示针对X的私有或保护,这里X指代某些外围的包、类与其单例对象,也可以 用this表示只有当前类自己可以访问。

这样允许定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。 同样的技巧在Java里是不可能的:

package bobsrockets {
	package navigation {
	
		private[bobsrockets] class Navigator {         // acc in bobsrockets pkg
			protected[navigation] def useStarChart() {}  // like Java pprotected
			class LegOfJourney {
				private[Navigator]    val distance = 100   // like java private
				private[LegOfJourney] val days = 100       // like scala private
			}
			private[this] var speed = 200                // acc same instance
		}

	}
	package launch {
		import navigation._

		object Vehicle { 
			private[launch] val guide = new Navigator   // accessable in launch pkg
		}

	}
} 

扩大范围到包:private[包名]

Navigator被标记为private[bobsrockets]。这就是说这个类对包含在bobsrockets 包的所有的类和对象可见。

特别是对象Vehicle里对Navigator的访问被允许,因为Vehicle包含在包launch中 ,而launch包在bobsrockets中。

另一方面,包bobsrockets包之外的所有代码都不能访问类Navigator

private修饰词同样可以直接是外围包。对象Vehicle类的guide对象的访问修饰符是 这样的例子。这种访问修饰符等价于Java的包私有访问。

指向外围类、对象:private[类或对象]

private的修饰词还能指向外围类或对象。

例如LegOfJourney里的distance变量被标记为private[Navigator], 因此它在类Navigator的任何地方都可见。

这与Java里的内部类的私有成员具有同样的访问能力。private[C]里的C如果是最外层 的类,那么private的意思和Java一致。

添加指定包、类、对象:protected[包或类或对象]

所有的修饰词也可以用在protected上,与private意思相同。

也就是说,类Cprotected[X]修饰符允许C的所有子类和外围的包、类、或对象X 访问被标记的定义。

例如,useStarChart方法在Navigator所有子类以及包含在navigation包里的所有 代码能够被访问。这与Java的protected意思完全一致。

只能当前实例访问:private[this]

Scala还具有一种比private更严格的访问修饰符。被private[this]标记的定义仅能在 包含了定义的同一个实例中被访问。这种定义被称为对象私有(object-private)。

例如,代码13.11中,类Navigator的的speed定义就是对象私有的。这就是说所有的 访问必须不仅是在Navigator类里,而且还要是同一个Navigator实例发生的。

因此在Navigator内访问speedthis.speed是合法的。然而以下的访问,将不被允许 ,即使它发生在Navigator类之中:

  val other = new Navigator
  other.speed // this line would not compile 

把成员标记为private[this]是一个让它不能被同一个类中其它实例访问的保障。这在做 文档时比较有用。有时它也能让写出更通用的变体注释(参见以后会讲到的参数类型化章节 的变体注释)。

伴生对象的可见度性

Java里,静态成员和实例成员属于同一个类,因此访问修饰符可以统一地应用在他们之上。 在Scala里没有静态成员,代之以可以拥有包含成员的仅存在一个的伴生对象。

  class Rocket {
    import Rocket.fuel
    private def canGoHomeAgain = fuel > 20
  }

  object Rocket {
    private def fuel = 10
    def chooseStrategy(rocket: Rocket) {
      if (rocket.canGoHomeAgain)
        goHome()
      else
        pickAStar()
    }
    def goHome() {}
    def pickAStar() {}
  }
 

Scala的访问规则给予了伴生对象和类一些特权。类把它所有的访问权限共享给半生对象, 反过来也是如此。特别的是,对象可以访问所有它的伴生类的私有成员,就好象类也可以 访问所有伴生对象的私有成员一样。

举个例子,上面的Rocket类可以访问方法fuel,它在Rocket对象中被声明为私有。 类似地,Rocket对象也可以访问Rocket类里面的私有方法canGetHome

有一个例外,说到protected static成员时,Scala和Java的相似性被打破了。Java类C 的保护静态成员可以被C的所有子类访问。相反,伴生对象的protected成员没有意义, 因为单例对象没有任何子类。