当前位置:编程学习 > JAVA >>

Thinking in Java 4th chap8笔记-多态

1.封装通过合并特征和行为来创建新的数据类型。实现隐藏则通过将细节“私有化”,把接口和实现分离。而多态的作用则是消除类型之间的耦合关系。
     多态-动态绑定/后期绑定/运行时绑定
2.将一个方法的调用同一个方法主体关联起来被称作绑定。
     在前期绑定的情况下,即在程序执行前进行绑定,由编译器和连接程序实现。这样的话,编译器只有一个基类引用如Instrument的时候,它无法知道究竟调用哪个方法才对。解决的办法为后期绑定,它的含义就是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或者运行时绑定。如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时能动态判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。后期绑定机制随编程语言的不同而有所不同,但是只要想一下就会得知,不管怎样都必须在对象中安置某种类型信息。
     Java中除了static方法和final方法外(private方法也是final方法)之外,其他所有的方法都是后期绑定。这意味着,通常情况下,我们不必判断是否应该进行后期绑定,它会自动发生。为什么将某个方法声明为final,它可以防止其他人覆盖该方法。但也许更重要的一点是,它可以有效的“关闭”动态绑定,或者说告诉编译器不需要对其进行动态绑定。这样,编译器就可以为final方法调用生成更有效地代码。然而,大多是情况下,这样做对程序的整体性能不会有什么改观。所以,最好根据设计来决定是否使用final,而不是出于试图提高性能的目的来使用final。
     注:去查看一下java动态绑定的实现原理
3.可扩展性:
     在一个设计良好的oop程序中,大多数或者所有都会遵循tune模型,只与基类接口通信。这样的程序是可扩展的,因为可以从通用的基类继承出新的数据类型,从而添加一些功能。那些操纵基类接口的方法是不需要任何改动,就可以应用到新类。
      即tune接口不做任何修改,依旧正常运行,这就是我们期待的多态所具有的特性。我们所做的代码修改,不会对程序中其他不应该受到影响的部分产生破坏。换句话说,多态是一项让程序员“将改变的事物与未变的事物分开来”的重要技术。
4.    1.private方法被自动认为是final方法,而且对导出类是屏蔽的。即只有非private/final方法才可以被覆盖,不过还是要密切关注覆盖private方法的现象,这时虽然编译器不会报错,但也不会按照我们期望的执行。所以,在导出类中,对于基类中的priavate方法,最好不要采用相同的名字。
      2.多态缺陷之域与静态方法
           1.只有普通的方法调用是多态的,例如你直接访问某个域,这个访问就在编译期进行解析。
           2.如果某个方法是静态的,也不会具有多态性。因为静态方法是与类而不是与某单个对象关联的。
        注:1.个人觉得静态方法的调用是直接和静态方法所属的类相关的,而不是你多态指定了一个导出类,就会调用导出类的方法;其永远属于其所属类的。
        2.在你要覆盖的方法上加一个Override注解,就会都报错了,所以这是一个好习惯。
 5.构造器实际上是static方法,只不过该static声明是隐式的。
      基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊任务,即:检查对象是否被正确构造。导出类只能访问自己的成员,不能访问基类的成员,基类的成员通常是private的,只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有的构造器都能得到调用,否则就不可能构造完整对象。这正是编译器强制每个导出类部分都必须调用构造器的原因。
6.调用构造器顺序:
     1.调用基类构造器,这个步骤会不断的反复递归下去。首先是构造这种层次结构的根,然后是下一层导出类,等等,直至最低层的导出类。
     2.按照声明顺序调用成员的初始化方法。
     3.调用导出类构造函数的主体。
     ->保证基类中可供我们访问的成员都已得到初始化。->保证所有基类成员以及当前对象的成员对象都被初始化了。
     注:当基类中也有成员对象时,也按照相应顺序,先初始化成员对象,然后执行构造函数的主体
