2.3 作用域
作用域决定了程序中的变量、方法和其他符号的可见范围。任何符号在其作用域外都是完全不可见的,不能被使用。在这一节我们将简要地介绍作用域的主要内容,从最高层开始。
2.3.1 Java包
Java包提供了一种机制,它把相关类型分组到一个全局唯一的命名空间。这种分组机制可以防止在一个包的命名空间内的标识符和其他开发人员在其他命名空间内创建和使用的标识符冲突。
一个典型的Java程序由很多Java包的代码组成。典型的Java运行时环境提供了如java.lang和java.util这样的包。此外,程序可能会依赖于其他通用的库,类似于org.apache树。传统上,应用代码(你所创建的代码)在你所创建的包内,包名是通过反转域名并附加程序名字生成的。因此,如果你的域名是androidhero.com,你的包所属的树的根是com.androidhero,则可以把代码放到如com.androidhero.awesomeprogram和com.androidhero.geohottness.service这样的包中。用于Android应用的典型的包在布局上会包含一个持久性包、UI包和负责应用逻辑和控制器代码的包。
包除了定义了全局唯一的命名空间外,包内对象的成员(成员变量和方法)之间的可见性也不同。类的内部变量对于在同一个包内的类是可见的,而对于其他包内的类则是不可见的。这个话题在后面还会进一步探讨。
声明一个类属于某个包的方法是,在定义类的文件的最上方,使用package这个关键字按照下面这个方式声明:
package your.qualifieddomainname.functionalgrouping
不要过分简化包名!因为一个快速、临时的实现方式可能需要使用很多年,如果不能保证包名唯一,那么以后一定会深受其困扰。
一些大型的项目通过使用完全不同的顶级域名来实现公有API包和这些API的实现之间的隔离。举个例子,Android API使用顶级包名android,这些API的实现则在com.android包内。Sun的Java源代码采用的机制也类似于此。公有API在Java包内,但是这些API的实现则放在了sun包内。在任意一种情况下,如果一个应用导入的是某个实现包,则这个应用会反复无常,因为它依赖于一些非公有的API。
虽然把代码添加到已有的包内是可以的,但通常认为这是一种不好的做法。通常情况下,除了名字空间,包通常是一棵源代码树,其至少和逆转的域名一样高。虽然这只是传统习惯,但是Java开发人员通常会期望com.brashandroid.coolapp.ui这个包中包含了CoolApp UI的所有源代码。如果另一棵树的某些地方也有CoolApp UI的一些代码,很多人会觉得很不习惯。
注意: Android应用框架中也有包的概念。但它不同于这里所说的包,在第3章会对Android应用框架的这种包进行专门说明。不要把它和Java的包相混淆。
2.3.2 访问修饰符和封装
前文提过,类的成员有特殊的可见性规则。大多数Java块中的定义是有作用域的:它们只在代码块本身及内嵌于其中的代码块中可见。然而,类中的定义在代码块外也可能是可见的。Java支持类将其顶级成员(其方法和成员变量)通过访问修饰符(access modifiers)发布给其他类的代码。访问修饰符关键字修改了声明的可见性。
在Java中有3个访问修饰符关键字:public、protected和private。共支持4种访问级别。访问修饰符影响的是类成员在类外面的访问性,但类内部的代码块遵循的是普通的作用域,不需要考虑访问修饰符的影响。
private修饰符的限制最高。带private关键字的声明在代码块外是不可见的。这种声明是最安全的,它会确保仅在类的内部会有指向这个声明的引用。private声明越多,类就越安全。
限制程度仅次于private修饰符的是默认的访问限制,即package访问。没有任何修饰符的声明属于默认情况,默认的访问可见性是指只能在同一个包中的其他类中可见。默认访问是创建对象共享的一种非常便捷的方式,Java的默认声明和C++中的friend声明
类似。
protected访问修饰符除了支持所有的默认访问权限之外,还允许访问子类。任何包含protected声明的类都能够访问这些声明。
public访问修饰符是限制条件最弱的修饰符,其允许从任何地方对它进行访问。
下面的这个例子对这些修饰符的使用方式进行了具体的演示。有4个类,分别属于两个不同的包over.here和over.there,这4个类都引用了其中类Accessible中声明的成员变量: