访问权限控制
访问权限控制主要是为了解决类库开发的问题,类库的代码往往需要重构,但是类库的使用者却需要代码保持不变,为此,对于类库中的代码,使用者的访问应该是受限制的,他应该只能访问部分需要公开的代码,比如所谓的API。
因此程序代码需要约定代码的访问权限。在java中,访问权限从大到小为:public, protected, 包访问权限(没有关键词), private. 其中包是库的基本单元。
包:库单元
包是通过名字空间组织的一组类。
通过包可以很方便的把实现某一功能的类组织起来,在类文件(.java)中通过 package package_name
指定类所在的包,通过import
来导入。比如import java.util.ArrayList
导入java.util
包下的ArrayList
类。
代码组织
编译一个.java文件时,生成一个同名的.class文件,其中.java文件必须保证只有一个public的类和任意数量的非public类。
通过package指定所在的包
1
2
3
4
5// access/mypackage/MyClass.java
package access.mypackage;
Class MyClass{
// ...
}通过import导入
1
2
3
4
5
6
7// access/ImportedMyClass.java
import access.mypackage.MyClass;
Class ImportedMyClass{
public static void main(String[] args){
MyClass m = new MyClass();
}
}
包命名与包查找
上文通过package指定了包,并通过import导入包中的类,那么import是如何找到这个类呢?
首先,需要给包一个独一无二的名字,最好的办法是通过域名, 比如package com.ajm
。而包文件所处的路径应当与域名相同,这样才能通过包的名字查找。package com.ajm
对应的代码目录结构是{src}/com/ajm/
其中src
是包所在的系统路径。
import只是在.java文件中指明要包含的包,但是java是如何查找并导入的呢?实际上,在javac
、java
命令中,我们需要用-CLASSPATH
指定包的路径。
java解释器的运行过程如下:首先找出环境变量CLASSPATH
(通过操作系统设置), CLASSPATH
包含了一个或多个目录,是查找.class文件的根目录;然后将.换成反斜杠,比如import access.mypackage.MyClass
换成access/mypackage/
, 在CLASSPATH
中查找这个子目录,然后找出MyClass.class文件。
访问权限修饰词
public
所有人都可以访问,用于提供给类库使用者的接口。1
2
3
4
5
6
7
8// access/dessert/Cookie.java
package access.dessert;
public class Cookie{
public Cookie(){
System.out.println("Cookie Constructor");
}
void bite(){ System.out.println("bite"); } // 不加修饰词,默认包访问权限
}
1 | // access/Dinner.java |
如上,Cookie在包access.dessert
中,但是在这个包之外的Dinner
却可以访问Cookie的public构造器。
包访问权限
包访问权限是默认的访问权限,不需要关键词修饰,同一个包内的各个类可以互相访问1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// access/Dinner1.java
package access;
import access.dessert.*;
public class Dinner1{
public static void main(String[] args){
Cookie cookie = new Cookie(); // 可以访问
cookie.bite(); // 不能访问
}
}
// access/dessert/Dinner2.java
package access.dessert;
public class Dinner2{
public static void main(String[] args){
Cookie cookie = new Cookie(); // 可以访问
cookie.bite(); // 可以访问
}
}
protected
包可以访问,包以外的继承类可以访问。有些方法或域想要让包外的派生类可以使用,但是如果用public修饰,所有人都可以使用,为了解决这个问题,引入protected.
1 | //: access/ChocolateChip.java |
例如ChocolateChip类继承自Cookie,想要使用bite()方法,但是由于在包access.dessert
之外,无法访问。为了解决这个问题,可以将bite()方法改为protected,如下:1
2
3
4
5
6
7
8// access/dessert/Cookie.java
package access.dessert;
public class Cookie{
public Cookie(){
System.out.println("Cookie Constructor");
}
protected void bite(){ System.out.println("bite"); } // 包内类和派生类可以访问
}
private
只有类内部可以访问
注意
class 只能是public 或 包访问权限的,因为protected, private作用在class上没有意义。因此,在一个.java文件中,希望暴露给所有人使用的类,用public; 只是用于包内的辅助类,默认包权限。
单例模式
访问控制的一个很好的例子是单例设计模式。有时候我们想要某个类只能生成一个对象实例。在实际应用中,有时候系统只需要拥有一个全局对象,比如服务器程序中,服务器的配置信息存放在一个文件中,可以通过一个单例对象
读取这个文件信息,其他对象可以通过这个单例对象获取这些信息。
为了实现这个目的,显然,该类的构造器只能是私有的,否则就可以通过new
创建对象了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// singleton(饿汉模式:类加载时生成对象)
public class Singleton{
private static final Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
// singleton(懒汉模式)
public class Singleton{
private static Singleton INSTANCE = null;
private Singleton(){}
public static Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
但是,以上的单例实现不是线程安全的,getInstance方法如果被两个线程同时访问,都因为INSTANCE == null
而创建实例,就会产生两个实例。