# 5.2 Java访问指示符
针对类内每个成员的每个定义,Java访问指示符`public`,`protected`以及`private`都置于它们的最前面——无论它们是一个数据成员,还是一个方法。每个访问指示符都只控制着对那个特定定义的访问。这与C++存在着显著不同。在C++中,访问指示符控制着它后面的所有定义,直到又一个访问指示符加入为止。
通过千丝万缕的联系,程序为所有东西都指定了某种形式的访问。在后面的小节里,大家要学习与各类访问有关的所有知识。首次从默认访问开始。
## 5.2.1 “友好的”
如果根本不指定访问指示符,就象本章之前的所有例子那样,这时会出现什么情况呢?默认的访问没有关键字,但它通常称为“友好”(Friendly)访问。这意味着当前包内的其他所有类都能访问“友好的”成员,但对包外的所有类来说,这些成员却是“私有”(Private)的,外界不得访问。由于一个编译单元(一个文件)只能从属于单个包,所以单个编译单元内的所有类相互间都是自动“友好”的。因此,我们也说友好元素拥有“包访问”权限。
友好访问允许我们将相关的类都组合到一个包里,使它们相互间方便地进行沟通。将类组合到一个包内以后(这样便允许友好成员的相互访问,亦即让它们“交朋友”),我们便“拥有”了那个包内的代码。只有我们已经拥有的代码才能友好地访问自己拥有的其他代码。我们可认为友好访问使类在一个包内的组合显得有意义,或者说前者是后者的原因。在许多语言中,我们在文件内组织定义的方式往往显得有些牵强。但在Java中,却强制用一种颇有意义的形式进行组织。除此以外,我们有时可能想排除一些类,不想让它们访问当前包内定义的类。
对于任何关系,一个非常重要的问题是“谁能访问我们的‘私有’或`private`代码”。类控制着哪些代码能够访问自己的成员。没有任何秘诀可以“闯入”。另一个包内推荐可以声明一个新类,然后说:“嗨,我是Bob的朋友!”,并指望看到Bob的`protected`(受到保护的)、友好的以及`private`(私有)的成员。为获得对一个访问权限,唯一的方法就是:
(1) 使成员成为`public`(公共的)。这样所有人从任何地方都可以访问它。
(2) 变成一个“友好”成员,方法是舍弃所有访问指示符,并将其类置于相同的包内。这样一来,其他类就可以访问成员。
(3) 正如以后引入“继承”概念后大家会知道的那样,一个继承的类既可以访问一个`protected`成员,也可以访问一个`public`成员(但不可访问`private`成员)。只有在两个类位于相同的包内时,它才可以访问友好成员。但现在不必关心这方面的问题。
(4) 提供“访问器/变化器”方法(亦称为“获取/设置”方法),以便读取和修改值。这是OOP环境中最正规的一种方法,也是Java Beans的基础——具体情况会在第13章介绍。
## 5.2.2 `public`:接口访问
使用`public`关键字时,它意味着紧随在`public`后面的成员声明适用于所有人,特别是适用于使用库的客户程序员。假定我们定义了一个名为`dessert`的包,其中包含下述单元(若执行该程序时遇到困难,请参考第3章3.1.2小节“赋值”):
```
//: Cookie.java
// Creates a library
package c05.dessert;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
void foo() { System.out.println("foo"); }
} ///:~
```
请记住,`Cookie.java`必须驻留在名为`dessert`的一个子目录内,而这个子目录又必须位于由`CLASSPATH`指定的`C05`目录下面(`C05`代表本书的第5章)。不要错误地以为Java无论如何都会将当前目录作为搜索的起点看待。如果不将一个`.`作为`CLASSPATH`的一部分使用,Java就不会考虑当前目录。
现在,假若创建使用了`Cookie`的一个程序,如下所示:
```
//: Dinner.java
// Uses the library
import c05.dessert.*;
public class Dinner {
public Dinner() {
System.out.println("Dinner constructor");
}
public static void main(String[] args) {
Cookie x = new Cookie();
//! x.foo(); // Can't access
}
} ///:~
```
就可以创建一个`Cookie`对象,因为它的构造器是`public`的,而且类也是`public`的(公共类的概念稍后还会进行更详细的讲述)。然而,`foo()`成员不可在`Dinner.java`内访问,因为`foo()`只有在`dessert`包内才是“友好”的。
(1) 默认包
大家可能会惊讶地发现下面这些代码得以顺利编译——尽管它看起来似乎已违背了规则:
```
//: Cake.java
// Accesses a class in a separate
// compilation unit.
class Cake {
public static void main(String[] args) {
Pie x = new Pie();
x.f();
}
} ///:~
```
在位于相同目录的第二个文件里:
```
//: Pie.java
// The other class
class Pie {
void f() { System.out.println("Pie.f()"); }
} ///:~
```
最初可能会把它们看作完全不相干的文件,然而`Cake`能创建一个`Pie`对象,并能调用它的`f()`方法!通常的想法会认为`Pie`和`f()`是“友好的”,所以不适用于`Cake`。它们确实是友好的——这部分结论非常正确。但它们之所以仍能在`Cake.java`中使用,是由于它们位于相同的目录中,而且没有明确的包名。Java把象这样的文件看作那个目录“默认包”的一部分,所以它们对于目录内的其他文件来说是“友好”的。
## 5.2.3 `private`:不能接触!
`private`关键字意味着除非那个特定的类,而且从那个类的方法里,否则没有人能访问那个成员。同一个包内的其他成员不能访问`private`成员,这使其显得似乎将类与我们自己都隔离起来。另一方面,也不能由几个合作的人创建一个包。所以`private`允许我们自由地改变那个成员,同时毋需关心它是否会影响同一个包内的另一个类。默认的“友好”包访问通常已经是一种适当的隐藏方法;请记住,对于包的用户来说,是不能访问一个“友好”成员的。这种效果往往能令人满意,因为默认访问是我们通常采用的方法。对于希望变成`public`(公共)的成员,我们通常明确地指出,令其可由客户程序员自由调用。而且作为一个结果,最开始的时候通常会认为自己不必频繁使用`private`关键字,因为完全可以在不用它的前提下发布自己的代码(这与C++是个鲜明的对比)。然而,随着学习的深入,大家就会发现`private`仍然有非常重要的用途,特别是在涉及多线程处理的时候(详情见第14章)。
下面是应用了`private`的一个例子:
```
//: IceCream.java
// 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`,所以可防止对这个类的继承(这是第6章要重点讲述的主题)。
若确定一个类只有一个“助手”方法,那么对于任何方法来说,都可以把它们设为`private`,从而保证自己不会误在包内其他地方使用它,防止自己更改或删除方法。将一个方法的属性设为`private`后,可保证自己一直保持这一选项(然而,若一个引用被设为`private`,并不表明其他对象不能拥有指向同一个对象的`public`引用。有关“别名”的问题将在第12章详述)。
## 5.2.4 `protected`:“友好的一种”
`protected`(受到保护的)访问指示符要求大家提前有所认识。首先应注意这样一个事实:为继续学习本书一直到继承那一章之前的内容,并不一定需要先理解本小节的内容。但为了保持内容的完整,这儿仍然要对此进行简要说明,并提供相关的例子。
`protected`关键字为我们引入了一种名为“继承”的概念,它以现有的类为基础,并在其中加入新的成员,同时不会对现有的类产生影响——我们将这种现有的类称为“基类”或者“基本类”(Base Class)。亦可改变那个类现有成员的行为。对于从一个现有类的继承,我们说自己的新类“扩展”(`extends`)了那个现有的类。如下所示:
```
class Foo extends Bar {
```
类定义剩余的部分看起来是完全相同的。
若新建一个包,并从另一个包内的某个类里继承,则唯一能够访问的成员就是原来那个包的`public`成员。当然,如果在相同的包里进行继承,那么继承获得的包能够访问所有“友好”的成员。有些时候,基类的创建者喜欢提供一个特殊的成员,并允许访问派生类。这正是`protected`的工作。若往回引用5.2.2小节“`public`:接口访问”的那个`Cookie.java`文件,则下面这个类就不能访问“友好”的成员:
```
//: ChocolateChip.java
// Can't access friendly member
// in another class
import c05.dessert.*;
public class ChocolateChip extends Cookie {
public ChocolateChip() {
System.out.println(
"ChocolateChip constructor");
}
public static void main(String[] args) {
ChocolateChip x = new ChocolateChip();
//! x.foo(); // Can't access foo
}
} ///:~
```
对于继承,值得注意的一件有趣的事情是倘若方法`foo()`存在于类`Cookie`中,那么它也会存在于从`Cookie`继承的所有类中。但由于`foo()`在外部的包里是“友好”的,所以我们不能使用它。当然,亦可将其变成`public`。但这样一来,由于所有人都能自由访问它,所以可能并非我们所希望的局面。若象下面这样修改类`Cookie`:
```
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void foo() {
System.out.println("foo");
}
}
```
那么仍然能在包`dessert`里“友好”地访问`foo()`,但从`Cookie`继承的其他东西亦可自由地访问它。然而,它并非公共的(`public`)。
- Java 编程思想
- 写在前面的话
- 引言
- 第1章 对象入门
- 1.1 抽象的进步
- 1.2 对象的接口
- 1.3 实现方案的隐藏
- 1.4 方案的重复使用
- 1.5 继承:重新使用接口
- 1.6 多态对象的互换使用
- 1.7 对象的创建和存在时间
- 1.8 异常控制:解决错误
- 1.9 多线程
- 1.10 永久性
- 1.11 Java和因特网
- 1.12 分析和设计
- 1.13 Java还是C++
- 第2章 一切都是对象
- 2.1 用引用操纵对象
- 2.2 所有对象都必须创建
- 2.3 绝对不要清除对象
- 2.4 新建数据类型:类
- 2.5 方法、参数和返回值
- 2.6 构建Java程序
- 2.7 我们的第一个Java程序
- 2.8 注释和嵌入文档
- 2.9 编码样式
- 2.10 总结
- 2.11 练习
- 第3章 控制程序流程
- 3.1 使用Java运算符
- 3.2 执行控制
- 3.3 总结
- 3.4 练习
- 第4章 初始化和清除
- 4.1 用构造器自动初始化
- 4.2 方法重载
- 4.3 清除:收尾和垃圾收集
- 4.4 成员初始化
- 4.5 数组初始化
- 4.6 总结
- 4.7 练习
- 第5章 隐藏实现过程
- 5.1 包:库单元
- 5.2 Java访问指示符
- 5.3 接口与实现
- 5.4 类访问
- 5.5 总结
- 5.6 练习
- 第6章 类复用
- 6.1 组合的语法
- 6.2 继承的语法
- 6.3 组合与继承的结合
- 6.4 到底选择组合还是继承
- 6.5 protected
- 6.6 累积开发
- 6.7 向上转换
- 6.8 final关键字
- 6.9 初始化和类装载
- 6.10 总结
- 6.11 练习
- 第7章 多态性
- 7.1 向上转换
- 7.2 深入理解
- 7.3 覆盖与重载
- 7.4 抽象类和方法
- 7.5 接口
- 7.6 内部类
- 7.7 构造器和多态性
- 7.8 通过继承进行设计
- 7.9 总结
- 7.10 练习
- 第8章 对象的容纳
- 8.1 数组
- 8.2 集合
- 8.3 枚举器(迭代器)
- 8.4 集合的类型
- 8.5 排序
- 8.6 通用集合库
- 8.7 新集合
- 8.8 总结
- 8.9 练习
- 第9章 异常差错控制
- 9.1 基本异常
- 9.2 异常的捕获
- 9.3 标准Java异常
- 9.4 创建自己的异常
- 9.5 异常的限制
- 9.6 用finally清除
- 9.7 构造器
- 9.8 异常匹配
- 9.9 总结
- 9.10 练习
- 第10章 Java IO系统
- 10.1 输入和输出
- 10.2 增添属性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File类
- 10.5 IO流的典型应用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 压缩
- 10.9 对象序列化
- 10.10 总结
- 10.11 练习
- 第11章 运行期类型识别
- 11.1 对RTTI的需要
- 11.2 RTTI语法
- 11.3 反射:运行期类信息
- 11.4 总结
- 11.5 练习
- 第12章 传递和返回对象
- 12.1 传递引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只读类
- 12.5 总结
- 12.6 练习
- 第13章 创建窗口和程序片
- 13.1 为何要用AWT?
- 13.2 基本程序片
- 13.3 制作按钮
- 13.4 捕获事件
- 13.5 文本字段
- 13.6 文本区域
- 13.7 标签
- 13.8 复选框
- 13.9 单选钮
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 视窗化应用
- 13.16 新型AWT
- 13.17 Java 1.1用户接口API
- 13.18 可视编程和Beans
- 13.19 Swing入门
- 13.20 总结
- 13.21 练习
- 第14章 多线程
- 14.1 反应灵敏的用户界面
- 14.2 共享有限的资源
- 14.3 堵塞
- 14.4 优先级
- 14.5 回顾runnable
- 14.6 总结
- 14.7 练习
- 第15章 网络编程
- 15.1 机器的标识
- 15.2 套接字
- 15.3 服务多个客户
- 15.4 数据报
- 15.5 一个Web应用
- 15.6 Java与CGI的沟通
- 15.7 用JDBC连接数据库
- 15.8 远程方法
- 15.9 总结
- 15.10 练习
- 第16章 设计模式
- 16.1 模式的概念
- 16.2 观察器模式
- 16.3 模拟垃圾回收站
- 16.4 改进设计
- 16.5 抽象的应用
- 16.6 多重分发
- 16.7 访问器模式
- 16.8 RTTI真的有害吗
- 16.9 总结
- 16.10 练习
- 第17章 项目
- 17.1 文字处理
- 17.2 方法查找工具
- 17.3 复杂性理论
- 17.4 总结
- 17.5 练习
- 附录A 使用非JAVA代码
- 附录B 对比C++和Java
- 附录C Java编程规则
- 附录D 性能
- 附录E 关于垃圾收集的一些话
- 附录F 推荐读物