前言
这里介绍在Scala中,包和引入的相关操作。
会与Java作对比。
1. 将代码放入包中
如果你看过前面的章节会知道,所有的示例代码中都是从object 对象开始。
这里,使用包的形式。
如:
package gyt.navigation
class Navigator
如Java一样,如果你的包名是网站,建议倒着写。
另外一种类型C#的命名空间的做法,称为打包。如:
package gyt.navigation {
class Navigator
}
打包是一个更加通用的表示法,让我们可以在一个文件里包含多个内容。如,将源代码和测试代码放在一起:
package gyt {
package navigation {
// 位于 gyt.navigation 中
class Navigator
package tests {
// 位于 gyt.navigation.tests 中
class Tests
}
}
}
2. 对相关代码的精简访问
将代码按照包层次划分以后,不仅有助于浏览代码,同时告诉编译器,同一个包中的代码之间存在某种相关性。在同一个包中访问代码时,允许使用简短的,不带限定前缀的名称。
如:
package gyt {
package navigation {
class Navigator {
// 简短
val map = new StarMap
}
class StarMap
}
class Ship {
// 加上包名
val nav = new navigation.Navigator
}
package fleets {
class Fleet {
// 属于同一级包
def addShip() = {
new Ship
}
}
}
}
如果不在同一级包,不能使用简短访问,如:
package gyt {
class Ship
}
package gyt2.fleets {
class Fleet {
def addShip() = {
// 无法通过编译
new Ship
}
}
}
不再同一级包,需要使用包名,如:
package gyt {
class Ship
}
package gyt2.fleets {
class Fleet {
def addShip() = {
// 能够通过编译
new gyt.Ship
}
}
}
位于不同文件,使用_root_,因为,所有的顶层包都被当做_root_的成员,写法如:
gyt 与 gyt3 都属于各自文件的顶层包时:
3. 引入
引入关键字import,被引入的包和它的成员可以简单使用,而不需要限定名称。
准备一个待引入的包:
package gyt
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)
}
引入语句示例:
// 引入Fruit
import gyt.Fruit
// 引入gyt的所有成员
import gyt._
// 引入Fruits的所有成员
import gyt.Fruits._
另外,Scala的引入可以出现在代码的任何地方。
Scala的灵活引入:
与Java相比,Scala的import 子句要灵活得多。主要的区别有三点:
在Scala中,引入可以:
- 出现在任何位置
- 引用对象(不论是单例还是常规对象),而不只是包
- 让你重命名并隐藏某些被引入的成员
Scala引入可以重命名或隐藏指定的成员。做法是包括在花括号内的引入选择器子句中。如:
// 只引入Fruits对象中的Apple和Orange
import Fruits.{Apple, Orange}
// 只引入Fruits对象中的Apple和Orange, 并对Apple重命名为NewAp
import Fruits.{Apple=>NewAp, Orange}
// 引入Fruits的所有成员, 相当于import Fruits._
import Fruits.{_}
// 引入Fruits的所有成员, 并对Apple重命名为NewAp
import Fruits.{Apple=>NewAp,_}
// 引入Fruits的所有成员, 除了Apple
import Fruits.{Apple=>_,_}
因此,可以总结引入选择器:
- 一个简答的x。
- 一个重命名x=>y。
- 一个隐藏子句x=>_。
- 捕获所有x=>_。
4. 隐式引入
Scala对每个程序都隐式地添加了一些引入。每个".scala"源文件的顶部都添加了如下三个引入子句:
import java.lang._
import scala._
import Predef._
所以,能够直接使用java.lang、scala、Predef 包中的对象。
注意,引入的顺序,如果出现同名的对象,后面的引入会遮挡前面的引入。 就像内部的同名变量会遮挡外部的同名变量。
5. 访问修饰符
Java中有四种访问修饰符,而Scala中有三种:private、protected、公共的。Scala与Java大体保持一致,但也有一些重要区别。
- 私有成员
私有成员只在含定义的类或对象的内部可见,与Java相同。但是,有一个区别,如:
class Outer {
class Inner {
private def f() = println("hello")
class InnerMore {
f() // OK
}
}
(new Inner).f() //错误, 无法访问
}
也就是说,Scala无法从外部类访问内部类的私有成员,而Java可以。
- 受保护的成员
更Java相比,Scala对proteced成员的访问也更加严格。在Scala中,protected的成员只能从定义该成员的子类访问。而Java允许同一个包内的其他类访问这个类的受保护的成员。
Scala提供了另一种方式到达这个效果,即:修饰符[限定词]
如:
class father {
protected def f() = println("hello")
}
class sun extends father {
f() // OK
}
class other {
f() // 错误
}
- 公共成员
Scala没有专门的修饰符修饰公共成员:任何没有被标记为private和protected的成员都是公共的。公共成员可以从任何位置访问。
前面提到了,Scala可以使用修饰符[限定词] 的格式更加细粒度的修饰成员。
例如:
源代码:
解释:
值得一提的是,private[this]的定义,只能在包含该定义的同一个对象访问。该定义称为对象私有。
也就是说,对它的访问必须来自对象内部,并且为同一个实例。
如果不是同一个实例,则访问不被允许,如:
// speed 的修饰符为private[this]
val other = new Navigator
other.speed // 不能被编译, 不是同一个实例
可见性和伴生对象
在Java中,静态成员和实例成员同属于一个类,因此访问修饰符对它们的应用方式是统一的。
Scala没有静态成员,而使用伴生对象来承载只存在一次的成员。
伴生类和伴生对象无论各自的成员是公共的、protected、private都可以互相访问。
不过,伴生对象中,protected 成员没有意义,因为伴生对象没有子类。
6. 包对象
目前为止,能够往包中添加类、特质、孤立对象等。这些都是放在包内顶层最常见的定义。任何你能够放在类级别的定义,都能够放在包级别。如在包级别放一个函数,作为整个包都可以使用的助手函数。
具体的做法是把定义放在包对象。每个包都允许一个包对象,任何放在包对象的定义都会被当作包本身的成员。
示例:
gyt/package.scala
注意:package + object
package object gyt {
def f() = {
println("hello")
}
}
gyt/Test.scala
package gyt
object Test {
def main(args: Array[String]): Unit = {
f()
}
}
包对象会被编译成名为package.class的类文件,所以,该文件位于它增强的包的对象的目录下。习惯将包对象的源代码放在包名目录下的package.scala中。
运行:
需要构建一个Scala项目,这里使用IDEA。