《On Java 中文版 基础卷》阅读笔记 实现隐藏 11
7.2 Java 访问权限修饰符
Java 访问权限修饰符包括 public、protected 和 private,它们放在类中成员定义的前面,包括字段和方法。
每个访问权限修饰符仅控制对该特定定义的访问。
java 规定,一个 java 文件可以有多个类,但只能有一个 public 修饰的类。

如果不提供访问权限修饰符,则表示 "包访问权限"。
因此,无论如何,任何事物都有某些形式的访问控制。
在接下来的几节里,你将了解到各种访问权限。
7.2.1 包访问权限
本章之前的所有示例都只使用了 public 修饰符,或者没有使用修饰符(即默认访问权限)。
默认访问权限没有关键字,通常称为包访问权限(有时称为 "友好访问权限")。
这意味着当前包中的所有其他类都可以访问该成员。
对于此包之外的所有类,该成员显示为 private。
一个编译单元(即一个 java 文件)只能属于一个包,所以一个编译单元中的所有类都可以通过包访问权限来相互访问。
一个 java 文件是一个编译单元,一个 java 文件中可以包含多个类 class,只能有一个 public 修饰的类 class。
包访问权限将相关的类分组到一个包中,以便它们可以轻松地交互。
包中的类可以访问该包里具有包访问权限的成员,因此你 "拥有" 了该包中的代码。
只有你拥有的代码才能对你拥有的其他代码进行包访问,这是合理的。
包访问权限是将类分组到一个包中的原因之一。
在许多语言里,你可以任意组织文件中的定义方式,
但在 Java 中,你必须以合理的方式组织它们。
另外,如果有的类不应该访问当前包中定义的类的话,你可能需要排除它。
类控制着哪些代码可以访问其成员。
来自另一个包的代码不能只是因为说了一句 "嗨,我是 Bob 的朋友",
就能看到 Bob 的 protected、包访问权限和 private 成员。
授予成员访问权限的唯一方法如下所示。
1.将成员设为 public,这样任何地方的任何人都可以访问它。
2.通过去掉访问权限修饰符来授予成员包访问权限,并将其他类放在同一个包中,这样该包中的其他类就可以访问该成员了。
3.正如你在第 8 章会看到的,当引入继承时,**通过继承而来的类(子类)可以访问父类的 protected 成员以及 public 成员(但不能访问 private 成员)。只有当两个类在同一个包中时,它才可以访问父类的包访问权限成员。**但现在不必担心继承和 protected。
4.提供可以读取和更改值的访问器 (accessor) 和修改器 (mutator) 方法(也称为 "get/set" 方法)。
7.2.2 public:接口访问权限
当使用 public 关键字时,就意味着紧跟在 public 后面的成员声明对于所有人都是可用的,尤其是对于使用该库的客户程序员。
假设你定义了一个包含以下编译单元的 dessert 包:
java
// hiding/dessert/Cookie.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Creates a library
package hiding.dessert;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
void bite() { System.out.println("bite"); }
}
记住,Cookie.java 生成的类文件必须置于名为 dessert 的子目录中,该子目录位于 hiding 目录下(表示本书的第 7 章),
而 hiding 目录必须位于 CLASSPATH 指定的路径之一里。
不要错误地认为 Java 会始终将当前目录视为搜索的起点之一。
如果 CLASSPATH 中没有指定 "." 作为路径之一,Java 就不会在当前目录里查找。
现在可以创建一个程序来使用 Cookie 了:
java
package hiding;
// hiding/Dinner.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Uses the library
import hiding.dessert.*;
public class Dinner {
public static void main(String[] args) {
Cookie x = new Cookie();
//- x.bite(); // Can't access
}
}
/* Output:
Cookie constructor
*/
你可以创建一个 Cookie 对象,因为它的构造函数是 public 的,而且类也是 public 的**(稍后将进一步介绍 public 类的概念)**。
然而,bite() 方法在 Dinner.java 中是不可访问的,因为 bite() 只提供了在包 dessert 中的访问,所以编译器会阻止你使用它。
默认包
你可能会惊讶地发现以下代码可以编译,尽管它看起来似乎违反了规则,没有指明 package。
java
// hiding/Cake.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Accesses a class in a separate compilation unit
class Cake {
public static void main(String[] args) {
Pie x = new Pie();
x.f();
}
}
/* Output:
Pie.f()
*/
下面的代码是处于相同目录中的第二个文件:
java
// hiding/Pie.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// The other class
class Pie {
void f() { System.out.println("Pie.f()"); }
}

