3.1 Java类的继承
在面向对象程序设计中,类的继承(inheritance)是其最重要的特色之一。通过继承可以创建分等级层次的类,使得对对象的描述更加清晰;通过继承可以实现代码的复用,实现减少编写程序工作量的目的;通过继承可以实现重写类中的变量或方法,修改和完善类定义;通过继承可以实现在无源代码的情况下修改被继承的类;通过继承可以实现使用Java类库提供的类。
3.1.1 概念和语法
在面向对象程序设计体系中,类的继承是其重要的一种机制,被定义为对已有的类加以利用,并在此基础上为其添加新功能的一种方式。类的继承与人类遗传因子的继承类似,与之不同的是,Java类的继承是单一的,而且继承是“完全”继承的,单一继承避免了多重继承出现的二义性等问题造成的继承混乱。
类的继承是通过Java关键字extends实现的,被继承的类称为父类,继承父类的类称为子类,类继承语句的语法格式为:
关键字extends是用来指示定义的类所继承的父类,由于Java体系规定Java类的继承是单一的,所以,extends关键字后面只能指定一个父类。继承有时也被称为派生,子类是由父类派生而来的。
在Java语言体系中,所有的类都是有父类的,当定义的类没有关键字extends指出父类,则该类默认的父类是Object类。
【示例3-1】 定义Sample类代码。
Sample类的父类是Object类,其等效的实际代码为:
Object类是java.lang基础包中的一个类,它被称为“根类”——所有类的根,Java体系中所有的类最终的父类就是Object类,即Java所有的类都是Object类的子类,换句话说,Java所有的类都是从Object类派生出来的,而java.lang包是Java语言体系提供编程的基础包。因此,Java的编程是从一个已存在的类开始的,在此基础上再添加一些新的属于子类的代码,从而实现程序新功能的要求。
Java的继承是完全的继承,即一个类继承了父类的所有状态(成员变量)和行为(成员方法)。例如,Sample类继承了Object类的所有成员,Object类中成员有clone、equals、finalize、getClass、hashCode、notify、notifyAll、toString、wait等方法,在用Sample类创建的类对象中,这些方法都可以被调用。因此,类的继承体现了Java代码的复用性。
Java的继承是一个单一“链”状态的,继承不只是对其直接父类的继承,父类包括所有直接或间接被继承的类,继承是一直延续到Object类。
【示例3-2】 继承演示示例。下述程序代码为父类代码:
SubSample类继承SuperSample类,SubSample是SuperSample类的子类,其代码为:
一个类对象的创建是从其根类到父类对象开始的,创建一个子类对象时,最先创建的是根类对象,然后依次创建父类对象,最后创建子类对象,但是,所有父类对象成员的引用都是通过子类对象实现的,就好像所有父类中的成员变量和方法全都包含在子类中一样。
【示例3-3】 下面的Sample程序是当使用SubSample类创建一个对象subSample时,父类中DisplaySuper()方法和子类中DisplaySub()方法都包含在subSample对象中,同时根类Object中的方法也包含在subSample对象中,因为SuperSample类继承了Object类。使用SubSample类创建一个对象subSample以及应用对象成员的程序代码为:
类的继承允许子类使用父类的变量和方法,如同这些变量和方法是属于子类本身的一样,但是,类的继承是有权限的,子类可以继承父类中访问权限设定为public,protected的成员变量和方法,不能继承访问权限为private的成员变量和方法。
【示例3-4】 Java类的继承实际上是子类对父类功能的扩展。例如一个描述平面直角坐标系的类为PlaneCoordinate,其中有x和y两个方向的坐标量。其程序代码为:
当需要定义一个空间直角坐标系类SpaceCoordinate时,只需要在继承了平面直角坐标系PlaneCoordinate类的子类中添加一个z方向的坐标量,其程序代码为:
上述程序是在已有平面直角坐标系PlaneCoordinate类的基础上扩展为SpaceCoordinate类,只添加一个坐标量z就可以实现描述空间直角坐标系的功能。
另外,当一个父类被验证没有错误时,例如Java语言提供的类库,通过子类继承后,只是在子类中添加新代码,新代码对父类没有任何影响。因此,如果子类创建的对象在运行时发生错误,则其错误将被限定在子类代码中,与父类代码无关。
3.1.2 Java类继承关系的测试
Java类都是有继承关系的,每个类至少有一个父类,还可以有多个父类,Java运行系统提供了动态测试一个对象是属于哪些类(含父类)的实例的功能。Java关键字instanceof就是用于实现该功能的,instanceof是一个双目对象运算符,使用instanceof运算符构成表达式的语法格式为:
由instanceof运算符构成的表达式功能是判断oneObj对象是否为ClassName类(或者ClassName类的父类)的一个实例,当oneObj对象是ClassName类的一个实例时,该表达式的值为true,否则为false。instanceof运算符的功能是可以明确子类继承了哪些父类,当该表达式的值为true时,说明创建oneObj对象的类与ClassName类是一个类,或者有父子关系,当该表达式的值为false时,说明两个类没有关系。
【示例3-5】 使用instanceof运算符的Java应用程序。
该程序执行结果为显示3个true,因为subSample对象是所有被测试类的实例。
3.1.3 隐藏、覆盖和重载
在面向对象程序设计中,由于Java的类都具有继承关系,并且所有的类都有父类,因此,在继承的过程中将会发生隐藏(hidden)、覆盖(override)和重载(overload)现象。隐藏、覆盖和重载都是针对父类中的非private成员变量和方法而言的,访问权限为private的变量和方法不会被继承到子类中,因此也就不存在隐藏、覆盖和重载的现象。
1. 隐藏
隐藏现象发生于子类与父类之间,隐藏是针对父类中成员变量和静态方法而言的。当在子类中声明与父类中成员变量具有相同变量名的变量时,则实现了对父类中成员变量的隐藏。
【示例3-6】 成员变量隐藏的演示程序。
当创建一个子类SubSample的对象时,该对象的成员变量只有子类中声明的变量x,在父类中声明的变量x被隐藏了,通过子类对象是访问不到父类中的x变量的。
当在子类中声明与父类中静态成员方法具有相同的方法名并具有相同的输入参数列表和相同的返回类型的方法,即子类与父类中的方法完全相同时,则实现了对父类中静态成员方法的隐藏。
【示例3-7】 成员方法隐藏的演示程序。
在父类中的Display()方法永远不能通过子类创建的对象实现引用,因为Java运行环境不可能为两个完全相同的静态方法分配不同的存储空间,因此,就父类的静态方法而言,实现对该方法的隐藏。
2. 覆盖
覆盖也称为重写,覆盖现象发生于子类与父类之间,是指在子类中声明一个与父类具有相同的方法名、输入参数列表、返回值、访问权限等的方法,不同之处只有方法体,即在子类中重新编写方法实现的功能。覆盖常用于替换父类相同的方法,实现功能的更新。
【示例3-8】 在子类中实现对父类中方法覆盖的Java演示程序。
覆盖不同于静态方法的隐藏,父类中被隐藏的方法在子类中是完全不可用的,而父类中被覆盖的方法在子类中是可以通过其他方式被引用的。另外,覆盖常被用于对接口中声明的方法的实现。
3. 重载
重载现象可以发生在子类与父类之间,也可以发生在同一个类中。重载是指在子类与父类之间或在同一类中定义多个具有相同的方法名、访问权限等的方法,这些方法的区别在于它们可以有不同的返回类型,或者有不同的输入参数列表,在参数列表中参数类型、参数个数、参数顺序等至少有一个是不相同。重载也可以看作定义不同的方法。
【示例3-9】 发生在子类与父类之间的重载方法的Java演示程序。
【示例3-10】 发生在同一类中重载方法的Java演示程序。
3.1.4 构造方法的重载
在创建对象的语句格式中,关键字new后指示的是调用类的构造方法,当一个类没有定义构造方法时,其创建类对象时调用无任何操作的默认构造方法,一个类默认的构造方法是指无形式参数列表的构造方法,在创建类对象的同时调用父类和根类的无形式参数列表的(默认)构造方法创建子类的对象。
【示例3-11】 默认父类构造方法创建子类对象的Java演示程序。
上述程序在main()方法中创建subSample对象时,其输出显示为:
该输出为SubSample类的父类被创建时调用其构造方法得到的输出显示。
当子类有自身的无形式参数列表的构造方法时,其创建对象时先创建父类对象,再创建子类对象。
【示例3-12】 创建子类对象的同时也创建父类对象的Java演示程序。
上述程序在main()方法中创建subSample对象时,其输出显示为:
其输出显示说明了创建对象的顺序。
当一个类有多个构造方法时,则发生构造方法的重载现象,创建类对象时需要指明使用哪个构造方法实现对象的创建。
【示例3-13】 多个构造方法创建类对象的Java演示程序。
上述程序在main()方法中分别使用不同的构造方法创建两个对象sample1和sample2。
当一个父类的构造方法出现重载现象时,即有多于一个构造方法时,并在父类中有默认的构造方法,应用子类创建类对象时,先创建的父类对象是使用父类中默认构造方法创建的,当父类没有默认构造方法,只有非默认构造方法时,在继承该父类的子类中需要显式调用父类的某一个非默认的构造方法来实现父类对象的创建。
【示例3-14】 显式调用父类构造方法的Java演示程序。
关键字super相当于指向父类构造方法的指针,通过它指定创建父类对象时所需要使用的父类中的一个非默认的构造方法。