章六 访问权限控制
前言
本章的主要内容为访问控制,Java中共有四种访问控制修饰符,访问权限从大到小分别是public,protected,包访问权限和private。对于类成员,包括域,方法,构造器以及后面即将介绍的内部类(内部接口),含上述所有四种访问控制权限。对于类(接口)本身,只含有public和包访问权限这两种访问控制权限。
访问权限控制的初衷是分开类的接口和实现。这样一来,对于类的使用者(客户端程序员)来说,只需了解类的接口即可使用类进行开发。二来,对类的设计者而言,可以在不改变类的接口的情况下,对类的实现进行重构,从而在后期提高类的性能。因此,访问权限可以被视为类创建者和类使用者的协议。OOP的三大特征,封装,继承和多态。其中封装可以分为两层理解:第一层是将方法和数据封装成类,第二层是通过访问控制隐藏具体实现。至于继承和多态的内容,将在下面两章详解。
初识访问权限,一定要将对类本身的访问权限和对类成员的访问权限分开,不然会感到迷惑。Java设置访问权限时,将访问权限设置在了类内部,类成员的访问权限是要理解的重点。对于类这一层而言,Java通过包来组织内聚的类库,对于包(类库)内的每个.java文件,最多只有一个public类且与文件名相同,文件中其他类不可加public修饰符,即为包访问权限,这些类被置于默认包中,包外不可见。
1. 包:库单元
包内含一组类,它们在同一名字空间下被组织在一起,如Java SE中的工具包java.util,及其包中的成员类java.util.ArrayList。由此可见,包是Java中管理名字空间的机制,类似C++中的命名空间。Java中每一个.java文件被称为一个编译单元,每个编译单元只能有一个与文件名完全相同的public class类以及若干包访问权限的类。
代码组织
编译一个.java文件后,文件中的每个类都会生成.class的输出文件。Java可运行程序是一组可以打包并压缩为Java文档文件(JAR)的.class文件。Java解释器负责这些文件的查找、装载和解释。包中的文件必须在文件中除注释外的第一条程序代码使用package语句,用以声明该编译单元出于该包内。其他包中必须通过import语句或指定全名来使用该类。
包名
一个包中可以包括多个.class文件,为避免混乱,可以将同一包中的所有.class文件置于同一目录下。这样可以解决两个问题:命名和查找。第一点,Java中通过反转域名来对包进行命名,这样可以保证包名的唯一性。第二点,Java把包名分解为机器上的目录,方便查找。
若import两个包中含有同名文件时,则需要给出全名以避免冲突。
定制工具库
此时,可以通过创建自己工具包来减少或消除重复代码,如本书中作者使用的net.mindview的三个包,可以通过使用static import语句来直接使用类的static方法。
条件编译
Java没有C的条件编译功能。该功能主要用于跨平台问题和调试。Java有JVM解决跨平台问题。对于调试,Java可以通过修改import package语句在调试版和开发版直接切换,实现调试功能。
使用包的忠告
无论何时创建包,都已经在给定包的名称的时候隐含指定了目录结构。
2. Java访问权限修饰符
Java中,共有三个关键字用以修饰访问权限:public,protected和private;如果不加修饰,则为默认访问权限(包访问权限);综上,Java中共有4个不同的访问权限。需要说明的是,Java访问权限修饰符置于类的每个成员(域,方法,内部类)中,每个访问权限修饰符仅控制其所修饰的特定成员的访问权。下面,以访问权限从小到大的顺序介绍Java中4个不同的访问权限。
private:无法访问
private修饰符表示,除了包含该成员的类之外,其他任何类都无法访问该成员。通过隔离,类的设计者可以方便修改该成员。下面介绍private修饰符几个常用的应用场景:
- 控制对象创建:将类的一个或多个构造器设为private,可以通过设立public方法调用private构造器来创建对象。
- 助手方法:通过将助手方法设为private,防止包内其他地方误用,也方便自己修改。
- 域:同C++一样,除非必须公开底层细节,Java的域应该尽量设定为private,可以设置access routines。
包访问权限
当类成员不加访问修饰符时,则说明该成员为包访问权限。包访问权限把类群聚在一个包里的做法提供了意义和理由。类控制着那些代码有权访问自己的成员。要想取得对某成员的访问权限的方法如下:
- 使该成员成为public,这样,谁都可以访问它。哪怕其类是默认访问权限,也可通过继承机制来访问,程序示例如下:
class PackageOnly{
public void accessible(){
System.out.println("This method can be accessed anywhere, anyone");
}
}
public class Public{
//this public class inherits the method accessible
//now developers can get access to accessible through this class
}
- 该成员为protected,那么该类的子类(不管在什么地方)以及包内的其他类都可以访问该成员
- 该成员为包访问权限,将需要访问的类与其放置在同一个包中,则包内的其他类可以访问该成员
- 该成员为private,通过access routines访问和修改该成员。
protected:继承访问权限
protected访问修饰符的访问权限略大于包访问权限,它将被修饰成员的访问权限从同包扩展到了不同包的子类。对于包内的其他类,其访问权限等同于包访问权限。
public:接口访问权限
public被用来修饰能被随意访问的成员,也被称为类的接口。
这里需要补充的一点是,对于一个编译单元中的非public类,其被视为在包的默认包中,可以访问其他类具有包访问权限的成员。
3.类的接口与实现
访问权限的控制是对具体实现的隐藏,是OOP中封装概念的一大支柱。Java将访问控制权限用于修饰类成员,一方面设定了类使用者能使用和不能使用的界限;另一方面,将接口和具体实现分离。这样,类的使用者可以通过阅读API快速上手,而不必关系功能实现的细节。
需要说明的是,javadoc所提供的注释文档的功能降低了程序代码可读性对客户端程序员的重要性。可以采用一种以访问权限从高到低的顺序定义和实现类成员。
4.类的访问权限
类的访问权限只有两种:public和包访问权限。这里,有一些注意事项:
- 每个编译单元(文件)都只能有一个public类。即每个编译单元都有单一的公共接口,用public类表示。
- public类的名称必须完全与含有该编译单元的文件名相匹配,包括大小写。
- 编译单元内不带public类也是可以的。但main()必须是public static的。
除了显示的public和包访问权限外,类还可以通过设置其所有构造器为private来实现无法访问的效果。这种情况下有一种例外,可以通过类的static成员创造该类实例。这样,通过使用static和private可以实现一种常见的设计模式——单例模式,代码示例如下:
class Singleton{
private Singleton() {
//do nothing here
}
private static Singleton single = new Singleton();
public static Singleton getSingleton(){
return single;
}
}
如上述代码所示,通过设置类的构造器为private,使得只能在类内static成员内创建该类示例。通过将single设置private,使得外部只能通过getSingleton()接口获取single对象,实现了封装。