乍一看这两个文件好像完全不相关,但 Cake 能够创建一个 Pie 对象并调用它的 f() 方法(注意 CLASSPATH 中必须有 "." 才能编译,没有配置 CLASSPATH 也能编译)。
你通常可能会认为 Pie 和 f() 具有包访问权限,因此不能用于 Cake。它们确实有包访问权------这是正确的。
它们在 Cake.java 中可用是因为它们都在同一目录中,并且没有明确的包名 。
Java 将这样的文件看作属于该目录的 "默认包" 的隐含部分,因此它们为该目录中所有其他文件提供了包访问权限。
7.2.3 private:你无法访问它
private 关键字意味着,除了包含该成员的类之外,其他任何类都不能访问,当然在该类的方法内部也能访问。
同一个包中的其他类无法访问 private 成员,因此这就好像你故意将类与自己隔离。
不过从另一方面来说,一个包由几个人合作创建的可能性也并非不存在。
使用 private 的话,就可以自由更改该成员,而不必担心它会影响同一个包中的另一个类。
默认的包访问权限通常已经提供了足够的隐藏。
记住,使用该类的客户程序员是无法访问包访问权限成员的。
这是比较合理的,因为默认的包访问权限是你通常使用的访问权限(如果你忘记添加任何访问控制,则会默认获得该访问权限)。
因此,你通常会考虑的是,哪些成员需要公开访问权限来给客户程序员调用。
最初你可能认为自己不会经常使用 private 关键字,因为没有它好像也可以正常工作。
但是,坚持使用 private 是十分重要的,尤其在涉及多线程的情况下(正如你将在进阶卷第 5 章中看到的那样)。
下面是一个使用 private 的例子:
java
package hiding;
// hiding/IceCream.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Demonstrates "private" keyword
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
public class IceCream {
public static void main(String[] args) {
//- Sundae x = new Sundae();
Sundae x = Sundae.makeASundae();
}
}
上面的示例显示了一个 private 大显身手的场景:控制对象的创建方式,并防止特定的构造器(或所有构造器)被调用。
在这个例子中,你不能通过它的构造器来创建一个 Sundae 对象;相反,你必须调用 makeASundae() 方法来做这件事。
只要能确定是类的 "辅助" 方法,这个方法就可以设为 private,以确保在包的其他地方不会意外地使用它,从而让自己无法再更改或删除。
将方法指定为 private 让你保留了这些选择。
对于类中的 private 字段也是如此。
除非你必须要公开底层实现(可能性比你以为的要小),否则就将字段设为 private。
然而,仅仅因为一个对象的引用在类中是 private 的,并不意味着其他对象不能拥有对同一个对象的public引用。
(请参阅进阶卷第 2 章了解别名问题。)
7.2.4 protected:继承访问权限
要理解 protected 访问权限修饰符,就需要提前做些准备。
首先,注意在第 8 章介绍继承之前,你即使不理解这一节的内容也可以继续阅读本书。
但为了完整起见,这里是 protected 的一个简要说明和相关示例。
protected 关键字处理的是继承的概念,它利用一个现有类------我们叫作基类 (base class) ------并在不修改现有类的情况下向该类添加新成员,还可以改变该类现有成员的行为。
为了继承一个类,你需要声明自己的新类扩展 (extends) 了现有类,如下所示:
java
class Foo extends Bar{
类定义的其余部分看起来都是一样的。
如果你创建了一个新包,并需要继承另一个包中的类,那么唯一可以访问的成员是这个类的 public 成员。
当然如果继承同一个包中的类,就可以操作所有的包访问权限成员了。
有时候基类的创建者想要把特定成员的访问权限赋给子类,而不是所有的类,这时候 protected 就可以发挥作用了。
protected 还提供了包访问权,也就是说,同一包中的其他类也可以访问 protected 元素。
如果你回顾文件 Cookie.java,就会知道下面的类不能调用包访问权限成员 bite():
java
package hiding;
// hiding/ChocolateChip.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Can't use package-access member from another package
import hiding.dessert.*;
public class ChocolateChip extends Cookie {
public ChocolateChip() {
System.out.println("ChocolateChip constructor");
}
public void chomp() {
//- bite(); // Can't access bite
}
public static void main(String[] args) {
ChocolateChip x = new ChocolateChip();
x.chomp();
}
}
/* Output:
Cookie constructor
ChocolateChip constructor
*/

如果类 Cookie 中存在一个方法 bite(),那么这个方法也存在于任何继承 Cookie 的类中。
但是 bite() 只具有包访问权限并且位于另一个包中,因此无法在当前包中使用它。
你可以将其修改为 public,但这样的话每个人就都可以访问它了,这也许不是你想要的。
如果按如下方式更改类 Cookie:
java
// hiding/cookie2/Cookie.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package hiding.cookie2;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void bite() {
System.out.println("bite");
}
}
这样任何继承 Cookie 的类都可以访问 bite():
java
package hiding;
// hiding/ChocolateChip2.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
import hiding.cookie2.*;
public class ChocolateChip2 extends Cookie {
public ChocolateChip2() {
System.out.println("ChocolateChip2 constructor");
}
public void chomp() { bite(); } // Protected method
public static void main(String[] args) {
ChocolateChip2 x = new ChocolateChip2();
x.chomp();
}
}
/* Output:
Cookie constructor
ChocolateChip2 constructor
bite
*/
这时尽管 bite() 也有包访问权限,但它不是 public 的。
7.2.5 包访问权限与公共构造器
当定义一个具有包访问权限的类时,你可以给它一个 public 构造器,此时编译器不会提示任何错误:
java
// hiding/packageaccess/PublicConstructor.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
package hiding.packageaccess;
class PublicConstructor {
public PublicConstructor() {}
}
如果运行 Checkstyle 这样的工具,它会指出这是虚假陈述,并且从技术上讲这样定义类是错误的。
你实际上无法从包外部访问这个所谓的 public 构造器:
java
package hiding;
// hiding/CreatePackageAccessObject.java
// (c)2021 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// {WillNotCompile}
import hiding.packageaccess.*;
public class CreatePackageAccessObject {
public static void main(String[] args) {
new PublicConstructor();
}
}

如果手动编译此文件,你将看到一个编译器错误消息:

因此,在只有包访问权限的类中声明一个 public 构造器,实际上并不会使这个构造器成为 public 的,编译器应该在声明处将其标记为一个错误。