包与访问限制
包和引用
定义包
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
语句就可以直接使用name
和color
了。这两个索引等价于fruit.name
和
fruit.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 => _, _} //
把某样东西重命名为_
就是表示把它隐藏掉。这对避免出现混淆的局面有所帮助。比方说
你有两个包,Fruits
和Notebooks
,它们都定义了类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.List
;import 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()
访问非法,因为f
在Inner
中被声明为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
意思相同。
也就是说,类C
的protected[X]
修饰符允许C
的所有子类和外围的包、类、或对象X
访问被标记的定义。
例如,useStarChart
方法在Navigator
所有子类以及包含在navigation
包里的所有
代码能够被访问。这与Java的protected意思完全一致。
只能当前实例访问:private[this]
Scala还具有一种比private
更严格的访问修饰符。被private[this]
标记的定义仅能在
包含了定义的同一个实例中被访问。这种定义被称为对象私有(object-private)。
例如,代码13.11中,类Navigator
的的speed
定义就是对象私有的。这就是说所有的
访问必须不仅是在Navigator
类里,而且还要是同一个Navigator
实例发生的。
因此在Navigator
内访问speed
和this.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
成员没有意义,
因为单例对象没有任何子类。