Java核心技术-继承

继承

继承的基本思想是:基于已有的类创建新的类。

父类并不是优于子类或拥有比子类更多的功能,子类比父类拥有的功能更多。

Employee类(父类、超类)

public class Employee {
    private String name;
    private Date hireDay;
    private Double salary;

    public Employee() {
    }

    public Employee(String name, Date hireDay, Double salary) {
        this.name = name;
        this.hireDay = hireDay;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getHireDay() {
        return hireDay;
    }

    public void setHireDay(Date hireDay) {
        this.hireDay = hireDay;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }
}

Manager类(子类)

public class Manager extends Employee{
    private Double bonus;

    public Manager() {
    }

    public Manager(Double bonus) {
        this.bonus = bonus;
    }

    public Manager(String name, Date hireDay, Double salary, Double bonus) {
        super(name, hireDay, salary);
        this.bonus = bonus;
    }

    public Double getBonus() {
        return bonus;
    }

    public void setBonus(Double bonus) {
        this.bonus = bonus;
    }
}

由于setBonus不是Employee类中定义的,所以属于Employee类的对象不能使用它。但尽管Manager类中没有显示地定义getNamegetHireDay等方法, 但是可以对Manger对象使用这些方法,这是因为Manager类自动地继承了父类Employee中地这些方法。童颜地namesalaryhireDay都是从父类继承而来。

通过扩展父类定义子类的时候,只需要指出子类与父类的不同之处。因此在设计类的时候,应该将最一般的方法放在父类中,而将更特殊的方法放在子类中,这种将通用功能抽取到父类的做法在面向对象程序设计中十分普遍。

覆盖方法

父类中有些方法子类并不一定适用,以薪水salary为例,Manager子类中,薪水应该是工资和奖金的总和。因此需要提供一个新的方法来覆盖(override)父类中的这个方法。

Manager类中覆盖getSalary方法

@Override
public Double getSalary() {
    return super.getSalary() + this.getBonus();
}

注意,因为salary字段是从Employee类中继承而来,而本身Manager并没有这个字段(显式的指定),所以我们需要使用特殊关键字super解决这个问题:super.getSalary()

superthis的概念并不一样,因为super不是一个对象的引用,例如,不能将值super赋给另一个对象变量,它只是一个指示编译器调用父类方法的特殊关键字。

构造器

public Manager(String name, Date hireDay, Double salary, Double bonus) {
    super(name, hireDay, salary);
    this.bonus = bonus;
}

这里的super具有不同的含义,由于Manager类的构造器不能访问Employee类的私有字段,所以必须通过一个构造器来初始化这些私有字段,可以利用特殊的super语法调用这个构造器。使用super调用构造器的语句必须是子类构造器的第一条语句

关于thissuper

关键字this有两个含义:

  • 隐式参数的引用
  • 调用该类的其他构造器

关键字super有两个含义:

  • 调用父类的方法
  • 调用父类的构造器

thissuper这两个关键字紧密相关,调用构造器的语句只能作为另一个构造器的第一条语句出现。构造器参数可以传递给当前(this)的另一个构造器,也可以传递给父类(super)的构造器。

接下来我们可以这样使用:

public class Test {
    public static void main(String[] args) {
        Employee[] employees = new Employee[3];
        Manager boss = new Manager("ljq", new Date(), 20000d, 10000d);
        employees[0] = boss;
        employees[1] = new Employee("zcc", new Date(), 20000d);
        employees[2] = new Employee("coco", new Date(), 20000d);
        Arrays.stream(employees)
                .forEach(employee -> System.out.println(employee.getSalary()));
    }
}

注意,这里的employees虽然是Employee对象数组,但完全可以将子类的Manger赋值进去,当元素引用的是Employee对象时,调用的是Employee类的getSalary方法,当元素是Manager时,调用的时Manager类中的getSalary方法。虚拟机知道元素实际引用的对象类型,因此能够正确的调用相应的方法。

上述情况被称为多态:一个对象变量可以指示多种实际类型的现象称为多态(polymorphism)。这种在运行时能够自动选择适当的方法称为动态绑定(dynamic binding)。

继承层次

继承并不仅限于一个层次。如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qk6bwu5q-1672060059326)(E:\笔记-全\图片\继承层次图.png)]

由一个公共父类派生出来的所有类的集合称为继承层次。在继承层次中,从某个特定的类到其祖先的路径称为该类的继承链

java中不支持多继承,但可以实现多个接口。

多态

有一个简单规则可以用来判断是否应该将数据设计为继承关系,这就是 is-a 规则,它指出子类的每个对象也是父类的对象

is-a 规则的另外一种表述是替换原则。它指出程序中出现父类对象的任何地方都可以使用子类对象替换。

方法调用

