在日常生活中,我们经常用到“是一种(IS-A)”关系来组织和表达知识,从而将知识组织成一种有层次、可分类的体系结构。例如,鸭梨是一种梨,梨是一种水果;大叶榕是一种树,树是一种植物等等,数不胜数。
在面向对象程序中,用IS-A关系来描述类与类之间的关系,称之为继承(inheritance)。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。继承是java程序设计中的一项核心技术。
有时候,我们需要创建一个新的类A,但是它具有一个已存在的类B所具有的功能,同时也有类B没有的功能。那么,我们可以认为类A与类B存在IS-A关系,“A IS-A B”。可以将A比作树,将B比作植物,那么树具有植物的特性,同时也具有树特有的特性,以区别花、草等其他植物。
创建类A时,我们可以将类B的代码复制过来,然后再在这个基础上添加代码。但这个方法让我们感到很麻烦。我们还可以用面向对象中的继承机制来构建这个新的类,我们只需要添加那些特有的特征就行了。使用继承机制,就不必每次都从头开始定义一个新的类,而是将这个新的类作为一个或者若干个现有的类的扩充或特殊化。
在以上类A和类B的例子中,类A继承了类B,那么类B称为类A的父类(parent)、超类(super-class)或基类(base),而类A称为类B的子类(child)或派生类(derived-class)。一个类的祖先类(ancestor)包括了其父类及其父类的祖先类,一个类的后代类(descendant)包括了其子类及其子类的后代类。类A继承了类B也可以说是类B派生出了类A。
继承机制主要具有双重作用:一是作为类的构造机制,二是作为类型的构造机制。
作为类的构造机制,继承是面向对象程序设计语言中支持软件重用的一种重要方式,而作为类型构造机制,继承是实现动态多态性的基础。
java中用保留字extends表示继承关系。继承的语法格式为:
public class 子类 extends 父类{
//子类新增的数据成员和方法
//子类重写父类的成员和方法
}
下面举例来说明java的继承机制,以员工和经理的关系为例。
代码清单1:Employee.java
import java.util.Date;
import java.util.GregorianCalendar;
/**
* 员工类
* @version v1.0 2018-03-13
* @adthor 叶汉伟
*/
public class Employee {
private String name;
private double salary;
private Date hireDay;
/**
* 构造函数
* @param n 员工姓名
* @param s 员工工资
* @param year 雇佣年份
* @param month 雇佣月份
* @param day 雇佣日
*/
public Employee(String n,double s,int year,int month,int day){
this.name=n;
this.salary=s;
GregorianCalendar calendar=new GregorianCalendar(year,month-1,day);
this.hireDay=calendar.getTime();
}
public String getName(){
return this.name;
}
public double getSalary(){
return this.salary;
}
public Date getHireDay(){
return this.hireDay;
}
public void raiseSalary(double byPercent){
double raise=salary*byPercent/100;
salary+=raise;
}
}
代码清单2:Manager.java
/**
* 经理类,继承了员工类Employee,经理也是员工。
* @version v1.0 2018-03-13
* @author 叶汉伟
*/
public class Manager extends Employee{
private double bonus;
/**
* 构造函数
* @param n 员工姓名
* @param s 员工工资
* @param year 雇佣年份
* @param month 雇佣月份
* @param day 雇佣日
*/
public Manager(String name,double s,int year,int month,int day){
super(name, s, year, month, day);
this.bonus=0;
}
//经理的工资=基本工资+奖金
public double getSalary(){
double baseSalary=super.getSalary();
return baseSalary+bonus;
}
//奖金
public void setBonus(double b){
bonus=b;
}
}
代码清单3:ManagerTest.java
public class ManagerTest {
public static void main(String[] args){
Manager boss=new Manager("Carl Cracker",80000,1987,12,15);
boss.setBonus(5000);
Employee[] staff=new Employee[3];
staff[0]=boss;
staff[1]=new Employee("Harry Hacker",50000,1989,10,1);
staff[2]=new Employee("Tommy Tester",40000,1990,3,15);
for(Employee e:staff)
System.out.println("name:"+e.getName()+",salary="+e.getSalary());
}
}
运行结果:
name:Carl Cracker,salary=85000.0
name:Harry Hacker,salary=50000.0
name:Tommy Tester,salary=40000.0
在以上程序中,我用Employee类型的对象staff[0]引用了Manager类型的对象,程序仍能正常运行,说明了程序中任何出现超类的地方都可以用其子类置换。在此例中,staff[0]与boss引用同一个对象,但编译器仍认为staff[0]是Employee类型的对象,boss为Manager类型的对像,所以staff[0]不能用Manager类的方法。如staff[0].setbounds(5000);
是错误的用法,编译器会报错。
继承机制引入了受保护(protected)成员,提供了一种新的成员访问控制级别,其可以理解为介于公有(public)和私有(private)之间。
在继承中,子类继承了超类除构造函数之外的所有成员,这些成员成为子类的继承成员。继承成员不仅包括超类中定义的共有、受保护及私有成员,还包括了超类的继承成员。
在子类中,子类可以访问自身定义的所有成员,也可以访问父超类的共有和受保护继承成员,但不能访问超类的私有继承成员。
继承成员在子类中的访问控制与它们在超类中的访问控制相同。及原先在超类中是共有的成员,被子类继承后认为共有的成员;原先在超类中是受保护的成员,被子类继承后仍为受保护的成员;原先在超类中是私有的成员,被子类继承后认为私有的成员,但子类不能访问。数据类型为子类的对象不能访问子类的及其父类的受保护成员
当子类要用到超类中的一个方法,而超类的这个方法有不适合子类时,我们可以选择在子类中重定义这个方法。子类重定义超类的方法是指的方法是指在子类中定义一个与超类的某个方法有完全相同接口的方法,这是称子类的这个方法重定义了超类同接口的方法。所为方法接口完全相同是指返回类型、方法名和方法参数列表完全相同。向以上例子中,Manager类的getSalary()方法就是重定义了超类Employee的getSalary()方法。
在重定义超类(祖先类)的方法时,不允许降低方法的访问控制权限。如在超类中是public的方法,在子类重定义时不能定义成protected或private。不过java允许在子类中重定义成员数据是降低其访问控制权限。如在超类中是public的数据成员,在子类可重定义为protected或private的数据成员。
对于成员数据来说,只要子类定义了与祖先类同名的成员数据就是重定义了祖先类的成员数据,而且屏蔽了祖先类的成员数据,子类及子类的使用者再也不能访问该祖先类的成员数据。
java中的this与super都是每一个对象实例中的特殊私有成员,是一个引用变量,this的类型是该对象实例所属的类类型,super的类型是它所属的对象实例的超类类型。
它们有两种使用方式:
第一种:调用类的数据或方法成员。this是调用自身的成员,而super则是调用其超类(祖先类)中的数据成员。如以上例子的Employee类,其部分代码如下:
public class Employee {
private String name;
......
public Employee(String name,double s,int year,int month,int day){
this.name=name;
.........
}
.......
}
在构造函数中用到了this.name=name;
,this指明了name是它所在的类(Employee)中定义的name,而不是参数列表中的那个name。这种使用方法的格式总结为:this.本类成员
和 super.超类成员
。其中超类成员不能是私有成员。
第二种:调用构造函数。this是在自身的构造函数中调用自身的另一个构造函数,而super则是在自身的构造函数中调用其超类的构造函数。例如Manager类的构造函数:
public Manager(String name,double s,int year,int month,int day){
super(name, s, year, month, day);//必须放在第一句
this.bonus=0;
}
其中有super(name, s, year, month, day);
,这是Manager的构造函数调用了其超类Employee的构造函数,初始化了继承成员name、salary、hireDay。这种使用方法的格式总结为:this(参数列表);
和 super(参数列表);
。注意,他们都必须放在构造方法的第一句。