1 类,超类和子类
1 Java与C++定义继承类的方式十分相似。Java用关键字extends代替了C++中的冒号(:)。在Java中,所有的继承都是公有继承,而没有C++中的私有继承和保护继承。
2 子类方法并不能直接访问超类的私有域,而且应该用super.超类方法的方式获取私有域的值。super和this不一样,因为super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。在子类中可以增加域,增加方法或覆盖超类的方法,而不能删除继承的任何域和方法。在Java中使用关键字super调用超类的方法,而在C++中则采用超类名加上::操作符的形式。
3 由于子类的构造器不能访问超类的私有域,所以必须利用超类的构造器对这部分私有域进行初始化,我们可以通过super实现对超类构造器的调用。使用super调用构造器的语句必须是子类构造器的第一条语句。如果子类的构造器没有显式的调用超类的构造器,则自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则Java编译器将报告错误。
4 一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定。在Java中,不需要将方法声明为虚拟方法。动态绑定是默认的处理方式。如果不希望让一个方法具有虚拟特征,可以将它标记为final。
5 由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy),在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链。通常,一个祖先类可以拥有多个子孙继承链,例如,可以由Employee类派生出子类Programmer或Secretary,它们与Manager类没有任何关系。
2 多态
1 有一个用来判断是否应该设计为继承关系的简单规则,这就是is-a规则,它表明子类的每个对象也是超类的对象。例如,每个经理都是雇员,因此,将Manager类设计为Employee类的子类是显而易见的,反之不然,并不是每个雇员都是经理。。
2 在Java中,对象变量是多态的。一个Employee变量既可以引用一个Employee类对象,也可以引用一个Employee类的任何一个子类对象(例如,Manager,Executive,Secretary等)。
Manager boss = new Manager();
Employee[] staff=new Employee[3];
staff[0] = boss;
在这个例子中,staff[0]与boss引用同一个对象。但编译器将staff[0]看成Employee对象。这意味着,可以这样调用
boss.setBonus(5000);//OK
但不能这样调用 staff[0].setBonus(5000);// Error
这是因为staff[0]声明的类型是Employee,而setBonus不是Employee类的方法。
不能将一个超类的引用赋给子类变量因为不是所有的雇员都是经理。 Manager m = staff[i] //Error
3 在Java中,子类数组的引用可以转换为超类数组的引用,而不需要采用强制类型转换例如:
Manager[] managers = new Manager[10];
Employee[] staff = managers;//OK
staff[0]=new Employee("Harrry Hacker",,,);//OK
编译器允许这个复制操作,此时staff[0]和managers[0]引用的是同一个对象。似乎我们把一个普通雇员擅自归入经理行列之中了。这是一种很忌讳发生的事,当调用managers[0].setBonus(1000)的时候,将会导致调用一个不存在的实例域,进而搅乱相邻存储空间的内容。为了不发生这类错误,所有的数组都要牢记创建它们的元素类型,并负责监督仅将类型兼容的引用存储到数组中。
4 当程序运行,并且采用动态绑定调用方法时,,虚拟机一定调用与x类型所引用对象的实际类型最适合的那个类的方法。假设x的实际类型是D,它是C类的子类。如果D类定义类方法f(String),就直接调用它;否则,将在D类的超类中寻找f(String),以此类推。
5 在覆盖一个方法的时候,子类的方法不能低于超类方法的可见性。特别是,如果超类方法是public,子类方法一定要声明为public。经常会发生这类错误:在声明子类方法的时候,遗漏了public修饰符。此时,编译器会把它解释为试图提供更严格的访问权限。
6 不允许扩展的类被称为final类,如果在定义类的时候使用final修饰符就表明这个类是final类。例如,假设希望阻止人们定义Executive类的子类,就可以在定义这个类的时候,使用final修饰符声明。声明格式为:
public final class Executive extends Manager{
}
类中的特定方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法)。
域也可以被声明为final。对于final域来说,构造对象之后就不允许改变它们的值了。不过如果将一个类声明为final,只有其中的方法自动地成为final而不包括域。将方法或类声明为final主要目的是:确保它们不会在子类中改变语义。
7 有时需要将某个类的对象引用转换为另外一个类的对象引用。 譬如Manager boss = (Manager) staff[0];进行转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。例如,在managerTest类中,由于某些项是普通雇员,所以staff数组必须是Employee对象的数组。我们需要将数组中引用经理的元素恢复成Manager类,以便能够访问新增加的所有变量。将一个子类引用赋给一个超类变量,编译器是允许的,但是将一个超类的引用赋 给一个子类变量,必须进行类型转换,这样才能通过运行时检查。如果在继承链上进行向下的类型转换,需要先检查:
if(staff[1] instanceof Manager)
{
boss= (Manager)staff[1];
}
所以在进行类型转换前,要记住只能在层次内进行类型转换。在将超类转换为子类之前,应该使用instanceof进行检查。不过在一般情况下,应该尽量少用类型转换和instanceof运算符。
Employee.java package inheritance; import java.time.*; public class Employee { private String name; private double salary; private LocalDate hireDay; public Employee(){ name=""; salary=0; hireDay=LocalDate.now(); } public Employee(String name,double salary,int year,int month,int day){ this.name=name; this.salary=salary; hireDay = LocalDate.of(year,month,day); } public String getName(){ return name; } public double getSalary(){ return salary; } public LocalDate getHireDay(){ return hireDay; } public void raiseSalary(double byPercent){ double raise = salary*byPercent/100; salary+=raise; } }
Manager.java
package inheritance; public class Manager extends Employee { private double bonus; public Manager(){ super(); bonus=0; } public Manager(String name,double salary,int year,int month,int day){ super(name, salary, year, month, day); bonus=0; } public double getSalary(){ double baseSalary = super.getSalary(); return baseSalary+bonus; } public void setBonus(double b){ bonus=b; } public double getBonus(){ return bonus; } }
ManagerTest.java
package inheritance; public class ManagerTest { public static void main(String[] args){ Manager boss= new Manager("Carl Cracker",80000,1987,12,15); Employee[] staff =new Employee[3]; staff[0] = boss; boss.setBonus(5000); 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()); } } }