继承
类,超类,子类
超类又称父类、基类,是被继承的对象。子类又称派生类,是继承父类的对象。子类能从超类那里继承字段和方法,并可以添加新的方法和字段,但不会删除任何字段和方法。也就是说,子类比超类的功能更多。在设计类时,应当将最一般的方法放在超类中,将更特殊的方法放在子类中。
一个祖先类可以有多个派生类,且可以有多层次。即子类的下面还可以有子类。但一个子类只能有一个父类
覆盖方法
超类中的方法可能对于子类来说并不适用,因此,需要提供一个新的方法来覆盖超类的这个方法。
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 dayOfMonth){
this.name = name;
this.salary = salary;
hireDay = LocalDate.of(year,month,dayOfMonth);
}
/**
* @无参构造器
* */
public Employee(){
name = "none";
salary = 0;
hireDay = LocalDate.of(0000,1,1);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public LocalDate getHireDay() {
return hireDay;
}
public void setHireDay(LocalDate hireDay) {
this.hireDay = hireDay;
}
@Override
public String toString() {
return getClass().getName()+"{" +
"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 dayOfMonth,double bonus) {
super(name,salary,year,month,dayOfMonth);//调用父类的构造器,且该语句必须是子类构造器中的第一个语句
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public double getSalary() { //覆盖父类的方法
double baseSalary = super.getSalary();//调用父类的getSalary方法
return baseSalary + getBonus();
}
@Override
public String toString() {
return super.toString() +
"{ bonus=" + bonus+"}";
}
}
上述代码中子类Manager覆盖了父类的getSalary()方法,因为子类中有bonus字段,经理的薪资总和必须是工资+奖金。
注意一点,由于子类不能直接访问父类的字段,因此需要调用到父类的getSalary()方法。
调用方法是super.getSalary()。(注意,不能直接写成getSalary(),这样会一直调用自己陷入死循环。)
警告:在覆盖一个方法时,子类方法不能低于超类方法的可见度性。比如超类方法是public,则子类方法也要声明为public。
子类构造器
子类要创建自己的构造器,首先需要调用父类的构造器,这里同时也是super关键字的第二种用法
public Manager(String name, double salary,int year,int month,int dayOfMonth,double bonus) {
super(name,salary,year,month,dayOfMonth);//调用父类的构造器,且该语句必须是子类构造器中的第一个语句
this.bonus = bonus;
}
super(参数)是调用父类中带有指定参数的构造器,特别注意的是,该语句必须在构造器方法中的第一行。如果构造子类对象时没有显示的调用超类的构造器,那么超类中必须有一个无参构造器供子类调用。
super和this关键字很像,但两者用法不同:
this:一是指示隐式参数的引用,二是调用该类的其他构造器。
super:一是调用父类的方法,二是调用超类的构造器
多态
var staff = new Employee[3];
var boss = new Manager("boss",50000,1999,12,31,10000);
staff[0] = boss;
staff[1] = new Employee("zhangsan",10000,2001,12,31);
staff[2] = new Employee("lisi",5000,2002,12,31);
for (Employee e: staff) {
System.out.println(e.getName()+" "+e.getSalary());
}
输出结果:
boss 60000.0
zhangsan 10000.0
lisi 5000.0
尽管这里e声明为Employee对象,但实际上e既可以引用Employee对象,也可以引用Manager对象,当e引用为Manager对象时,e.getSalary()调用的是Manager对象的getSalary方法。
一个对象变量可以指示多种实际类型,这被称为多态。在运行时能够自动的选择适当的方法,称为动态绑定。
在这个例子中,变量staff[0]与boss引用同一个对象,但编译器只将staff[0]看做是一个Employee对象
这意味着,可以这样调用:
boss.setBonus(5000);
而不能这样:
staff[0].setBonus(5000);
注意,不能将超类的引用赋值给子类
在Java中,子类引用数组可以转换成超类引用数组,而不需要使用强制转换:
Manager manager[] = new Manager[10];
Employee staff[] = manager;
这是完全合法的,如果manager[i]是一个Manager,它也一定是一个Employee。但是,注意下面这段:
staff[0] = new Employee(“zhangsan”,5000,1999,12,31);
这段代码可以通过编译器,但是有个问题,staff和manager引用的是同一个对象,并且该对象是Manager类型,但是我们把Employee对象放入到Manager对象数组中,等于说我们把员工放到了经理行列中,这是不行的,如果我们此时调用manager[0].setBonus(5000),将会访问一个不存在的实例字段,进而搅乱相邻的存储空间内容。因此,每次创建数组时应当牢记其元素类型,并负责监督将类型兼容的引用存储到数组中。
理解方法调用
假设要调用x.f(args),隐式参数x声明为c类的一个对象,调用过程如下:
① 编译器查看对象的声明类型和方法,但有可能存在多个名字为f但参数不一样的方法,编译器将会一一列举c类中所有名为f的方法和其超类中所有名为f且可访问的方法
② 编译器根据提供的参数类型找到对应的方法,如果在所有名为f的方法中存在一个与所提供参数类型完全匹配的方法,就选择这个方法,该过程称为重载解析。如果没有找到或者发现经过类型转换后有多个方法与之匹配,则会报错。
③ 如果是private、static、final方法或者构造器,那个编译器可以准确地知道应该调用哪个方法,这称为静态绑定。如果要调用的方法依赖于隐式参数的实际类型,则称为动态绑定。
每次调用方法都要完成这个搜索,时间开销相当。因此虚拟机预先为每个类计算了一个方法表,其中列出了所有方法的签名(方法名和参数列表)和要调用的实际方法。
阻止继承
不允许扩展的类被称为final类,使用final修饰符。
如果方法被声明为final,表示所有子类都不能覆盖这个方法。
不过如果一个类被声明为final,则其中所有方法都自动声明为final。
其目的是为了确保他们不会在子类中改变语意。
强制转换类型
强制转换有两点需要注意:
1.只能在继承层次结构内进行强制类型转换
进行强制类型转换的唯一原因是:要在暂时忘记对象的实际类型之后使用对象的全部功能。
Java 编译器允许在具有直接或间接继承关系的类之间进行类型转换。如果把引用类型转换为子类类型,则称为向下转型;如果把引用类型转换为父类类型,则称为向上转型。
对于向下转型,必须进行强制类型转换;对于向上转型,不必使用强制类型转换。
如果两者之间没有继承关系,则不允许转换!
2.再将超类强制转换成子类之前,应当使用instanceOf进行检查
在进行强制类型转换之前,先查看能否成功的转换,为此需要使用instanceOf操作符,例如:
if(staff1 instanceof Manager){ //判断左边对象是否是右边的子类对象,或是否是右边对象类型本身,返回值为boolean类型
Manager manager2 = (Manager) staff1;
}
else{
System.out.println("无法强制转换");
}
因为staff是Manager的父类对象,因此无法转换。
子类中的东西要比父类多,因此将承诺夺得转换为承诺少的会出问题,因为可能转换后父类没有子类中的某些方法。