ImageVerifierCode 换一换
格式:DOCX , 页数:38 ,大小:38.93KB ,
资源ID:2824332      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bingdoc.com/d-2824332.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(第6章 java类再生.docx)为本站会员(b****2)主动上传,冰点文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰点文库(发送邮件至service@bingdoc.com或直接QQ联系客服),我们立即给予删除!

第6章 java类再生.docx

1、第6章 java类再生本教程由yyc,spirit整理 清风小木虫美化- 清风小木虫 精彩无极限 - 第6章 类再生“Java引人注目的一项特性是代码的重复使用或者再生。但最具革命意义的是,除代码的复制和修改以外,我们还能做多得多的其他事情。”在象C那样的程序化语言里,代码的重复使用早已可行,但效果不是特别显著。与Java的其他地方一样,这个方案解决的也是与类有关的问题。我们通过创建新类来重复使用代码,但却用不着重新创建,可以直接使用别人已建好并调试好的现成类。但这样做必须保证不会干扰原有的代码。在这一章里,我们将介绍两个达到这一目标的方法。第一个最简单:在新类里简单地创建原有类的对象。我们把

2、这种方法叫作“合成”,因为新类由现有类的对象合并而成。我们只是简单地重复利用代码的功能,而不是采用它的形式。第二种方法则显得稍微有些技巧。它创建一个新类,将其作为现有类的一个“类型”。我们可以原样采取现有类的形式,并在其中加入新代码,同时不会对现有的类产生影响。这种魔术般的行为叫作“继承”(Inheritance),涉及的大多数工作都是由编译器完成的。对于面向对象的程序设计,“继承”是最重要的基础概念之一。它对我们下一章要讲述的内容会产生一些额外的影响。对于合成与继承这两种方法,大多数语法和行为都是类似的(因为它们都要根据现有的类型生成新类型)。在本章,我们将深入学习这些代码再生或者重复使用的

3、机制。6.1 合成的语法就以前的学习情况来看,事实上已进行了多次“合成”操作。为进行合成,我们只需在新类里简单地置入对象句柄即可。举个例子来说,假定需要在一个对象里容纳几个String对象、两种基本数据类型以及属于另一个类的一个对象。对于非基本类型的对象来说,只需将句柄置于新类即可;而对于基本数据类型来说,则需在自己的类中定义它们。如下所示(若执行该程序时有麻烦,请参见第3章3.1.2小节“赋值”):/: SprinklerSystem.java/ Composition for code reusepackage c06;class WaterSource private String s;

