面向对象方法与技术参考资料.doc_第1页
面向对象方法与技术参考资料.doc_第2页
面向对象方法与技术参考资料.doc_第3页
面向对象方法与技术参考资料.doc_第4页
面向对象方法与技术参考资料.doc_第5页
已阅读5页,还剩2页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

重载(overload):同一个类中名字相同但参数列表不同的多个方法之间的关系。关于重载,是我们比较熟悉的了,最常见的就是运用在类的多个构造函数中,看一下 Java 帮助文档,就可以明白这一情况了。下面一个谜题:1. publicclassConfusing 2. 3. privateConfusing(Objecto) 4. System.out.println(Object); 5. 6. 7. privateConfusing(doubledArray) 8. System.out.println(doublearray); 9. 10. 11. publicstaticvoidmain(Stringargs) 12. newConfusing(null); 13. 14. 问此时 main() 中将会输出什么?初初一看,并没有多分析就觉得应该是输出“Object”,虽然Java中的数组实际上也是引用类型,但毕竟Object 是所有类的最终父类,而且目前 JDK 就连参数中的基本数据类型变量也可以被自动想上转型成包装类而成为 Object 的子类。于是我保守一点地就认为参数 null 应该是匹配到 Object 那个重载方法去了。可是这答案是错的,JVM 对于重载方法的解析是这样的:先找出方法名匹配的所有可能的方法;然后根据传进来的形参再次筛选出可能的重载方法;最后才是在这些方法中匹配到一个最精确的一个方法。于是,上面的那个谜题就变成确定哪一个才是最精确这一点子上了。而关于如何判断最精确,有这样的机制:如果某个重载方法能够接收所有传递给另一个重载方法的实参类型,那么对于参数列表来看,显然后者至少是前者的子集,当然也就更精确了。回到谜题上来,Confusing(Object)可以接受任何传递给 Confusing(double )的参数(任何数组引用最终能够都是 Object 对象),因此 main() 中的 null 应该是被 JVM 匹配到 Confusing(double ) 中,也就有了与我所认为的结果相反的输出了。小结:这个谜题表明了我们在写重载方法时,最好是明确地区分出各个方法中的参数列表,不要让彼此之间有互相包含、模糊不清的关系。虽然重载是为了在相同名字的方法中传入实参,由 JVM 动态解析选择合适的方法,但有时也很容易陷入这种方便背后所带来的地雷区当中。其中一种可行的办法就是,提供不同的方法名。但是构造函数的名字一定得相同的啊?实际上,就算是构造函数,也有很好的重构手法将其清晰地区分开来,不使用重载而是用不同名称的方法,将原本需要重载的构造函数委托给具有最大完整参数列表的私有构造函数中。又是一本经典,值得看哦重写(override):父类中的实例方法被其子类重新实现。既然是实例方法,那就是非 static 修饰的了,否则就是 static 静态方法了,那叫做类方法。在我看来,正是重写这一机制的存在,才为多态机制提供了基础。或许 implements (实现)一个 interface (接口)中所声明的方法也能成为重写,因为 interface 的一部分存在原因也是为了多态。对于重写,在Java 解惑中有下面这个谜题让我明白:绝对不能在构造函数中调用可能会被子类重写的方法。1. classPoint 2. protectedfinalintx,y; 3. privatefinalStringname; 4. 5. Point(intx,inty) 6. this.x=x; 7. this.y=y; 8. name=makeName(); 9. 10. 11. protectedStringmakeName() 12. return+x+,+y+; 13. 14. 15. publicfinalStringtoString() 16. returnname; 17. 18. 19. 20. publicclassColorPointextendsPoint 21. privatefinalStringcolor; 22. 23. ColorPoint(intx,inty,Stringcolor) 24. super(x,y); 25. this.color=color; 26. 27. 28. protectedStringmakeName() 29. returnsuper.makeName()+:+color; 30. 31. 32. publicstaticvoidmain(Stringargs) 33. System.out.println(newColorPoint(4,2,purple); 34. 35. 此时程序运行结果并不是我们所想的 4,2:purple ,而是 4,2:null 。为什么会这样?看看下面用流程标号注释过的代码,就能理解了。1. classPoint 2. protectedfinalintx,y; 3. privatefinalStringname; 4. 5. Point(intx,inty) 6. this.x=x; 7. this.y=y; 8. name=makeName();/3.由于被子类重写过的makeName() 9. 10. 11. protectedStringmakeName() 12. return+x+,+y+; 13. 14. 15. publicfinalStringtoString() 16. returnname; 17. 18. 19. 20. publicclassColorPointextendsPoint 21. privatefinalStringcolor; 22. 23. ColorPoint(intx,inty,Stringcolor) 24. super(x,y);/2.调用Point父类构造函数 25. this.color=color;/5.初始化color,可是已经太晚了. 26. 27. 28. protectedStringmakeName() 29. /4.问题来了:它在子类构造函数之前调用了 30. /而此时的color是null的啊! 31. returnsuper.makeName()+:+color; 32. 33. 34. publicstaticvoidmain(Stringargs) 35. /1.调用ColorPoint子类构造函数 36. System.out.println(newColorPoint(4,2,purple); 37. 38. 思路很清晰了,ColorPoint 子类中的构造函数中的 this.color = color; 还未被执行到就将 null 作为 String color 的值了。正是因为这种来来回回的调用使得程序变得不正常了,在我看来,有那么一点类似于“回调”的意思。要去除这种代码结构的不合理,最好还是把 Point 父类构造函数中调用 makeName() 方法一句去掉,然后在 toString 中判断并调用 makeName() 来为 name 初始化隐藏(hide):子类的某个字段、静态方法、成员内部类与其父类的具有相同名字(对于静态方法还需要相同的参数列表),此时父类对应的字段、静态方法、成员内部类就被隐藏了。举个例子,天鹅(Swan)是会飞的,而丑小鸭(UglyDuck)小时候是不会飞的,看看下面的代码,看看能够打印出什么。1. classSwan 2. publicstaticvoidfly() 3. System.out.println(swancanfly.); 4. 5. 6. 7. classUglyDuckextendsSwan 8. publicstaticvoidfly() 9. System.out.println(uglyduckcantfly.); 10. 11. 12. 13. publicclassTestFly 14. publicstaticvoidmain(Stringargs) 15. Swanswan=newSwan(); 16. SwanuglyDuck=newUglyDuck(); 17. swan.fly(); 18. uglyDuck.fly(); 19. 20. 按道理的话,我们认为应该是输出两句不同的结果,因为我们可能认为 UglyDuck 继承了 Swan 并且“重写”了 fly() 方法,而且在 main() 方法中 Swan uglyDuck =newUglyDuck(); 也表明了 uglyduck 实际上是 UglyDuck 类型的,因此构成了多态行为。其实,运行结果是两句“swan can fly .”,为什么会这样子?原因有下:1、父类 Swan 中的 static 静态方法 fly() 是不能被重写的,上一段我对重写二字用了双引号;2、尽管子类 UglyDuck 中的 fly() 方法与父类中的有一致的参数列表,但是对于 static 方法来说,这叫隐藏(hide),而不是重写(override);3、对于 static 方法,根本不存在像多态那样的动态分派机制,JVM 不会根据对象引用的实际类型来调用对应的重写方法。这一点在个例子中是最重要的。对于 static 方法,我们称之为类方法,不是实例方法,对 static 方法的调用直接用所属类名加个点就行,如 UglyDuck.fly() 。而实例方法就不得不使用对象引用来获得其可访问方法的调用权。在上面的例子 main() 中的 uglyDuck.fly() 语句,JVM 根本据不会去判断 uglyDuck 引用的究竟是什么类型,既然调用的是 fly() 方法,那么 JVM 只会根据 uglyDuck 的声明类型(即 Swan 类)去获得该 static 方法的调用。根本就谈不上多态这就说明,最好避免用对象引用的方式来访问一个 static 方法。此外,别以为在继承关系上的父类、子类只要方法名、参数列表一致就是重写(override)而构成多态,其实还得看看父类中的方法有没有被什么修饰符声明(在这个例子中是 static 修饰的)。再如 final 修饰符的方法则表明不可被子类重写,即方法名、参数列表不能和父类完全一致。在我看来,这一类修饰符就表明了方法、变量、字段等特有的性质,或者是身份。对于隐藏(hide),实际上是为了使得父类中的该方法、字段、内部类等不允许再被下一级继承树的子子类所继承。说起隐藏,我想起代码大全 2当中刚好看过的内容,作者认为把握住信息隐藏的原则来思考软件构建要优于面向对象原则。有点抽象难懂,书中还讲到封装、模块化和抽象等几个概念,建议看看,我也要回过头去多啃啃这些抽象概念。要修改上面代码,只需要去掉两个 static 则可,那就构成多态了。Java 解惑中其他谜题还讲到多种该注意的地方,可以看看。小结:1、注意掌握重写(override)与隐藏(hide)的异同点:相同点就是两者都是相对于继承树中父类、子类来说,而不同点就是其目的以及所造成的效果。别把重写和隐藏混淆在一起了;2、对于 static 方法,要避免用具体的对象引用来调用,而应该简单的用其所属类名进行调用即可。遮蔽(shadow):其实就是平时我们可能遇到的窄作用域的变量名、方法名、类名等将其他相同名字的变量、方法、类屏蔽掉的现象。例如,最常见的就是局部变量将类实例变量屏蔽了。其实,遮蔽这个词我之前好像也没什么印象,不过作用域屏蔽这种情况我们大多应该会避免的了,因为课堂上、教材上对于变量作用域的内容已经讲解过了,尽管没有这么一个术语。此时如果想要获得被遮蔽实体的引用、调用,则只能通过完整的限定名去实现了。不过有一些情况可能是根本就引用不到的,被屏蔽得太严密了。遮掩(obscure):一个变量可以遮掩具有相同名字的一个类,只要它们都在同一个范围内:如果这个名字被用于变量与类型都被许可的范围,那么它将引用到变量上。相似地,一个变量名或一个类名可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类。如果一个类型或一个包被遮掩了,那么你不能通过其简单名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字。遵守命名习惯就可以极大地消除产生遮掩的可能性。其实,遮掩这个术语我更是完全没听过了,上面这一段是从Java 解惑中引用过来的。我觉得,如果代码是一个人所写,或者团队中大家都遵守了一定的命名规范,而且也各自分配了一定职责,那

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论