《Java核心技术》第10版读书笔记之Chap5(1)——类的继承

主要内容

Core Java中的第五章主要涵盖以下话题:

  • 面向对象基础
  • Object类
  • ArrayList<>容器简介
  • 基本数据类型的封装类与自动装/拆箱
  • 变参函数
  • 枚举类
  • 反射

下面逐一记录关键之处:

继承基础

Java中的继承

extends关键字

Java中的继承语法如下,用extends关键字表示继承关系。

public class Child extends Base {
    //添加成员变量、成员方法
}

所谓继承,我觉得主要有以下4层含义:

  1. 子类继承了父类成员方法的调用权
  2. 子类继承了父类的完整对象(即成员变量)
  3. 子类可以在此基础上增加新的成员方法和成员变量
  4. 子类也可以覆写(override)父类的成员方法,这一点是运行时多态的基础。

相较于C++中的继承,Java中的继承有如下特点:

  • 只有公有类型的继承
  • 只有单继承(但可实现多个接口)

关于继承,有一个所谓的代替原则:既然子类是对基类功能的“增强”,那么在需要使用基类对象的场合用子类的对象代替基类的对象,应该也能满足相应的要求。继承在逻辑上的含义是“is a kind of”,即子类 is a kind of 父类。

super前缀

针对上面的例子,我们给出一个完整版的实现,首先是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;
}

注意:

  1. super前缀仅告诉Java编译器在其父类中寻找该方法,并不表示一个指向基类对象的指针或引用,这一点和this不同。
  2. super前缀有两类用法:
    • 用super.方法名()指明调用父类的成员方法
    • 用super(参数)的方式在子类构造方法的入口处调用父类构造方法(这点有点类似于C++中的初始化表。注意:如果不用此方法人工调用,编译器默认调用父类的无参构造方法,若父类没有无参构造方法,则编译时报错)
  3. super关键字和this引用的实质不同,但就用途来说还是有相似之处的:
    • this对象指代的是本类型当前的实例对象,可用其访问本类中的非静态成员变量或函数。
    • 用this(参数)在该类构造函数入口处调用另一个构造函数

因此,最终的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<Employee> staffs = new ArrayList<Employee>();
        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)的函数,但不能调用父类中未定义而子类新增的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值