4、 WaterSource() System.out.println(WaterSource(); s = new String(Constructed); public String toString() return s; public class SprinklerSystem private String valve1, valve2, valve3, valve4; WaterSource source; int i; float f; void print() System.out.println(valve1 = + valve1); System.out.println(valv

5、e2 = + valve2); System.out.println(valve3 = + valve3); System.out.println(valve4 = + valve4); System.out.println(i = + i); System.out.println(f = + f); System.out.println(source = + source); public static void main(String args) SprinklerSystem x = new SprinklerSystem(); x.print(); /:WaterSource内定义的一

6、个方法是比较特别的:toString()。大家不久就会知道,每种非基本类型的对象都有一个toString()方法。若编译器本来希望一个String,但却获得某个这样的对象,就会调用这个方法。所以在下面这个表达式中:System.out.println(source = + source) ;编译器会发现我们试图向一个WaterSource添加一个String对象(source =)。这对它来说是不可接受的,因为我们只能将一个字串“添加”到另一个字串,所以它会说:“我要调用toString(),把source转换成字串!”经这样处理后,它就能编译两个字串,并将结果字串传递给一个System.ou

7、t.println()。每次随同自己创建的一个类允许这种行为的时候,都只需要写一个toString()方法。如果不深究,可能会草率地认为编译器会为上述代码中的每个句柄都自动构造对象(由于Java的安全和谨慎的形象)。例如,可能以为它会为WaterSource调用默认构建器,以便初始化source。打印语句的输出事实上是:valve1 = nullvalve2 = nullvalve3 = nullvalve4 = nulli = 0f = 0.0source = null在类内作为字段使用的基本数据会初始化成零,就象第2章指出的那样。但对象句柄会初始化成null。而且假若试图为它们中的任何一个

8、调用方法,就会产生一次“违例”。这种结果实际是相当好的(而且很有用),我们可在不丢弃一次违例的前提下,仍然把它们打印出来。编译器并不只是为每个句柄创建一个默认对象,因为那样会在许多情况下招致不必要的开销。如希望句柄得到初始化,可在下面这些地方进行:(1) 在对象定义的时候。这意味着它们在构建器调用之前肯定能得到初始化。(2) 在那个类的构建器中。(3) 紧靠在要求实际使用那个对象之前。这样做可减少不必要的开销假如对象并不需要创建的话。下面向大家展示了所有这三种方法:/: Bath.java/ Constructor initialization with compositionclass So

9、ap private String s; Soap() System.out.println(Soap(); s = new String(Constructed); public String toString() return s; public class Bath private String / Initializing at point of definition: s1 = new String(Happy), s2 = Happy, s3, s4; Soap castille; int i; float toy; Bath() System.out.println(Inside

10、 Bath(); s3 = new String(Joy); i = 47; toy = 3.14f; castille = new Soap(); void print() / Delayed initialization: if(s4 = null) s4 = new String(Joy); System.out.println(s1 = + s1); System.out.println(s2 = + s2); System.out.println(s3 = + s3); System.out.println(s4 = + s4); System.out.println(i = + i

11、); System.out.println(toy = + toy); System.out.println(castille = + castille); public static void main(String args) Bath b = new Bath(); b.print(); /:请注意在Bath构建器中,在所有初始化开始之前执行了一个语句。如果不在定义时进行初始化,仍然不能保证能在将一条消息发给一个对象句柄之前会执行任何初始化除非出现不可避免的运行期违例。下面是该程序的输出:Inside Bath()Soap()s1 = Happys2 = Happys3 = Joys4

12、= Joyi = 47toy = 3.14castille = Constructed调用print()时,它会填充s4,使所有字段在使用之前都获得正确的初始化。6.2 继承的语法继承与Java(以及其他OOP语言)非常紧密地结合在一起。我们早在第1章就为大家引入了继承的概念,并在那章之后到本章之前的各章里不时用到,因为一些特殊的场合要求必须使用继承。除此以外,创建一个类时肯定会进行继承,因为若非如此,会从Java的标准根类Object中继承。用于合成的语法是非常简单且直观的。但为了进行继承,必须采用一种全然不同的形式。需要继承的时候,我们会说:“这个新类和那个旧类差不多。”为了在代码里表面这

13、一观念,需要给出类名。但在类主体的起始花括号之前,需要放置一个关键字extends,在后面跟随“基础类”的名字。若采取这种做法,就可自动获得基础类的所有数据成员以及方法。下面是一个例子:/: Detergent.java/ Inheritance syntax & propertiesclass Cleanser private String s = new String(Cleanser); public void append(String a) s += a; public void dilute() append( dilute(); public void apply() appen

14、d( apply(); public void scrub() append( scrub(); public void print() System.out.println(s); public static void main(String args) Cleanser x = new Cleanser(); x.dilute(); x.apply(); x.scrub(); x.print(); public class Detergent extends Cleanser / Change a method: public void scrub() append( Detergent.

15、scrub(); super.scrub(); / Call base-class version / Add methods to the interface: public void foam() append( foam(); / Test the new class: public static void main(String args) Detergent x = new Detergent(); x.dilute(); x.apply(); x.scrub(); x.foam(); x.print(); System.out.println(Testing base class:

16、); Cleanser.main(args); /:这个例子向大家展示了大量特性。首先,在Cleanser append()方法里,字串同一个s连接起来。这是用“+=”运算符实现的。同“+”一样,“+=”被Java用于对字串进行“过载”处理。其次,无论Cleanser还是Detergent都包含了一个main()方法。我们可为自己的每个类都创建一个main()。通常建议大家象这样进行编写代码,使自己的测试代码能够封装到类内。即便在程序中含有数量众多的类,但对于在命令行请求的public类,只有main()才会得到调用。所以在这种情况下,当我们使用“java Detergent”的时候,调用的是

17、Degergent.main()即使Cleanser并非一个public类。采用这种将main()置入每个类的做法,可方便地为每个类都进行单元测试。而且在完成测试以后,毋需将main()删去;可把它保留下来,用于以后的测试。在这里,大家可看到Deteregent.main()对Cleanser.main()的调用是明确进行的。需要着重强调的是Cleanser中的所有类都是public属性。请记住,倘若省略所有访问指示符,则成员默认为“友好的”。这样一来,就只允许对包成员进行访问。在这个包内,任何人都可使用那些没有访问指示符的方法。例如,Detergent将不会遇到任何麻烦。然而,假设来自另外某

18、个包的类准备继承Cleanser,它就只能访问那些public成员。所以在计划继承的时候,一个比较好的规则是将所有字段都设为private,并将所有方法都设为public(protected成员也允许衍生出来的类访问它;以后还会深入探讨这一问题)。当然,在一些特殊的场合,我们仍然必须作出一些调整,但这并不是一个好的做法。注意Cleanser在它的接口中含有一系列方法:append(),dilute(),apply(),scrub()以及print()。由于Detergent是从Cleanser衍生出来的(通过extends关键字),所以它会自动获得接口内的所有这些方法即使我们在Detergen

19、t里并未看到对它们的明确定义。这样一来,就可将继承想象成“对接口的重复利用”或者“接口的再生”(以后的实施细节可以自由设置,但那并非我们强调的重点)。正如在scrub()里看到的那样,可以获得在基础类里定义的一个方法,并对其进行修改。在这种情况下,我们通常想在新版本里调用来自基础类的方法。但在scrub()里,不可只是简单地发出对scrub()的调用。那样便造成了递归调用,我们不愿看到这一情况。为解决这个问题,Java提供了一个super关键字,它引用当前类已从中继承的一个“超类”(Superclass)。所以表达式super.scrub()调用的是方法scrub()的基础类版本。进行继承时,

20、我们并不限于只能使用基础类的方法。亦可在衍生出来的类里加入自己的新方法。这时采取的做法与在普通类里添加其他任何方法是完全一样的:只需简单地定义它即可。extends关键字提醒我们准备将新方法加入基础类的接口里,对其进行“扩展”。foam()便是这种做法的一个产物。在Detergent.main()里,我们可看到对于Detergent对象,可调用Cleanser以及Detergent内所有可用的方法(如foam())。6.2.1 初始化基础类由于这儿涉及到两个类基础类及衍生类,而不再是以前的一个,所以在想象衍生类的结果对象时,可能会产生一些迷惑。从外部看,似乎新类拥有与基础类相同的接口,而且可包

21、含一些额外的方法和字段。但继承并非仅仅简单地复制基础类的接口了事。创建衍生类的一个对象时,它在其中包含了基础类的一个“子对象”。这个子对象就象我们根据基础类本身创建了它的一个对象。从外部看,基础类的子对象已封装到衍生类的对象里了。当然,基础类子对象应该正确地初始化,而且只有一种方法能保证这一点:在构建器中执行初始化,通过调用基础类构建器,后者有足够的能力和权限来执行对基础类的初始化。在衍生类的构建器中,Java会自动插入对基础类构建器的调用。下面这个例子向大家展示了对这种三级继承的应用:/: Cartoon.java/ Constructor calls during inheritancec

22、lass Art Art() System.out.println(Art constructor); class Drawing extends Art Drawing() System.out.println(Drawing constructor); public class Cartoon extends Drawing Cartoon() System.out.println(Cartoon constructor); public static void main(String args) Cartoon x = new Cartoon(); /:该程序的输出显示了自动调用:Art

23、 constructorDrawing constructorCartoon constructor可以看出,构建是在基础类的“外部”进行的,所以基础类会在衍生类访问它之前得到正确的初始化。即使没有为Cartoon()创建一个构建器,编译器也会为我们自动合成一个默认构建器,并发出对基础类构建器的调用。1. 含有自变量的构建器上述例子有自己默认的构建器;也就是说,它们不含任何自变量。编译器可以很容易地调用它们,因为不存在具体传递什么自变量的问题。如果类没有默认的自变量,或者想调用含有一个自变量的某个基础类构建器,必须明确地编写对基础类的调用代码。这是用super关键字以及适当的自变量列表实现的,

24、如下所示:/: Chess.java/ Inheritance, constructors and argumentsclass Game Game(int i) System.out.println(Game constructor); class BoardGame extends Game BoardGame(int i) super(i); System.out.println(BoardGame constructor); public class Chess extends BoardGame Chess() super(11); System.out.println(Chess

25、constructor); public static void main(String args) Chess x = new Chess(); /:如果不调用BoardGames()内的基础类构建器,编译器就会报告自己找不到Games()形式的一个构建器。除此以外,在衍生类构建器中,对基础类构建器的调用是必须做的第一件事情(如操作失当,编译器会向我们指出)。2. 捕获基本构建器的违例正如刚才指出的那样,编译器会强迫我们在衍生类构建器的主体中首先设置对基础类构建器的调用。这意味着在它之前不能出现任何东西。正如大家在第9章会看到的那样,这同时也会防止衍生类构建器捕获来自一个基础类的任何违例事件

26、。显然,这有时会为我们造成不便。6.3 合成与继承的结合许多时候都要求将合成与继承两种技术结合起来使用。下面这个例子展示了如何同时采用继承与合成技术,从而创建一个更复杂的类,同时进行必要的构建器初始化工作:/: PlaceSetting.java/ Combining composition & inheritanceclass Plate Plate(int i) System.out.println(Plate constructor); class DinnerPlate extends Plate DinnerPlate(int i) super(i); System.out.prin

27、tln( DinnerPlate constructor); class Utensil Utensil(int i) System.out.println(Utensil constructor); class Spoon extends Utensil Spoon(int i) super(i); System.out.println(Spoon constructor); class Fork extends Utensil Fork(int i) super(i); System.out.println(Fork constructor); class Knife extends Ut

28、ensil Knife(int i) super(i); System.out.println(Knife constructor); / A cultural way of doing something:class Custom Custom(int i) System.out.println(Custom constructor); public class PlaceSetting extends Custom Spoon sp; Fork frk; Knife kn; DinnerPlate pl; PlaceSetting(int i) super(i + 1); sp = new

29、 Spoon(i + 2); frk = new Fork(i + 3); kn = new Knife(i + 4); pl = new DinnerPlate(i + 5); System.out.println( PlaceSetting constructor); public static void main(String args) PlaceSetting x = new PlaceSetting(9); /:尽管编译器会强迫我们对基础类进行初始化,并要求我们在构建器最开头做这一工作,但它并不会监视我们是否正确初始化了成员对象。所以对此必须特别加以留意。6.3.1 确保正确的清除Java不具备象C+的“破坏器”那样的概念。在C+中,一旦破坏(清除)一个对象,就会自动调用破坏器方法。之所以将其省略,大概是由于在Java中只需简单地忘记对象,不需强行破坏它们。垃圾收集器会在必要的时候自动回收内存。垃圾收集器大多数时候都能很好地工作,但在某些情况下,我们的类可能在自己的存在时期采取一些行动,而这些行动要求必须进行明确的清除工作。正如第4章已经指出的那样,我们并不知道垃圾收集器什么时候才会显身,或者说不知它何时会调用。所以一旦希望为一个类清除什么东西,必须写一个特别的方法,明确、专门地来做这件事情。同时,还要让客户程序员知道他们必须调用这个方

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2