一、为什么要访问控制
访问权限的控制常被称为是具体实现的隐藏。把数据和方法包装进类中,以及具体实现的隐藏,常被共同称作是封装。其结果是一种同时带有特征和行为的数据类型。
出于两个很重要的原因,访问权限控制将权限的边界划在了数据类型的内部。第一个原因是要设定客户端程序员可以使用和不可以使用的边界。可以在结构中建立自己的内部机制,而不必担心客户端程序员会偶尔地将内部机制当做是他们可使用的接口的一部分。
这个原因直接引出了第二个原因,即将接口和具体实现进行分离。如果结构是用于一组程序之中,而客户端程序员除了可以向接口发送信息之外什么也不可以做的话,那么久可以随意更改所有不是public的东西(例如default、protected和private的成员),而不会破坏客户端代码。(引自《java编程思想》P120)
二、成员的访问控制权限
Java有四种访问控制权限:public、protected、default、private。
类内部 | 同一个包中的类 | 子类 | 其它包中的类 | |
---|---|---|---|---|
private | yes | |||
default | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
三、包访问权限 Vs Public 构造器
当你定义一个具有包访问权限的类时,你可以在类中定义一个 public 构造器,编译器不会报错:
// hiding/packageaccess/PublicConstructor.java
package hiding.packageaccess;
class PublicConstructor {
public PublicConstructor() {}
}
有一个 Checkstyle 工具,你可以运行命令 gradlew hiding:checkstyleMain 使用它,它会指出这种写法是虚假的,而且从技术上来说是错误的。实际上你不能从包外访问到这个 public 构造器:
// hiding/CreatePackageAccessObject.java
// {WillNotCompile}
import hiding.packageaccess.*;
public class CreatePackageAcessObject {
public static void main(String[] args) {
new PublicConstructor();
}
}
如果你编译下这个类,会得到编译错误信息:
CreatePackageAccessObject.java:6:error:
PublicConstructor is not public in hiding.packageaccess;
cannot be accessed from outside package
new PublicConstructor();
^
1 error
因此,在一个具有包访问权限的类中定义一个 public 的构造器并不能真的使这个构造器成为 public,在声明的时候就应该标记为编译时错误。
五、类访问权限
访问权限修饰符也可以用于确定类库中的哪些类对于类库的使用者是可用的。如果希望某个类可以被客户端程序员使用,就把关键字 public 作用于整个类的定义。这甚至控制着客户端程序员能否创建类的对象。
- 每个编译单元(即每个文件)中只能有一个 public 类。这表示,每个编译单元有一个公共的接口用 public 类表示。该接口可以包含许多支持包访问权限的类。一旦一个编译单元中出现一个以上的 public 类,编译就会报错。
- public 类的名称必须与含有该编译单元的文件名相同,包括大小写。所以对于 Widget 来说,文件名必须是 Widget.java,不能是 widget.java 或 WIDGET.java。再次强调,如果名字不匹配,编译器会报错。
- 虽然不是很常见,但是编译单元内没有 public 类也是可能的。这时可以随意命名文件(尽管随意命名会让代码的阅读者和维护者感到困惑)。
注意:类既不能是 private 的(这样除了该类自身,任何类都不能访问它),也不能是 protected 的。所以对于类的访问权限只有两种选择:包访问权限或者 public。为了防止类被外界访问,可以将所有的构造器声明为 private,这样只有你自己能创建对象