友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第29部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
成员。但现在不必关心这方面的问题。
(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。 默认包
大家可能会惊讶地发现下面这些代码得以顺利编译——尽管它看起来似乎已违背了规则:
131
…………………………………………………………Page 133……………………………………………………………
//: Cake。java
// Accesses a class in a separate
// pilation 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();
132
…………………………………………………………Page 134……………………………………………………………
}
} ///:~
这个例子向我们证明了使用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 {
133
…………………………………………………………Page 135……………………………………………………………
public Cookie() {
System。out。println(〃Cookie constructor〃);
}
protected void foo() {
System。out。println(〃foo〃);
}
}
那么仍然能在包dessert 里“友好”地访问 foo(),但从Cookie 继承的其他东西亦可自由地访问它。然而,
它并非公共的(public)。
5。3 接口与实现
我们通常认为访问控制是“隐藏实施细节”的一种方式。将数据和方法封装到类内后,可生成一种数据类
型,它具有自己的特征与行为。但由于两方面重要的原因,访问为那个数据类型加上了自己的边界。第一个
原因是规定客户程序员哪些能够使用,哪些不能。我们可在结构里构建自己的内部机制,不用担心客户程序
员将其当作接口的一部分,从而自由地使用或者“滥用”。
这个原因直接导致了第二个原因:我们需要将接口同实施细节分离开。若结构在一系列程序中使用,但用户
除了将消息发给public 接口之外,不能做其他任何事情,我们就可以改变不属于 public 的所有东西(如
“友好的”、protected 以及private),同时不要求用户对他们的代码作任何修改。
我们现在是在一个面向对象的编程环境中,其中的一个类(class)实际是指“一类对象”,就象我们说“鱼
类”或“鸟类”那样。从属于这个类的所有对象都共享这些特征与行为。“类”是对属于这一类的所有对象
的外观及行为进行的一种描述。
在一些早期 OOP 语言中,如 Simula…67,关键字class 的作用是描述一种新的数据类型。同样的关键字在大
多数面向对象的编程语言里都得到了应用。它其实是整个语言的焦点:需要新建数据类型的场合比那些用于
容纳数据和方法的“容器”多得多。
在Java 中,类是最基本的 OOP 概念。它是本书未采用粗体印刷的关键字之一——由于数量太多,所以会造成
页面排版的严重混乱。
为清楚起见,可考虑用特殊的样式创建一个类:将 public 成员置于最开头,后面跟随protected、友好以及
private 成员。这样做的好处是类的使用者可从上向下依次阅读,并首先看到对自己来说最重要的内容(即
public 成员,因为它们可从文件的外部访问),并在遇到非公共成员后停止阅读,后者已经属于内部实施细
节的一部分了。然而,利用由 javadoc 提供支持的注释文档(已在第 2 章介绍),代码的可读性问题已在很
大程度上得到了解决。
public class X {
public void pub1( ) { /* 。 。 。 */ }
public void pub2( ) { /* 。 。 。 */ }
public void pub3( ) { /* 。 。 。 */ }
private void priv1( ) { /* 。 。 。 */ }
private void priv2( ) { /* 。 。 。 */ }
private void priv3( ) { /* 。 。 。 */ }
private int i;
// 。 。 。
}
由于接口和实施细节仍然混合在一起,所以只是部分容易阅读。也就是说,仍然能够看到源码——实施的细
节,因为它们需要保存在类里面。向一个类的消费者显示出接口实际是“类浏览器”的工作。这种工具能查
找所有可用的类,总结出可对它们采取的全部操作(比如可以使用哪些成员等),并用一种清爽悦目的形式
显示出来。到大家读到这本书的时候,所有优秀的 Java 开发工具都应推出了自己的浏览器。
134
…………………………………………………………Page 136……………………………………………………………
5。4 类访问
在Java 中,亦可用访问指示符判断出一个库内的哪些类可由那个库的用户使用。若想一个类能由客户程序员
调用,可在类主体的起始花括号前面某处放置一个 public 关键字。它控制着客户程序员是否能够创建属于这
个类的一个对象。
为控制一个类的访问,指示符必须在关键字class 之前出现。所以我们能够使用:
public class Widget {
也就是说,假若我们的库名是mylib,那么所有客户程序员都能访问Widget——通过下述语句:
import mylib。Widget;
或者
import mylib。*;
然而,我们同时还要注意到一些额外的限制:
(1) 每个编译单元(文件)都只能有一个public 类。每个编译单元有一个公共接口的概念是由那个公共类表
达出来的。根据自己的需要,它可拥有任意多个提供支撑的“友好”类。但若在一个编译单元里使用了多个
public 类,编译器就会向我们提示一条出错消息。
(2) public类的名字必须与包含了编译单元的那个文件的名字完全相符,甚至包括它的大小写形式。所以对
于Widget 来说,文件的名字必须是Widget。java,而不应是widget。java 或者WIDGET。java。同样地,如果
出现不符,就会报告一个编译期错误。
(3) 可能(但并常见)有一个编译单元根本没有任何公共类。此时,可按自己的意愿任意指定文件名。
如果已经获得了mylib 内部的一个类,准备用它完成由Widget 或者 mylib 内部的其他某些public 类执行的
任务,此时又会出现什么情况呢?我们不希望花费力气为客户程序员编制文档,并感觉以后某个时候也许会
进行大手笔的修改,并将自己的类一起删掉,换成另一个不同的类。为获得这种灵活处理的能力,需要保证
没有客户程序员能够依赖自己隐藏于mylib 内部的特定实施细节。为达到这个目的,只需将public 关键字从
类中剔除即可,这样便把类变成了“友好的”(类仅能在包内使用)。
注意不可将类设成 private (那样会使除类之外的其他东西都不能访问它),也不能设成protected (注释
④)。因此,我们现在对于类的访问只有两个选择:“友好的”或者 public。若不愿其他任何人访问那个
类,可将所有构建器设为private。这样一来,在类的一个static 成员内部,除自己之外的其他所有人都无
法创建属于那个类的一个对象(注释⑤)。如下例所示:
//: Lunch。java
// Demonstrates class access specifiers。
// Make a class effectively private
// with private constructors:
class Soup {
private Soup() {}
// (1) Allow creation via static method:
public static Soup makeSoup() {
return new Soup();
}
// (2) Create a static object and
// return a reference upon request。
// (The 〃Singleton〃 pattern):
private static Soup ps1 = new Soup();
public static Soup access() {
return ps1;
}
public void f() {}
}
class Sandwich { // Uses Lunch
void f() { new Lunch(); }
135
…………………………………………………………Page 137……………………………………………………………
}
// Only one public class allowed per file:
public class Lunch {
void test() {
// Can't do this! Private constructor:
//! Soup priv1 = new Soup();
Soup priv2 = Soup。makeSoup();
Sandwich f1 = new Sandwich();
Soup。access()。f();
}
} ///:~
④:实际上,Java 1。1 内部类既可以是“受到保护的”,也可以是“私有的”,但那属于特别情况。第 7 章
会详细解释这个问题。
⑤:亦可通过从那个类继承来实现。
迄今为止,我们创建过的大多数方法都是要么返回 void,要么返回一个基本数据类型。所以对下述定义来
说:
public static Soup access() {
return psl;
}
它最开始多少会使人有些迷惑。位于方法名(access)前的单词指出方法到底返回什么。在这之前,我们看
到的都是void,它意味着“什么也不返回”(void 在英语里是“虚无”的意思。但亦可返回指向一个对象的
句柄,此时出现的就是这个情况。该方法返回一个句柄,它指向类Soup 的一个对象。
Soup 类向我们展示出如何通过将所有构建器都设为 private,从而防止直接创建一个类。请记住,假若不明
确地至少创建一个构建器,就会自动创建默认构建器(没有自变量)。若自己编写默认构建器,它就不会自
动创建。把它变成 private 后,就没人能为那个类创建一个对象。但别人怎样使用这个类呢?上面的例子为
我们揭示出了两个选择。第一个选择,我们可创建一个static 方法,再通过它创建一个新的 Soup,然后返
回指向它的一个句柄。如果想在返回之前对Soup 进行一些额外的操作,或者想了解准备创建多少个 Soup 对
象(可能是为了限制它们的个数),这种方案无疑是特别有用的。
第二个选择是采用“设计方案”(Design Pattern)技术,本书后面会对此进行详细介绍。通常方案叫作
“独子”,因为它仅允许创建一个对象。类Soup 的对象被创建成Soup 的一个 static private 成员,所以有
一个而且只能有一个。除非通过public 方法access(),否则根本无法访问它。
正如早先指出的那样,如果不针对类的访问设置一个访问指示符,那么它会自动默认为“友好的”。这意味
着那个类的对象可由包内的其他类创建,但不能由包外创建。请记住,对于相同目录内的所有文件,如果没
有明确地进行package 声明,那么它们都默认为那个目录的默认包的一部分。然而,假若那个类一个static
成员的属性是public,那么客户程序员仍然能够访问那个static 成员——即使它们不能创建属于那个类的
一个对象。
5。5 总结
对于任何关系,最重要的一点都是规定好所有方面都必须遵守的界限或规则。创建一个库时,相当于建立了
同那个库的用户(即“客户程序员”)的一种关系——那些用户属于另外的程序员,可能用我们的库自行构
建一个应用程序,或者用我们的库构建一个更大的库。
如果不制订规则,客户程序员就可以随心所欲地操作一个类的所有成员,无论我们本来愿不愿意其中的一些
成员被直接操作。所有东西都在别人面前都暴露无遗。
本章讲述了如何构建类,从而制作出理想的库。首先,我们讲述如何将一组类封装到一个库里。其次,我们
讲述类如何控制对 自己成员的访问。
一般情况下,一个 C 程序项目会在 50K 到 100K 行代码之间的某个地方开始中断。这是由于
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!