Core Java中的第五章主要涵盖以下话题:
下面逐一记录关键之处:
Java中的继承语法如下,用extends关键字表示继承关系。
public class Child extends Base {
//添加成员变量、成员方法
}
所谓继承,我觉得主要有以下4层含义:
相较于C++中的继承,Java中的继承有如下特点:
关于继承,有一个所谓的代替原则:既然子类是对基类功能的“增强”,那么在需要使用基类对象的场合用子类的对象代替基类的对象,应该也能满足相应的要求。继承在逻辑上的含义是“is a kind of”,即子类 is a kind of 父类。
针对上面的例子,我们给出一个完整版的实现,首先是Employee类:
import java.time.LocalDate;
public class Employee {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary, int year, int month, int day) {
this.name = name;
this.salary = salary;
this.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;
}
}
然后是
public class Manager extends Employee {
private double bonus;
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
}
既然Manager类中有bonus,计算工资时就自然而然应该加上这部分福利。那么怎么加呢?
public double getSalary() {
return salary + bonus;
}
像上面这样是肯定不行的,因为salary是Employee的私有成员变量,子类虽然在内存模型上继承了父类的所有成员变量,但是却不能直接访问。那既然父类提供了getSalary方法,能不能用其解决问题呢:
public double getSalary() {
return getSalary() + bonus;
}
答案是还是不行,因为我们正在重写子类的getSalary方法,在其中调用getSalary的话实际是一个递归调用。为了调用到父类的getSalary方法,我们需要使用super关键字:
public double getSalary() {
return super.getSalary() + bonus;
}
注意:
因此,最终的Manager类如下所示:
public class Manager extends Employee{
private double bonus;
public Manager(String name, double salary, int year, int month, int day) {
super(name, salary, year, month, day); //调用父类带参数的构造方法
bonus = 0;
}
public Manager(double bonus, String name, double salary, int year, int month, int day) {
this(name, salary, year, month, day); //调用本类其他构造方法
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public double getSalary() {
return super.getSalary() + bonus;
}
}
方法重载(overload)发生在同类同名、不同参数类型或参数个数的方法之间。其实,方法的重载并不神奇,在编译器的符号表中,根本就没有符号名相同的方法(有的话编译时就会报符号重定义的错误了!):
在C++中,有关键字virtual修饰的函数成为虚函数,而在类中对于虚函数的调用或者通过基类指针指向子类对象并调用虚函数将通过虚表来寻址,是软件开发特别是框架设计中惯用的方法。
Java中也有虚方法的概念,但与C++不同的是,Java中默认的成员方法即为虚方法,如果想定义非虚方法,需用关键字final修饰。下面继续用Employee和Manager类加以演示:
import java.util.ArrayList;
public class HelloWorld {
public static void main(String[] args) {
ArrayList staffs = new ArrayList();
staffs.add(new Manager("BOSS", 3000, 2006, 4, 22, 1000));
staffs.add(new Manager("员工1号", 1500, 2006, 4, 22));
staffs.add(new Manager("员工2号", 2100, 2006, 4, 22));
staffs.add(new Manager("员工3号", 1700, 2006, 4, 22));
for (Employee e : staffs) {
System.out.println(e.getName() + ":" + e.getSalary());
}
}
}
程序运行结果如下:
BOSS:4000.0
员工1号:1500.0
员工2号:2100.0
员工3号:1700.0
Process finished with exit code 0
可以看出,尽管在Employee的ArrayList数组中存储有Manager类型的对象,其getSalary方法还是被正确的调用了,这就是多态的强大功能。
另外需要注意的是,Java中没有指针的概念,对对象进行赋值其实是引用,因此以下情况不会触发类型转换造成切割:
Manager[] managers = new Manager[10];
Employee[] staffs = managers;
然而下面的操作虽然能通过编译,但却是危险的,因为staffs和managers其实引用的是同一个数组:
staffs[0] = new Employee("某甲", 1000, 2012, 5, 27);
managers[0].setBonus(2000);
程序运行后,引发了异常,请注意,在new的那一行就已经出错了。因为Java记录了数组中的数据类型,试图创建本数组不兼容对象当然会引发异常:
Exception in thread "main" java.lang.ArrayStoreException: Employee
总结:可用父类类型引用派生类对象实例,可以调用到父类定义而子类覆写(override)的函数,但不能调用父类中未定义而子类新增的函数。