假设要调用x.f(args),隐式参数x声明为类C的一个对象。下面是调用过程的详细描述:

  1. 编译器查看对象的声明类型和方法名,需要注意的是:有可能存在多个名字为f但参数类型不一样的方法。编译器将会一一列举C类中所有名为f的方法和其父类中所有名为f而且可访问的方法。
  2. 接下来,编译器要确定方法调用中提供的参数类型,如果在所有名为f的方法中存在一个与所提供参数类型完全匹配的方法,就选择这个方法。这个过程为重载解析(overloading resolution)。如果没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,编译器就会报告一个错误。注意:返回类型虽然不是方法签名中的一部分,但是在覆盖一个方法的时候,需要保证子类中的方法的返回类型是父类方法返回类型的子类(如父类中返回的是Employee类型,则子类中可以是Manager),这种方法被称为有可协变的返回类型。
  3. 如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,这被称为静态绑定
  4. 程序运行并采用动态绑定调用方法时,虚拟机必须调用与x所引用对象地实际类型对应的那个方法。

在覆盖一个方法的时候, 子类方法不能低于父类方法的可见性。

阻止继承:final类和方法

有时候,我们可能希望阻止人们利用某个类定义子类,**不允许扩展的类被称为final类。**格式如下:

public final class Executive extends Manager{
}

类中某个特定方法也可以被声明为final,如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动称为final方法)

public final String getName() {
    return name;
}

字段也可以声明为final对于final字段来说,构造对象之后就不允许改变它们的值了(只能赋值一次),final类中只会将方法变成final,字段却不会。

将方法和类声明为final的主要原因是:确保它们不会在子类中改变语义

强制类型转换

**将一个类型强制转换称另一个类型的过程被称为强制类型转换。**进行强制类型转换的唯一原因是:要在暂时忽视对象的实际类型之后使用对象的全部功能。

在将一个值存入变量时,编译器将检查你是否承诺过多,如果将一个子类的引用赋给一个父类变量,编译器是允许的。但将一个父类的引用赋给一个子类变量时,就承诺过多了,必须进行强制类型转换,这样才能通过运行时检查。

个人理解:承诺过多感觉不是特别准确,我认为可以理解为承诺能做到的过多比较合适,子类引用给父类变量,父类变量并没有承诺做到的比子类变量多,而父类引用给到子类变量,这个变量承诺做到的东西会远远大于父类。简单来说,就是变量的承诺应小于等于引用实际的功能,不然我根据变量使用方法时,却发现引用找不到对应的方法,这不就是承诺能做到的功能过多了嘛

承诺过多会产生ClassCastException异常。

综上所述:

  • 只能在继承层次内进行强制类型转换。
  • 在将父类降至转换成子类之前,应使用instanceof进行检查。

equals方法设计原则

  1. 自反性:对于任何非空引用xx.equals(x)应该返回true
  2. 对称性:对于任何引用xy,当且仅当y.equals(x)返回true时,x.equals(y)返回true
  3. 传递性:对于任何引用xyz,如果x.equals(y)返回truey.equals(z)返回truex.equals(z)也应该返回true
  4. 一致性:如果xy引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
  5. 对于任意非空引用xx.equlas(null)应该返回false

在父类与子类之间,设计equals方法有两种情形:

  1. 如果子类可以有自己的相等性概念,则对称性需求将强制使用getClass检测。
  2. 如果由父类决定相等性概念,那么就可以使用instanceof检测,这样可以在不同子类的对象之间进行相等性比较。

书中给出的完美的equals方法的建议:

  1. 显示参数命名为otherObject,稍后需要将它强制转换成另一个名为other的变量。
  2. 检测thisotherObject是否相等(使用==判断内存地址)
  3. 检测otherObject是否为null,如果为null,返回false。(很有必要)
  4. 比较thisotherObject的类,如果equals的语义可以在子类中改变,就是用getClass检测;如果所有的子类都有相同的相等性语义,可以使用instanceof检测;
  5. otherObject强制转换为相应类类型的变量;
  6. 根据相等性概念的要求来比较字段。使用==比较基本类型字段,使用Objects.equals比较对象字段。如果所有的字段都匹配,就返回true,否则返回false

根据上述建议编写Employee类的equals方法

@Override
public boolean equals(Object otherObject) {
    if (this == otherObject) return true;
    if (otherObject == null || getClass() != otherObject.getClass()) {
        return false;
    }
    Employee other = (Employee) otherObject;

    return this.name == other.name
        && this.hireDay.equals(other.hireDay)
        && this.salary == other.salary;
}

反射

反射机制可以用来:

  • 在运行时分析类的能力;
  • 在运行时检查对象,例如,编写一个适用于所有类的toString方法;
  • 实现泛型数组操作代码;
  • 利用Method对象;

Class类

在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确的方法。

可以使用静态方法forName获得类名对应的Class对象。

try {
    Class<?> aClass = Class.forName("five.Employee");
    Method[] method = aClass.getMethods();
    Arrays.stream(method).forEach(System.out::println);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值