📄 chapter5.htm
字号:
Vector v = new Vector();<br>
它引用的到底是哪个Vector类呢?编译器对这个问题没有答案,读者也不可能知道。所以编译器会报告一个错误,强迫我们进行明确的说明。例如,假设我想使用标准的Java
Vector,那么必须象下面这样编程:<br>
java.util.Vector v = new java.util.Vector();<br>
由于它(与CLASSPATH一起)完整指定了那个Vector的位置,所以不再需要import
java.util.*语句,除非还想使用来自java.util的其他东西。<br>
<br>
5.1.2 自定义工具库<br>
掌握前述的知识后,接下来就可以开始创建自己的工具库,以便减少或者完全消除重复的代码。例如,可为System.out.println()创建一个别名,减少重复键入的代码量。它可以是名为tools的一个包(package)的一部分:<br>
<br>
197-198页程序<br>
<br>
所有不同的数据类型现在都可以在一个新行输出(P.rintln()),或者不在一个新行输出(P.rint())。<br>
大家可能会猜想这个文件所在的目录必须从某个CLASSPATH位置开始,然后继续com/bruceeckel/tools。编译完毕后,利用一个import语句,即可在自己系统的任何地方使用P.class文件。如下所示:<br>
<br>
198页下程序<br>
<br>
所以从现在开始,无论什么时候只要做出了一个有用的新工具,就可将其加入tools目录(或者自己的个人util或tools目录)。<br>
<br>
1. CLASSPATH的陷阱<br>
P.java文件存在一个非常有趣的陷阱。特别是对于早期的Java实现方案来说,类路径的正确设定通常都是很困难的一项工作。编写这本书的时候,我引入了P.java文件,它最初看起来似乎工作很正常。但在某些情况下,却开始出现中断。在很长的时间里,我都确信这是Java或其他什么在实现时一个错误。但最后,我终于发现在一个地方引入了一个程序(即第17章要说明的CodePackager.java),它使用了一个不同的类P。由于它作为一个工具使用,所以有时候会进入类路径里;另一些时候则不会这样。但只要它进入类路径,那么假若执行的程序需要寻找com.bruceeckel.tools中的类,Java首先发现的就是CodePackager.java中的P。此时,编译器会报告一个特定的方法没有找到。这当然是非常令人头疼的,因为我们在前面的类P里明明看到了这个方法,而且根本没有更多的诊断报告可为我们提供一条线索,让我们知道找到的是一个完全不同的类(那甚至不是public的)。<br>
乍一看来,这似乎是编译器的一个错误,但假若考察import语句,就会发现它只是说:“在这里可能发现了P”。然而,我们假定的是编译器搜索自己类路径的任何地方,所以一旦它发现一个P,就会使用它;若在搜索过程中发现了“错误的”一个,它就会停止搜索。这与我们在前面表述的稍微有些区别,因为存在一些讨厌的类,它们都位于包内。而这里有一个不在包内的P,但仍可在常规的类路径搜索过程中找到。<br>
如果您遇到象这样的情况,请务必保证对于类路径的每个地方,每个名字都仅存在一个类。<br>
<br>
5.1.3 利用导入改变行为<br>
Java已取消的一种特性是C的“条件编译”,它允许我们改变参数,获得不同的行为,同时不改变其他任何代码。Java之所以抛弃了这一特性,可能是由于该特性经常在C里用于解决跨平台问题:代码的不同部分根据具体的平台进行编译,否则不能在特定的平台上运行。由于Java的设计思想是成为一种自动跨平台的语言,所以这种特性是没有必要的。<br>
然而,条件编译还有另一些非常有价值的用途。一种很常见的用途就是调试代码。调试特性可在开发过程中使用,但在发行的产品中却无此功能。Alen
Holub(www.holub.com)提出了利用包(package)来模仿条件编译的概念。根据这一概念,它创建了C“断定机制”一个非常有用的Java版本。之所以叫作“断定机制”,是由于我们可以说“它应该为真”或者“它应该为假”。如果语句不同意你的断定,就可以发现相关的情况。这种工具在调试过程中是特别有用的。<br>
可用下面这个类进行程序调试:<br>
<br>
200页程序<br>
<br>
这个类只是简单地封装了布尔测试。如果失败,就显示出出错消息。在第9章,大家还会学习一个更高级的错误控制工具,名为“违例控制”。但在目前这种情况下,perr()方法已经可以很好地工作。<br>
如果想使用这个类,可在自己的程序中加入下面这一行:<br>
import com.bruceeckel.tools.debug.*;<br>
如欲清除断定机制,以便自己能发行最终的代码,我们创建了第二个Assert类,但却是在一个不同的包里:<br>
<br>
200-201页程序<br>
<br>
现在,假如将前一个import语句变成下面这个样子:<br>
import com.bruceeckel.tools.*;<br>
程序便不再显示出断言。下面是个例子:<br>
<br>
201页中程序<br>
<br>
通过改变导入的package,我们可将自己的代码从调试版本变成最终的发行版本。这种技术可应用于任何种类的条件代码。<br>
<br>
5.1.4 包的停用<br>
大家应注意这样一个问题:每次创建一个包后,都在为包取名时间接地指定了一个目录结构。这个包必须存在(驻留)于由它的名字规定的目录内。而且这个目录必须能从CLASSPATH开始搜索并发现。最开始的时候,package关键字的运用可能会令人迷惑,因为除非坚持遵守根据目录路径指定包名的规则,否则就会在运行期获得大量莫名其妙的消息,指出找不到一个特定的类——即使那个类明明就在相同的目录中。若得到象这样的一条消息,请试着将package语句作为注释标记出去。如果这样做行得通,就可知道问题到底出在哪儿。<br>
<br>
5.2 Java访问指示符<br>
针对类内每个成员的每个定义,Java访问指示符poublic,protected以及private都置于它们的最前面——无论它们是一个数据成员,还是一个方法。每个访问指示符都只控制着对那个特定定义的访问。这与C++存在着显著不同。在C++中,访问指示符控制着它后面的所有定义,直到又一个访问指示符加入为止。<br>
通过千丝万缕的联系,程序为所有东西都指定了某种形式的访问。在后面的小节里,大家要学习与各类访问有关的所有知识。首次从默认访问开始。<br>
<br>
5.2.1 “友好的”<br>
如果根本不指定访问指示符,就象本章之前的所有例子那样,这时会出现什么情况呢?默认的访问没有关键字,但它通常称为“友好”(Friendly)访问。这意味着当前包内的其他所有类都能访问“友好的”成员,但对包外的所有类来说,这些成员却是“私有”(Private)的,外界不得访问。由于一个编译单元(一个文件)只能从属于单个包,所以单个编译单元内的所有类相互间都是自动“友好”的。因此,我们也说友好元素拥有“包访问”权限。<br>
友好访问允许我们将相关的类都组合到一个包里,使它们相互间方便地进行沟通。将类组合到一个包内以后(这样便允许友好成员的相互访问,亦即让它们“交朋友”),我们便“拥有”了那个包内的代码。只有我们已经拥有的代码才能友好地访问自己拥有的其他代码。我们可认为友好访问使类在一个包内的组合显得有意义,或者说前者是后者的原因。在许多语言中,我们在文件内组织定义的方式往往显得有些牵强。但在Java中,却强制用一种颇有意义的形式进行组织。除此以外,我们有时可能想排除一些类,不想让它们访问当前包内定义的类。<br>
对于任何关系,一个非常重要的问题是“谁能访问我们的‘私有’或private代码”。类控制着哪些代码能够访问自己的成员。没有任何秘诀可以“闯入”。另一个包内推荐可以声明一个新类,然后说:“嗨,我是Bob的朋友!”,并指望看到Bob的“protected”(受到保护的)、友好的以及“private”(私有)的成员。为获得对一个访问权限,唯一的方法就是:<br>
(1) 使成员成为“public”(公共的)。这样所有人从任何地方都可以访问它。<br>
(2)
变成一个“友好”成员,方法是舍弃所有访问指示符,并将其类置于相同的包内。这样一来,其他类就可以访问成员。<br>
(3)
正如以后引入“继承”概念后大家会知道的那样,一个继承的类既可以访问一个protected成员,也可以访问一个public成员(但不可访问private成员)。只有在两个类位于相同的包内时,它才可以访问友好成员。但现在不必关心这方面的问题。<br>
(4)
提供“访问器/变化器”方法(亦称为“获取/设置”方法),以便读取和修改值。这是OOP环境中最正规的一种方法,也是Java
Beans的基础——具体情况会在第13章介绍。<br>
<br>
5.2.2 public:接口访问<br>
使用public关键字时,它意味着紧随在public后面的成员声明适用于所有人,特别是适用于使用库的客户程序员。假定我们定义了一个名为dessert的包,其中包含下述单元(若执行该程序时遇到困难,请参考第3章3.1.2小节“赋值”):<br>
<br>
203页下程序<br>
<br>
请记住,Cookie.java必须驻留在名为dessert的一个子目录内,而这个子目录又必须位于由CLASSPATH指定的C05目录下面(C05代表本书的第5章)。不要错误地以为Java无论如何都会将当前目录作为搜索的起点看待。如果不将一个“.”作为CLASSPATH的一部分使用,Java就不会考虑当前目录。<br>
现在,假若创建使用了Cookie的一个程序,如下所示:<br>
<br>
204页上程序<br>
<br>
就可以创建一个Cookie对象,因为它的构建器是public的,而且类也是public的(公共类的概念稍后还会进行更详细的讲述)。然而,foo()成员不可在Dinner.java内访问,因为foo()只有在dessert包内才是“友好”的。<br>
<br>
1. 默认包<br>
大家可能会惊讶地发现下面这些代码得以顺利编译——尽管它看起来似乎已违背了规则:<br>
<br>
204页下程序<br>
<br>
在位于相同目录的第二个文件里:<br>
<br>
205页上程序<br>
<br>
最初可能会把它们看作完全不相干的文件,然而Cake能创建一个Pie对象,并能调用它的f()方法!通常的想法会认为Pie和f()是“友好的”,所以不适用于Cake。它们确实是友好的——这部分结论非常正确。但它们之所以仍能在Cake.java中使用,是由于它们位于相同的目录中,而且没有明确的包名。Java把象这样的文件看作那个目录“默认包”的一部分,所以它们对于目录内的其他文件来说是“友好”的。<br>
<br>
5.2.3 private:不能接触!<br>
private关键字意味着除非那个特定的类,而且从那个类的方法里,否则没有人能访问那个成员。同一个包内的其他成员不能访问private成员,这使其显得似乎将类与我们自己都隔离起来。另一方面,也不能由几个合作的人创建一个包。所以private允许我们自由地改变那个成员,同时毋需关心它是否会影响同一个包内的另一个类。默认的“友好”包访问通常已经是一种适当的隐藏方法;请记住,对于包的用户来说,是不能访问一个“友好”成员的。这种效果往往能令人满意,因为默认访问是我们通常采用的方法。对于希望变成public(公共)的成员,我们通常明确地指出,令其可由客户程序员自由调用。而且作为一个结果,最开始的时候通常会认为自己不必频繁使用private关键字,因为完全可以在不用它的前提下发布自己的代码(这与C++是个鲜明的对比)。然而,随着学习的深入,大家就会发现private仍然有非常重要的用途,特别是在涉及多线程处理的时候(详情见第14章)。<br>
下面是应用了private的一个例子:<br>
<br>
205-206页程序<br>
<br>
这个例子向我们证明了使用private的方便:有时可能想控制对象的创建方式,并防止有人直接访问一个特定的构建器(或者所有构建器)。在上面的例子中,我们不可通过它的构建器创建一个Sundae对象;相反,必须调用makeASundae()方法来实现(注释③)。<br>
<br>
③:此时还会产生另一个影响:由于默认构建器是唯一获得定义的,而且它的属性是private,所以可防止对这个类的继承(这是第6章要重点讲述的主题)。<br>
<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -