第五章隐藏实施过程

上传人:沈*** 文档编号:135287387 上传时间:2022-08-15 格式:DOC 页数:15 大小:672.50KB
返回 下载 相关 举报
第五章隐藏实施过程_第1页
第1页 / 共15页
第五章隐藏实施过程_第2页
第2页 / 共15页
第五章隐藏实施过程_第3页
第3页 / 共15页
点击查看更多>>
资源描述
第5章隐藏实施过程“进行面向对象的设计时,一项基本的考虑是:如何将发生变化的东西与保持不变的东西分隔开。”这一点对于库来说是特别重要的。那个库的用户(客户程序员)必须能依赖自己使用的那一部分,并知道一 旦新版本的库岀台,自己不需要改写代码。而与此相反,库的创建者必须能自由地进行修改与改进,同时保 证客户程序员代码不会受到那些变动的影响。为达到这个目的,需遵守一定的约定或规则。例如,库程序员在修改库内的一个类时,必须保证不删除已有 的方法,因为那样做会造成客户程序员代码岀现断点。然而,相反的情况却是令人痛苦的。对于一个数据成 员,库的创建者怎样才能知道哪些数据成员已受到客户程序员的访问呢?若方法属于某个类唯一的一部分, 而且并不一定由客户程序员直接使用,那么这种痛苦的情况同样是真实的。如果库的创建者想删除一种旧有 的实施方案,并置入新代码,此时又该怎么办呢?对那些成员进行的任何改动都可能中断客户程序员的代 码。所以库创建者处在一个尴尬的境地,似乎根本动弹不得。为解决这个问题,Jav推岀了“访问指示符”的概念,允许库创建者声明哪些东西是客户程序员可以使用 的,哪些是不可使用的。这种访问控制的级别在“最大访问”和“最小访问”的范围之间,分别包括: public, “友好的(无关键字),prot以及tperdivate。根据前一段的描述,大家或许已总结岀作为- 名库设计者,应将所有东西都尽可能保持为“private” (私有),并只展示岀那些想让客户程序员使用的方 法。这种思路是完全正确的,尽管它有点儿违背那些用其他语言(特别是C)编程的人的直觉,那些人习惯于在没有任何限制的情况下访问所有东西。到这一章结束时,大家应该可以深刻体会到Jav访问控制的价值。然而,组件库以及控制谁能访问那个库的组件的概念现在仍不是完整的。仍存在这样一个问题:如何将组件 绑定到单独一个统一的库单元里。这是通过JaVMpackage(打包)关键字来实现的,而且访问指示符要受到类在相同的包还是在不同的包里的影响。所以在本章的开头,大家首先要学习库组件如何置入包里。这样 才能理解访问指示符的完整含义。5. 1包:库单元我们用impo关键字导入一个完整的库时,就会获得“包” (Package)。例如: import java.util.*;它的作用是导入完整的实用工具(Utility )库,该库属于标准av开发工具包的一部分。由于 Vect位于 java.Ut,所以现在要么指定完整名称“ java.util.Vector” i m可省语句),要么简单地指定 一个“ Vector” (因为npo是默认的)。若想导入单独一个类,可在 impo语句:里指定那个类的名字:import java.util.Vector;现在,我们可以自由地使用 Vector。然而,jav中.的其他任何类仍是不可使用的。之所以要进行这样的导入,是为了提供一种特殊的机制,以便管理“命名空间” (Name Space)。我们所有 类成员的名字相互间都会隔离起来。位于类A内的一个方法f()不会与位于类3内的、拥有相同“签名”(自变量列表)的f()发生冲突。但类名会不会冲突呢?假设创建一个sta类k将它安装到已有一个 stack 类(由其他人编写)的机器上,这时会岀现什么情况呢?对于因特网中的Jav应用,这种情况会在用户毫不知晓的时候发生,因为类会在运行一个 Jav程序的时候自动下载。正是由于存在名字潜在的冲突,所以特别有必要对Jav书的命名空间进行完整的控制,而且需要创建一个完全独一无二的名字,无论因特网存在什么样的限制。迄今为止,本书的大多数例子都仅存在于单个文件中,而且设计成局部(本地)使用,没有同包名发生冲突(在这种情况下,类名置于“默认包”内)。这是一种有效的做法,而且考虑到问题的简化,本书剩下的部 分也将尽可能地采用它。然而,若计划创建一个“对因特网友好”或者说“适合在因特网使用”的程序,必 须考虑如何防止类名的重复。为Ja诡a建一个源码文件的时候,它通常叫作一个“编辑单元”(有时也叫作“翻译单元”)。每个编译单 元都必须有一个以.jav结尾的名字。而且在编译单元的内部,可以有一个公共(public )类,它必须拥有 与文件相同的名字(包括大小写形式,但排除.java文件扩展名)。如果不这样做,编译器就会报告岀错。每个编译单元内都只能有一个 publ类c同样地,否则编译器会报告岀错)。那个编译单元剩下的类(如果 有的话)可在那个包外面的世界面前隐藏起来,因为它们并非“公共”的(非public),而且它们由用于主 publ类的支撑类组成。编译一个.ja文牛时,我们会获得一个名字完全相同的输岀文件;但对于.java文件中的每个类,它们都有 一个.cla扩展名。因此,我们最终从少量的.jav文件里有可能获得数量众多的.clas文件。如以前用一 种汇编语言写过程序,那么可能已习惯编译器先分割岀一种过渡形式(通常是一个.obj 文件),再用一个链 接器将其与其他东西封装到一起(生成一个可执行文件),或者与一个库封装到一起(生成一个库)。但那 并不是Jav的工作方式。一个有效的程序就是一系列.clas文件,它们可以圭寸装和压缩到一个 JA文件里(使用Java 提供的ja王具)。Jav解释器负责对这些文件的寻找、装载和解释(注释)。:Jav并没有强制一定要使用解释器。一些固有代码的Jav译器可生成单独的可执行文件。库也由一系列类文件构成。每个文件都有一个 publ类C并没强迫使用一个 publ类,c但这种情况最 很典型的),所以每个文件都有一个组件。如果想将所有这些组件(它们在各自独立的.java 和.cla文件 里)都归纳到一起,那么 pack关鍵字就可以发挥作用)。若在一个文件的开头使用下述代码:package mypackage;那么packa吾句必须作为文件的第一个非注释语句岀现。该语句的作用是指岀这个编译单元属于名为 mypacka一个库的一部分。或者换句话说,它表明这个编译单元内的publ类名位于mypack这个名e字的下面。如果其他人想使用这个名字,要么指岀完整的名字,要么与mypacka诒g使用impo关键字(使用前面给岀的选项)。注意根据 Jav包(封装)的约定,名字内的所有字母都应小写,甚至那些中间单 词亦要如此。例如,假定文件名是 MyClass.java。它意味着在那个文件有一个、而且只能有一bl类。而且那个类 的名字必须是 MyClass(包括大小写形式):package mypackage;public class MyClass / .现在,如果有人想使用 MyClass,或者想使用pack内的其他任何publ类,c他们必须用impcO关键 字激活mypack内的g名字,使它们能够使用。另一个办法则是指定完整的名称:mypackage.MyClass m = new mypackage.MyClass();i mpo关键字则可将其变得简洁得多: import mypackage.*;/ . . .MyClass m = new MyClass();作为一名库设计者,一定要记住pack和giempo关键字允许我们做的事情就是分割单个全局命名空间,保证我们不会遇到名字的冲突无论有多少人使用因特网,也无论多少人用Jav编写自己的类。5. 1. 1创建独一无二的包名大家或许已注意到这样一个事实:由于一个包永远不会真的“封装”到单独一个文件里面,它可由多个.cla文件构成,所以局面可能稍微有些混乱。为避免这个问题,最合理的一种做法就是将某个特定包使 用的所有.cla文件都置入单个目录里。也就是说,我们要利用操作系统的分级文件结构避免岀现混乱局 面。这正是Jav所采取的方法。它同时也解决了另两个问题:创建独一无二的包名以及找岀那些可能深藏于目录结构某处的类。正如我们在 第2章讲述的那样,为达到这个目的,需要将.clas文件的位置路径编码到 packa勺名字里。但根据约 定,编译器强迫pack名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的(由 Inter保证一注释,它控制着域名的分配),所以假如按这一约定行事package的名称就肯定不会 重复,所以永远不会遇到名称冲突的问题。换句话说,除非将自己的域名转让给其他人,而且对方也按照相 同的路径名编写Jav代码,否则名字的冲突是永远不会岀现的。当然,如果你没有自己的域名,那么必须创 造一个非常生僻的包名(例如自己的英文姓名),以便尽最大可能创建一个独一无二的包名。如决定发行自 己的Jav代码,那么强烈推荐去申请自己的域名,它所需的费用是非常低廉的。ernic .net这个技巧的另一部分是将 pack名解折成自己机器上的一个目录。这样一来,Java程序运行并需要装 载.cla文件的时候(这是动态进行的,在程序需要创建属于那个类的一个对象,或者首次访问那个类的一 个stat成员时),它就可以找到.clas文件驻留的那个目录。JavM释器的工作程序如下:首先,它找到环境变量CLASSPATHa(将者具有JaVI释能力的工具一如浏览器一一安装到机器中时,通过操作系统进行设定)CLASSPAT包含了一个或多个目录,它们作为 一种特殊的“根”使用,从这里展开对.clas文件的搜索。从那个根开始,解释器会寻找包名,并将每个点 号(句点)替换成一个斜杠,从而生成从 CLASSPA开始的一个路径名(所以 package foo.会变ir.baz 成foobar或者fzo/bar/baz ;具体是正斜杠还是反斜杠由操作系统决定)。随后将它们连接到一起, 成为CLASSP内T各个条目(入口)。以后搜索.clasS件时,就可从这些地方开始查找与准备创建的类 名对应的名字。此外,它也会搜索一些标准目录一一这些目录与JaV解释器驻留的地方有关。为进一步理解这个问题,下面以我自己的域名为例,它是。将其反转过来后,com.bruce就为我的类创建了独一无二的全局名称(com,edu,org,等e展名以前在Jav包.中都 是大写的,但自Java以来,2这种情况已发生了变化。现在整个包名都是小写的)。由于决定创建一个名 为ut啲库,我可以进一步地分割它,所以最后得到的包名如下: package com.bruceeckel.util;现在,可将这个包名作为下述两个文件的“命名空间”使用:/:Vector.java/ Creating a packagepackage com.bruceeckel.util;public class Vector public Vector() System.out.println( com.bruceeckel.util.Vector); /: 创建自己的包时,要求 packa吾句必须是文件中的第一个“非注释”代码。第二个文件表面看起来是类似的:/: List.java/ Creating a packagepackage com.bruceeckel.util;public class List public List() System.out.println( com.bruceeckel.util.List); /: 这两个文件都置于我自己系统的一个子目录中:C:DOCJavaTcombruceeckelutil若通过它往回走,就会发现包名 com.bruceeckel.uti l,但路径的第一部分又是什么呢A这是由ATH 环境变量决定的。在我的机器上,它是:CLASSPATH = . D:I J AVDOCJavaT可以看岀,CLASSP里能包含大量备用的搜索路径。然而,使用JA文件时要注意一个问题:必须将 JAR文件的名字置于类路径里,而不仅仅是它所在的路径。所以对一个名为grape.的JAR件来说,我们的类路径需要包括:CLASSPATH = . IL:I J AVXflavorsgrape.jar正确设置好类路径后,可将下面这个文件置于任何目录里(若在执行该程序时遇到麻烦,请参见第3章的3.1小节“赋值”):/:LibTest.java/ Uses the librarypackage cO5;import com.bruceeckel.util.*;public class LibTest public static void main(String args) Vector v = new Vector();List l = new List(); /: 编译器遇到impo语句后,它会搜索由 CLASSPeATHU录,查找子目录 combruceeckeluti l,然后查 找名称适当的已编译文件(对于 Vect是Vector.clasLsi,对是List.class) Ve注意;和rLis内t无论类还是需要的方法都必须设为 publico1.自动编译为导入的类首次创建一个对象时(或者访问一个类的stat成员时),编译器会在适当的目录里寻找同名的.cla文件(所以如果创建类 X的一个对象,就应该是 X.class)。若只发现class,它就是必须使用 的那一个类。然而,如果它在相同的目录中还发现了一个X.java,编译器就会比较两个文件的日期标记。如果X.ja比X.cl新s就会自动编译 X.java,生成一个最新的勺class。对于一个特定的类,或在与它同名的.java文件中没有找到它,就会对那个类采取上述的处理。2. 冲突若通过*导入了两个库,而且它们包括相同的名字,这时会岀现什么情况呢?例如,假定一个程序使用了下述 导入语句:import com.bruceeckel.util.*;import java.util.*;由于j ava.util.*也包含了一个类,所以这会造成潜在的冲突。然而,只要冲突并不真的发生,那么就不会产生任何问题一一这当然是最理想的情况,因为否则的话,就需要进行大量编程工作,防范那些可能可能永远也不会发生的冲突。如现在试着生成一个 Vector,就肯定会发生冲突。如下所示: Vector v = new Vector();它引用的到底是哪个 Vect类呢?编译器对这个问题没有答案,读者也不可能知道。所以编译器会报告一个 错误,强迫我们进行明确的说明。例如,假设我想使用标准的Java Vector,那么必须象下面这样编程:java.util.Vector v = new java.util.Vector();由于它(与CLASSPATH完整指定了那个 Vect的位置,所以不再需要 import j ava.util.*语句,除 非还想使用来自java.ut他1东西。5. 1. 2自定义工具库掌握前述的知识后,接下来就可以开始创建自己的工具库,以便减少或者完全消除重复的代码。例如,可为System.out.println()创建一个别名,减少重复键入的代码量。它Wo一个包(package)的 一部分:/: P.java/TheP.rint&P.rintlnshorpackagecom.bruceeckel.tools;handpublic clas public st System. public st System. public st System. public st System. public st System. public st System. public st System. public st System. public st System. public st System. public st System. public st System.public st System. public st System. public st System. public st System.s P aticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printaticvoidout.printrint(Object obj)(obj);rint(String s)s);rint(chars)s);rint(char c) c) ;rint(int i) i);rint(long l) l);rint(float f) (f);rint(double d) d) ;rint(boolean b) b);rintln() n();rintln(Object obj)n(obj);rintln(String s)1 n(s);rintln(char s)1 n(s);rintln(char c) 1 n(c);rintln(int i) 1 n(i);rintln(long l) n(l);public static void rintln(float f) System.out.println(f);public static void rintln(double d) System.out.println(d);public static void rintln(boolean b) System.out.println(b);/: 所有不同的数据类型现在都可以在一个新行输岀(P.rintln(),或者不在一个新行输岀(P.ri)nt()大家可能会猜想这个文件所在的目录必须从某个CLASSPM开台,然后继续com/bruceeckel/tools。编译完毕后,利用一个impo语句,即可在自己系统的任何地方使用P.cl文件s如下所示:/:ToolTest.java/ Uses the tools library import com.bruceeckel.tools.*;public class ToolTest public static void main(String args) P.rintln(HAvailable from now on!); /: 所以从现在开始,无论什么时候只要做岀了一个有用的新工具,就可将其加入too目录(或者自己的个人uti或too目录)。1. CLAS的陷阱THP.ja文件存在一个非常有趣的陷阱。特别是对于早期的Jav现方案来说,类路径的正确设定通常都是很困难的一项工作。编写这本书的时候,我引入了P.ja文件,它最初看起来似乎工作很正常。但在某些情况下,却开始岀现中断。在很长的时间里,我都确信这是Jav或其他什么在实现时一个错误。但最后,我终于发现在一个地方引入了一个程序(即第1章要说明的CodePackager.java),它使用了一个不同的类由于它作为一个工具使用,所以有时候会进入类路径里;另一些时候则不会这样。但只要它进入类路径,那 么假若执行的程序需要寻找 com.bruceeck中的类,t J o v首先发现的就是CodePackager.java中的 P。此时,编译器会报告一个特定的方法没有找到。这当然是非常令人头疼的,因为我们在前面的类P里明明看到了这个方法,而且根本没有更多的诊断报告可为我们提供一条线索,让我们知道找到的是一个完全不同 的类(那甚至不是 publ的)c。乍一看来,这似乎是编译器的一个错误,但假若考察impo语句,就会发现它只是说:“在这里可能发现了P”。然而,我们假定的是编译器搜索自己类路径的任何地方,所以一旦它发现一个P,就会使用它;若在搜索过程中发现了“错误的”一个,它就会停止搜索。这与我们在前面表述的稍微有些区别,因为存在一些讨 厌的类,它们都位于包内。而这里有一个不在包内的P,但仍可在常规的类路径搜索过程中找到。如果您遇到象这样的情况,请务必保证对于类路径的每个地方,每个名字都仅存在一个类。5. 1. 3利用导入改变行为Jav已取消的一种特性是 C的“条件编译”,它允许我们改变参数,获得不同的行为,同时不改变其他任何 代码。Jav之所以抛弃了这一特性,可能是由于该特性经常在C里用于解决跨平台问题:代码的不同部分根据具体的平台进行编译,否则不能在特定的平台上运行。由于Jav的设计思想是成为一种自动跨平台的语言,所以这种特性是没有必要的。然而,条件编译还有另一些非常有价值的用途。一种很常见的用途就是调试代码。调试特性可在开发过程中 使用,但在发行的产品中却无此功能Alen Holub()提岀了利用包(package )来模仿条 编译的概念。根据这一概念,它创建了 C “断定机制”一个非常有用的Jav版本。之所以叫作“断定机 制”,是由于我们可以说“它应该为真”或者“它应该为假”。如果语句不同意你的断定,就可以发现相关 的情况。这种工具在调试过程中是特别有用的。可用下面这个类进行程序调试:/:Assert.java/ Assertion tool for debugging package com.bruceeckel.tools.debug;public class Assert private static void perr(String msg) System.err.println(msg);public final static void is _ true(boolean exp) if(!exp) perr(Assertion failed);public final static void is _ false(boolean exp) if(exp) perr(Assertion failed);public final static voidis _ true(boolean exp, String msg) if(!exp) perr(Assertion failed:+ msg);public final static voidis _ false(boolean exp, String msg) if(exp) perr(Assertion failed:+ msg); /: 这个类只是简单地封装了布尔测试。如果失败,就显示岀岀错消息。在第9章,大家还会学习一个更高级的错误控制工具,名为“违例控制”。但在目前这种情况下,perr()方法已经可以很好地工作。如果想使用这个类,可在自己的程序中加入下面这一行:import com.bruceeckel.tools.debug.*;如欲清除断定机制,以便自己能发行最终的代码,我们创建了第二个Asse类,t但却是在一个不同的包里:/:Assert.java/ Turning off the assertion output / so you can ship the program.package com.bruceeckel.tools;publicclassAssertpublicfinalstaticvoidis_true(boolean exp)publicfinalstaticvoidis_false(boolean exp)publicfinalstaticvoidistrue(booleanexpStringmsg)publicfinalstaticvoidisfalse(booleanexp,Stringmsg)打 /:现在,假如将前一个impo语句变成下面这个样子: import com.bruceeckel.tools.程序便不再显示岀断言。下面是个例子:1 2 9/:TeStASSertjava/DemonStratingtheaSSertiontoolpackagec055/Commentthefollowing5anduncommentthe/SubSequentlinetochangeaSSertionbehaviorimportcombruceeckeltoolSdebug*5/importcombruceeckeltoolS5publicclaSSTeStASSertpublicStaticvoidmain(String 口argS)ASSert.iStrue(2+2)=5);ASSert.iSfalSe(1+1)=2);ASSert.iStrue(2+2)=5II52+2 =5I);ASSert.iSfalSe(1+1)=2,I1+1 !=2I); /:通过改变导入的package,我们可将自己的代码从调试版本变成最终的发行版本。这种技术可应用于任何种 类的条件代码。5. 1. 4包的停用大家应注意这样一个问题:每次创建一个包后,都在为包取名时间接地指定了一个目录结构。这个包必须存 在(驻留)于由它的名字规定的目录内。而且这个目录必须能从CLASSI开始搜索并发现。最开始的时候,packa关键字的运用可能会令人迷惑,因为除非坚持遵守根据目录路径指定包名的规则,否则就会在 运行期获得大量莫名其妙的消息,指岀找不到一个特定的类一一即使那个类明明就在相同的目录中。若得到 象这样的一条消息,请试着将 packa吾句作为注释标记岀去。如果这样做行得通,就可知道问题到底岀在 哪儿。5. 2 Java访问指示符针对类内每个成员的每个定义,Java访问指示符poublic,pro以及 ptreva都置于它们的最前面一 无论它们是一个数据成员,还是一个方法。每个访问指示符都只控制着对那个特定定义的访问。这与C + +存在着显著不同。在 C + +中,访冋指示符控制着它后面的所有定义,直到又一个访冋指示符加入为止。通过千丝万缕的联系,程序为所有东西都指定了某种形式的访问。在后面的小节里,大家要学习与各类访问 有关的所有知识。首次从默认访问开始。5. 2. 1 “友好的”如果根本不指定访问指示符,就象本章之前的所有例子那样,这时会岀现什么情况呢?默认的访问没有关键 字,但它通常称为“友好” (Friend)l访问。这意味着当前包内的其他所有类都能访问“友好的”成员, 但对包外的所有类来说,这些成员却是“私有” (Private )的,外界不得访问。由于一个编译单元(一个文 件)只能从属于单个包,所以单个编译单元内的所有类相互间都是自动“友好”的。因此,我们也说友好元 素拥有“包访问权限。友好访问允许我们将相关的类都组合到一个包里,使它们相互间方便地进行沟通。将类组合到一个包内以后 (这样便允许友好成员的相互访问,亦即让它们“交朋友”),我们便“拥有” 了那个包内的代码。只有我 们已经拥有的代码才能友好地访问自己拥有的其他代码。我们可认为友好访问使类在一个包内的组合显得有 意义,或者说前者是后者的原因。在许多语言中,我们在文件内组织定义的方式往往显得有些牵强。但在 Jav书,却强制用一种颇有意义的形式进行组织。除此以外,我们有时可能想排除一些类,不想让它们访问 当前包内定义的类。对于任何关系,一个非常重要的问题是“谁能访问我们的私有或priva代码e。类控制着哪些代码能够访问自己的成员。没有任何秘诀可以“闯入”。另一个包内推荐可以声明一个新类,然后说:“嗨,我是Bo的朋友!,并指望看到 Eo的“ protected(受到保护的)、友好的以及“ private” (私有)的成员。为获得对一个访问权限,唯一的方法就是:(1) 使成员成为public” (公共的)。这样所有人从任何地方都可以访问它。(2) 变成一个“友好”成员,方法是舍弃所有访问指示符,并将其类置于相同的包内。这样一来,其他类就 可以访问成员。(3) 正如以后引入“继承”概念后大家会知道的那样,一个继承的类既可以访问一个 proteC成鳧迪可 以访问一个publ成员(但不可访问priv成员e。只有在两个类位于相同的包内时,它才可以访问友好 成员。但现在不必关心这方面的问题。(4) 提供“访问器/变化器”方法(亦称为“获取/设置”方法),以便读取和修改值。这是 OO环境中最 正规的一种方法,也是 Java E的基础一具体情况会在第 13章介绍。5. 2. 2 publ i c:接口访问使用publ关键字时,它意味着紧随在 publ后面的成员声明适用于所有人,特别是适用于使用库的客户 程序员。假定我们定义了一个名为 dessd勺包t其中包含下述单元(若执行该程序时遇到困难,请参考第 3章3.1小节“赋值”):/:Cookie.java/ Creates a library package c05.dessert;public class Cookie public Cookie() System.out.println(HCookie constructor);void foo() System.out.println(fooH); /: 请记住,Cookie .必须驻留在名为 dess的一个子目录内,而这个子目录又必须位于由CLASSI指YTH定的C0目录下面(C05弋表本书的第5章)。不要错误地以为 Jav无仑如何都会将当前目录作为搜索的起 点看待。如果不将一个“”作为 CLASSI勺一部分使用,Jav就不会考虑当前目录。现在,假若创建使用了 Cook的一个程序,如下所示:/:Dinner.java/ Uses the libraryc05.desserpublicclassDipublicDinnerSystemout.ppublicstaticCookieX=/!Xfoo()mportnner() /:voidmain(StrnewCookie(); /Canztaccrintln(Dinnerconstructor);ing args) ess就可以创建一个 Cook对象,因为它的构建器是 publ的,c而且类也是 publ的c公共类的概念稍后还会 进行更详细的讲述)。然而,foo()成员不可在Dinner.内访问a因为foo()只有在ess包内才是“友好”的。1. 默认包大家可能会惊讶地发现下面这些代码得以顺利编译一一尽管它看起来似乎已违背了规则:31/:Cake.java/Accessesa classin a separate/compilation unit.classCakepublic static void main(StringargsPie x = new Pie(); x.f(); /: 在位于相同目录的第二个文件里:/:Pie.java/The otherclassSystem.out.prin1 n(Pie.f();class Pie void f() /: 最初可能会把它们看作完全不相干的文件,然而Cal能迩【J建一个Pi对象,并能调用它的f()方法!通常的想法会认为Pi和 f()是“友好的”,所以不适用于Cake。它们确实是友好的一一这部分结论非常正确。但 它们之所以仍能在Cake.j电使V用,是由于它们位于相同的目录中,而且没有明确的包名。Java把象这样 的文件看作那个目录“默认包”的一部分,所以它们对于目录内的其他文件来说是“友好”的。5. 2. 3 private:不能接触!priv关键字意味着除非那个特定的类,而且从那个类的方法里,否则没有人能访问那个成员。同一个包 内的其他成员不能访问 priv成员e这使其显得似乎将类与我们自己都隔离起来。另一方面,也不能由几 个合作的人创建一个包。所以 priv允许我们自由地改变那个成员,同时毋需关心它是否会影响同一个包 内的另一个类。默认的“友好”包访问通常已经是一种适当的隐藏方法;请记住,对于包的用户来说,是不 能访问一个“友好”成员的。这种效果往往能令人满意,因为默认访问是我们通常采用的方法。对于希望变 成public(公共)的成员,我们通常明确地指岀,令其可由客户程序员自由调用。而且作为一个结果,最开 始的时候通常会认为自己不必频繁使用 priv关键字,因为完全可以在不用它的前提下发布自己的代码(这与C + +是个鲜明的对比)。然而,随着学习的深入,大家就会发现 priv仍然有非常重要的用途,特 别是在涉及多线程处理的时候(详情见第14章)。下面是应用了 pri v的一个例子:/:IceCream.java/ Demonstrates private keywordpublic cla public s /! SuSundaess IceCre tatic voi ndae x =x = Sundam id main(Str new Sundae ae.makeASuing args)();ndae();classSundaeprivateSundastaticSundaereturnnewe() 打makeASundae()Sundae(); /:这个例子向我们证明了使用 priv的方便:有时可能想控制对象的创建方式,并防止有人直接访问一个特 定的构建器(或者所有构建器)。在上面的例子中,我们不可通过它的构建器创建一个Sund对象;相反,必须调用makeASundae()方法来实现(注释)。private,所以可防止private,从而保证自己 privaBt可保证自己 :此时还会产生另一个影响:由于默认构建器是唯一获得定义的,而且它的属性是 对这个类的继承(这是第 6章要重点讲述的主题)。若确定一个类只有一个“助手”方法,那么对于任何方法来说,都可以把它们设为 不会误在包内其他地方使用它,防止自己更改或删除方法。将一个方法的属性设为一直保持这一选项(然而,若一个句柄被设为private,并不表明其他对象不能拥有指向同一个对象的publ句柄。有关“别名”的问题将在第 12章详述)。5. 2. 4 protected:“友好的一种”protected(受到保护的)访问指示符要求大家提前有所认识。首先应注意这样一个事实:为继续学习本书一 直到继承那一章之前的内容,并不一定需要先理解本小节的内容。但为了保持内容的完整,这儿仍然要对此 进行简要说明,并提供相关的例子。prote (关键字为我们引入了一种名为“继承”的概念,它以现有的类为基础,并在其中加入新的成员,同时不会对现有的类产生影响一一我们将这种现有的类称为“基础类”或者“基本类” (Ease Class)。亦 可改变那个类现有成员的行为。对于从一个现有类的继承,我们说自己的新类“扩展” (extends) 了那个现 有的类。如下所示:class Foo extends Bar 类定义剩余的部分看起来是完全相同的。若新建一个包,并从另一个包内的某个类里继承,则唯一能够访问的成员就是原来那个包的publ成员。当然,如果在相同的包里进行继承,那么继承获得的包能够访问所有“友好”的成员。有些时候,基础类的创 建者喜欢提供一个特殊的成员,并允许访问衍生类。这正是protect作d若往回引用5.2小节“public :接口访问的IlCookie.文伟,v贝a下面这个类就不能访问“友好的成员:/:ChocolateChipjava/Can/taccessfriendl/inanotherclassimportc05dessert*5member1 3 31 3 #publicclassChocolateChipextendsCookiepublicChocolateChip() Systemoutprintln(11 ChocolateChipconstructor);publicstatic void mChocolateChip x =/!xfoo();/ Caain(String口args)newChocolateChip();n/taccessfoo1 3 # /:对于继承,值得注意的一件有趣的事情是倘若方法foo()存在于类)ok中,e那么它也会存在于从 Cookie继承的所有类中。但由于 foo()在外部的包里是“友好”的,所以我们不能使用它。当然,亦可将其变成 public。但这样一来,由于所有人都能自由访问它,所以可能并非我们所希望的局面。若象下面这样修改类 Cookie:publicclass Cookiepublic Cookie() System.out.println(HCookie constructor);protected void foo() System.out.println(fooH);那么仍然能在包dess里“友好地访问foo(),!Co)ok继承的其他东西亦可自由地访问它。然而, 它并非公共的(public)。5. 3接口与实现我们通常认为访问控制是“隐藏实施细节”的一种方式。将数据和方法封装到类内后,可生成一种数据类 型,它具有自己的特征与行为。但由于两方面重要的原因,访问为那个数据类型加上了自己的边界。第一个 原因是规定客户程序员哪些能够使用,哪些不能。我们可在结构里构建自己的内部机制,不用担心客户程序 员将其当作接口的一部分,从而自由地使用或者“滥用”。这个原因直接导致了第二个原因:我们需要将接口同实施细节分离开。若结构在一系列程序中使用,但用户 除了将消息发给publ接口之外,不能做其他任何事情,我们就可以改变不属于publ的所有东西(如“友好的、protec以及dprivate),同时不要求用户对他们的代码作任何修改。我们现在是在一个面向对象的编程环境中,其中的一个类(class )实际是指“一类对象”,就象我们说“鱼 类”或“鸟类”那样。从属于这个类的所有对象都共享这些特征与行为。“类”是对属于这一类的所有对象 的外观及行为进行的一种描述。在一些早期OO语言中,如Simula-67,关键字的作用是描述一种新的数据类型。同样的关键字在大 多数面向对象的编程语言里都得到了应用。它其实是整个语言的焦点:需要新建数据类型的场合比那些用于 容纳数据和方法的“容器”多得多。在Jav书,类是最基本的OO概念。它是本书未采用粗体印刷的关键字之一一一由于数量太多,所以会造成 页面排版的严重混乱。为清楚起见,可考虑用特殊的样式创建一个类:将publ成员置于最开头,后面跟随 protected、友好以及priv成员e这样做的好处是类的使用者可从上向下依次阅读,并首先看到对自己来说最重要的内容(即 publ成员,因为它们可从文件的外部访问),并在遇到非公共成员后停止阅读,后者已经属于内部实施细 节的一部分了。然而,利用由 java d提供(支持的注释文档(已在第 2章介绍),代码的可读性问题已在很 大程度上得到了解决。publicclassXpublicvoidpub1()publicvoidpub2()publicvoidpub3()privatevoidpriv1privatevoidpriv2privatevoidpriv3privateinti;/由于接口和实施细节仍然混合在一起,所以只是部分容易阅读。也就是说,仍然能够看到源码一一实施的细 节,因为它们需要保存在类里面。向一个类的消费者显示岀接口实际是“类浏览器”的工作。这种工具能查 找所有可用的类,总结岀可对它们采取的全部操作(比如可以使
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 办公文档 > 工作计划


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!