7.继承与清理:
     正常由垃圾回收器处理。
     不过如果确实遇到清理的问题,那么必须用心为新类创建dipose方法。由于继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose方法。当覆盖被继承类的dispose方法时,务必记住调用基类版本的dispose方法,否则基类的清理动作就不会发生。
     1.若某个子对象依赖于其他对象,则销毁的顺序应该和初始化顺序相反。对于字段,则意味着与声明的顺序相反。因为字段的初始化是按照声明的顺序进行的。对于基类,遵循C++析构函数的顺序,应该首先对其导出类进行清理,然后才是基类。这是因为导出类的清理可能会调用基类的某些方法,所以需要使基类的控件仍起作用而不是过早的销毁他们。
     注:如果某些成员对象中存在与一个或多个对象共享的情况下,如果要清理的情况下,则需要使用引用计数来跟踪那些依旧访问着共享对象的对象数量了。
     2.在一个构造器内部调用一个动态绑定方法->则会调用导出类的覆盖方法,而这个方法所操纵的成员可能还未进行初始化。这肯定会有很大问题.
      注:初始化的实际过程:
       1.在其他任何事物发生前,将分配给对象的存储空间初始化成二进制的0.
       2.调用基类构造器,此时如果调用被覆盖的draw方法,在调用子类构造器之前调用,由于步骤1的原因,我们会发现radius的值为0.
       3.按照声明顺序调用成员的初始化方法。
       4.调用导出类的构造主体。
 3.编写构造器准则:用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。在构造器内唯一能够安全调用的方法是基类中的final方法,也适用于private方法,private方法自动输入final方法。这些方法不能被覆盖,所以不会出现上述令人惊讶的问题。你可能无法总是遵循这条准则,但是应该朝着它努力。
8.协变返回类型:
     1.JavaSE5增加了协变返回类型。它表示在导出类的被覆盖方法可以返回基类方法的返回类型的某种导出类型。
9.
     1.如果首先考虑继承技术,则会加重我们的设计负担,使事情变的不必要的复杂起来。更好的方式是优先选择”组合“,尤其是不能确定应该使用哪一种方式时。组合不会强制我们的程序设计进入继承的层次结构中,而且组合更加灵活,因为它可以动态选择类型,因此也就选择了行为。相反,继承在编译时就需要知道确切类型。
      注:组合可以在运行期间获得动态灵活性--状态模式,而我们不能在运行期决定继承不同的对象,因为它要求在编译期间完全确定下来。见:Transmogrify.java
     2.一条通用的准则:用继承表达行为间的差异,用字段表达状态的变化。
     3.纯继承与扩展:
          1.所谓的纯粹的继承指只有在基类中已经建立的方法才可以才导出类中被覆盖,继承可以确保所有的导出类具有基类的接口,且绝对不会少。->导出类和基类具有一样的接口。也可以认为是一种纯替代,因为导出类完全可以替代基类,而在使用他们时,完全不需要知道关于子类的任何额外信息。->基类可以发送给导出类任何消息,因为二者有着完全相同的接口。我们只需要从导出类向上转型,永远不需要知道正在处理的对象的确切类型。所有这一切,都是通过多态处理的。
          2.扩展,可以称为is-like-a关系,因为导出类就像一个是一个基类,它有着相同的基本接口,但是它还具有由额外方法实现的其他特性。虽然这有一种有用且明智的办法(依赖于具体情况),但是它也有缺点。导出类中接口的扩展部分不能被基类访问,因此,一旦向上转型,就不能调用那些新方法。在这种情况下, 如果我们不进行向上转型,这样的问题就不会出现。但是通常情况下,我们需要重新查明对象的确切类型,以便能够访问该类型扩充的方法。
          3.向下转型与运行时类型识别:
  由于向上转型(在继承层次中向上移动)会丢失具体的类型信息。所以我们就想:通过向下转型,也就是在继承层次中向下移动,应该能够获取类型信息。然而我们知道向上转型是安全的,因为基类不会具有大于导出类的接口。因此我们通过基类接口发送的消息保证都能被接受。但是对于想下转型,如我们无法知道一个几何形状,它确实就是一个圆,它可以是一个三角形,正方形,或者其他一些类型。
补充:软件开发 , Java ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,