Java学习总结-第八章 继承和多态

本文总结源自《Java语言程序设计》原书第五版,作者为Y.daniel Liang,习题及编程练习均参照此书。


主要内容

在面向对象程序设计中,可以由已有的类派生出子类,这叫做继承(inheritance)
本章介绍继承的概念。着重理解父类和子类、关键字super的用法以及object类,了解多态和动态绑定、一般程序设计以及对象转换,介绍修饰符protect和final。

父类和子类

  • 在面向对象程序设计中,从另一个类C2中派生的类C1称为次类(subclass),C2则称为超类(superclass)。超类又称为父类(parent class)基类(base class),次类又称为子类(child class)扩展类(extend class)派生类(derived class)。子类从它的父类中继承可访问的数据域和方法,也可以添加新数据域和方法。

关键字super

  • 调用父类的构造方法
    • super(); 或super(参数);
    • 以上语句必须出现在子类构造方法的第一行,这也是调用父类构造方法的唯一方式。
    • 构造方法链(constructor chaining):在任何情况下,构造一个类的实例时,将会沿着继承链调用所有父类的构造方法,这叫做构造方法链
  • 调用父类的方法
    • super.方法名(参数);

方法覆盖

  • 有时候子类需要修改父类中定义的方法,这叫做方法覆盖(method overriding)
    • 为了覆盖方法,子类定义的方法必须与父类方法具有相同的头标志和相同的返回值类型。(使用ctrl+c、ctrl+v)
    • 实例方法仅当可访问时才能被覆盖。如果子类中定义的方法在父类中是私有的,那么这两个方法完全没有关系。
    • 静态方法也能继承,但不能被覆盖。如果父类中定义的静态方法在子类中重新定义,父类的方法将被隐藏。

对象类Object

  • Java中的每个类都源于java.lang.Object类。如果一个类在定义时没有指定继承谁,它的父类就是Object。
  • Object类的常用实例方法

    • equals()方法:检验两个对象是否相等。其默认实现为:
      1
      2
      3
      public boolean equals (Object obj) {
      return (this == obj);
      }

    由此可知,Object类中的equals()方法相当于使用==运算符。因此,Object类的子类很有必要修改equals方法,以便检验两个不同的对象是否具有相同的内容。

    • hashCode()方法:调用对象的hashCode()方法返回该对象的哈希代码。
      • 哈希码(hash code)是一个整数,用于在混杂集合中存储并能快速查找对象。
      • 编写代码时,如果两个对象相等,它们的哈希码一定相同。两个不同的对象也可能具有相同的哈希码。
      • 在程序一次运行期间,多次调用hashCode方法应该返回相同的整数,对不同的运行可以返回不同的整数。
    • toString()方法:返回一个代表该对象的字符串。默认情况下,返回一个由该对象所属的类名、at符号(@)和该对象十六进制的哈希码组成的字符串。

多态性、动态绑定和一般程序设计

  • 继承关系使一个子类继承父类的特征,并可以附加一些自己的新特征。子类是父类的某种专门化,每一个子类的实例都是父类的实例。因此对于需要父类类型的参数,总可以传入子类的实例。
  • 为父类对象设计的任何形式的代码都可以应用于子类,这个特征称为多态性(polymorphism)
  • 动态绑定(dynamic binding):代码提供多种实现,采用哪一种实现由Java虚拟机在运行时动态决定。
    • 动态绑定工作机制如下: //todo
  • 多态性一般允许方法使用范围更广的对象参数,这称为一般程序设计(generic programming)

对象类型转换和instanceof运算符

  • 转换可以把一个类的对象转换为继承链中的另一个对象。
  • 隐式类型转换(implicit casting):子类的实例永远是父类的实例,因此可以将一个子类的实例转换为父类的一个变量。这称为向上转换(upcasting)

    1
    Fruit f = new apple();
  • 显式类型转换(explicit casting):父类对象不一定是子类的实例,因此转换需要显式的告诉编译器转换的类型。把父类的实例转换成它的子类变量,这称为向下转换(downcasting)

    1
    Apple a = (Apple)f;
  • 为了确保显式转换能成功,可以用运算符instanceof来判断:

    1
    2
    3
    if(f instanceof Apple) {
    Apple a = (Apple)f;
    }
  • 为了能够进行一般程序设计,一个好的做法是把变量声明为父类类型,这样它可以接收任何子类类型的值。

