继承
利用继承,人们可以基于已存在的类构造一个新类,继承已存在的类就是复用(继承)这些类的方法和域。在此基础上,还可以添加一下新的方法和域,以满足新的需求。
覆盖方法
简答的代码示例:
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;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
}
public class Manager extends Employee {
private double bonus;
public Manager(String name, double salary, int year, int month, int day) {
// Manager 类的构造器不能访问 Employee 类的私有域,所以必须利用 Employee 类的构造器对这部分私有域进行初始化, 且使用 super 调用构造器的语句必须是子类构造器的第一条语句。
super(name, salary, year, month, day);
this.bonus = 0;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public double geSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
}
上述两个类中,Manager 和 Employee 之间是 “is-a" 的关系” “is-a”是继承的一个明显特征, 根据继承的定义, Manager 类继承了 name, salary, hireDay 三个域以及对应的三个 get 访问器方法。将通用的方法放在超类中,而将具有特殊用途的方法放在子类中,在面向对象程序设计中十分普遍。
需要注意:
Manager 类的 getSalary 方法不能直接访问父类的私有域, 只有父类才能访问其私有域,因此需要父类提供一个访问 salary 的公共接口,通过 super 关键字调用。
super 与 this 的不同:
super 不是一个对象的引用,不能将 super 付给另一个对象变量,它只是一个指示编译期调用超类方法的特殊关键字。
父类与子类构造器的关系
- 当子类没有带参构造器和无参构造器,父类有带参构造器和无参构造器时, 子类会有默认的无参构造器,并且该无参构造器会默认调用父类无参构造器的方法。
public class ExtendsTest {
public static void main(String[] args) {
Son son = new Son();
}
}
class Father {
private int age;
public Father() {
System.out.println("Father no argument constructor");
}
public Father(int age) {
this.age = age;
}
}
class Son extends Father {
}
// output
// Father no argument constructor
- 当子类没有带参构造器和无参构器,父类只有带参构造器时编译时检查会报错,因为当写了带参构造器时系统不会给出默认的无参构造器,此时子类不能调用父类的默认无参构造器。
class Father {
private int age;
public Father(int age) {
this.age = age;
}
}
class Son extends Father { // error There is no default constructor available in Father
}
- 当子类没有带参构造器而有无参构器,且父类同时有带参构造器和无参构造器时,子类的无参构造器在第一行默认调用父类的无参构造器。
public class ExtendsTest {
public static void main(String[] args) {
Son son = new Son();
}
}
class Father {
private int age;
public Father() {
System.out.println("Father no argument constructor");
}
public Father(int age) {
this.age = age;
}
}
class Son extends Father {
private String name;
public Son() {
System.out.println("Son no argument constructor");
}
}
// output
// Father no argument constructor
// Son no argument constructor
总结
- 如果子类的构造器没有显示地调用超类的构造器,则会自动地超类默认(没有参数)的构造器,若父类只存在带惨构造则不会有默认的无参构造器。
- 如果父类没有无参构造器,并且子类的构造器又没有显示地调用父类的其他构造器,则Java编译期将报告错误。
final
如果类用 final 修饰,其他类就不能继承它,如果方法用 final 修饰,子类就不能重写它, 如果类中的域用 final 修饰,这个域在构造对象完成之后就不能被修改。final 修饰符的主要作用为:确保被修饰的对象在子类中不会改变语义。
注意:
- final 修饰类时,类中方法自动变成 final, 但不包括域。
- 如果方法很简单,被频繁调用且没有真正地被覆盖,那么及时编译器会将这个方法进行内联处理(inline),例如内联调用
e.getName()
会被替换为访问e.name
域。
强制类型转换
进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后,使用对象的全部功能。在 Java 中,每个对象变量都属于一个类型,类型描述了这个变量所引用的以及能够引用的对象类型。
Java 是强类型语言,在将一个值存入变量时,编译器将检查是否允许该操作,将一个子类的引用复制给一个超类变量,编译器时允许的(向上转型,此时会丢失子类扩展的方法),但将一个超类的引用复制给一个子类变量(向下转型,此时可以找回子类扩展的方法),必须进行类型转换,这样才能通过运行时检查。
一般只有在使用子类中的特有方法是才需要进行强制类型转换,此时可以使用 instanceof 先检查类型,再进行类型转换。
Employee[] staff = new Employee[3];
Manager boss = new Manager();
staff[0] = boss;
staff[1] = new Employee();
if (staff[1] instanceof Manager) {
boss = (Manager) staff[1];
}
注意:
null instanceof C // false,不会报错,因为null没有引用任何对象,当然也不会引用C类型对象
本文章与github上同步,欢迎来玩,提交issue。
Reference
1.[Java核心技术·卷 I(原书第10版)](继承