隐藏数据域和静态方法

  • 可以覆盖一个实例方法,但不能覆盖一个数据域(实例或静态的)或静态方法。如果在子类中声明的数据域或静态方法与父类中的名字相同,父类中的将被隐藏,但是它依然存在。在子类中可以使用关键字super调用隐藏的数据域或静态方法,隐藏的域或方法也可以通过父类类型的引用变量来访问。
  • 使用引用变量调用实例方法时,变量所引用对象的实际类在运行时决定使用该方法的哪个实现。访问数据域或静态方法是,引用变量所声明的类型在编译时决定使用哪个数据域或静态方法。

protected数据和方法

  • 保护的(protected)修饰符可以应用于类中的数据和方法。公用类中保护的数据或方法可以被它的子类或同一包中的任何类访问,即使子类在不同的包中也可以。
  • 使用可见性修饰符
    修饰符private、protected、public称为可见性修饰符(visibility modifier)访问性修饰符(accessible modifier),因为他们指定类和类的成员如何被访问。这些修饰符的可见性按下面的顺序递增:
                   可见性逐渐增加
             -------------------------→
    private,空(如果没有修饰符),protectedpublic
    

final类、方法和变量

  • 用final修饰符说明一个类是终极的(final),不能做父类。例如:Math类。
  • 也可以把方法定义为终极的,终极方法不能被它的子类覆盖。
    方法内的终极局部变量就是常量。

finalize、clone和getclass方法

  • finalize()方法

    1
    protected void finalize() throws Throwable
    • 当一个对象变成垃圾时,finalize方法会被该对象的垃圾回收程序调用。
    • 默认情况下,finalize方法不做任何事。如果必要,子类应该覆盖finalize方法,释放系统资源或进行其他清洁工作。
      finalize方法是有Java虚拟机调用的,在自己的程序中不要书写任何调用该方法的代码。
  • clone方法
    • 要创建一个有单独内存空间的新对象,使用clone()方法。
    • 不是所有对象都可以被克隆。要成为一个可克隆的对象,它的类必须实现java.lang.Cloneable接口。
  • getClass方法
    • 一个类在使用时必须先装入。Java虚拟机装入类是,创建一个包含类信息的对象,这些信息有类名、构造方法和方法等。这个对象是java.lang.Class的一个实例,因为它描述有关类的信息,称它为元对象(meta-object)。每个对象都可以使用getClass()方法返回它的元对象。
    • 一个类只有一个元对象。每个对象都有一个元对象,如果两个对象是从同一个类创建的,那么它们的元对象相同。

初始化模块

  • 初始化模块(initialization block):是一个用大括号括住的语句块,它位于类的声明中,但是不在方法或构造方法内。它的作用就像把它放在了类中每个构造方法最开始的位置。
  • 一个类可以有多个初始化模块。在这种情况下,模块按照它们在类中出现的顺序执行。
  • 初始化模块分为实例初始化模块(instance initialization block)静态初始化模块(static initialization block)
  • 包含初始化模块的程序的执行顺序如下:
    1、类装载:静态数据域,静态初始化模块
    2、类创建:构造方法,实例数据域,实例初始化模块
    3、类运行:实例方法体

复习小结

  • 子类不是父类的自给,子类可以在父类的基础上进行扩展。
  • 如果一个类设计成子类,最好提供一个无参构造方法以避免程序错误。
  • 多态存在的条件:
    1、要有继承
    2、要有重写
    3、父类引用指向子类对象

编程练习

习题8.2 8.4源代码见我的Github: chapter8



本文章首发www.whtis.com,转载请注明出处


如果觉得这篇文章还有用的话,请我喝杯饮料呗~~