Java核心技术之继承

Java核心技术之继承

目录

一、继承的定义

多个类中存在相同属性和行为,将这些内容抽取到单独的一个类中,那么多个类无需再定义这些属性和行为,只需要继承那个类即可。 —— [ ImportNew ]

二、继承的实现

1.类的继承

定义子类

由继承的定义,可以得出超类和子类的概念,一般也称之为父类和子类。
定义中,“多个类”可以称之为子类,“单独的一个类”称为父类超类
子类可以直接访问父类中的非私有属性和行为。关键字extend表示继承。采用书上的例子进行讲解:

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 this.name;
    }
    public double getSalary(){
        return this.salary;
    }
    public LocalDate getHireDay(){
        return this.hireDay;
    }

}

下面是由继承Employee类来定义Manager类的格式:

public class Manager extends Employee
{

/**添加方法和域**/

}

extend关键字代表,Manger类这一新派生的类继承自Employee类,Employee类是已存在的类,称为父类;Manager类是由Employee类派生得来的,称为子类。根据继承的定义,新派生的Manager可以直接访问Employee类中的非私有属性和方法。在Manager类中不需要定义getName()等方法,直接访问父类的方法就可以获得返回值。
在Manager类中新,增加一个用于存储奖金信息的域,以及用于设置这个域的新方法。

public class Manager extends Employee{
    private double bonus;
    ...
    public void set Bonus(double bonus){
        this.bonus = bonus;
    }
}

对与类来说,父类是对一类事物最抽象最通用的域和方法的抽象,子类再继承这些通用的内容后,可以在自己的类内定义新的只属于自己的域和内容。所以,子类会被父类拥有更加丰富的功能。

抽象类

抽象就是从多个事物中将共性、通用的内容抽象出来。抽象方法是只有定义没有方法体,其具体实现由子类去完成。包含一个或多个抽象方法的类称之为抽象类。Employee类和Manager类其实都可以继承自Person类,而Person类又可以派生出Student和Teacher类。假设,需要为Student类和Teacher类加一个getDescription方法,用于对不同身份的类进行信息描述。在父类Person中就可以将getDescription定义为一个抽象方法,具体的实现由相应的子类去完成。

public class Person(){
    .......
    public abstract String getDescription();
}

public class Student extends Person(){
    ......
    public String getDescription(){
        system.out.println("I am a student!");
    }
}

public class Teacher extends Person(){
    ......
    public String getDescription(){
        system.out.println("I am a Teacher!");
    }
}

如果多个对象都具有相同的功能,但是功能的具体内容并不相同,在父类中可以抽象这个功能的定义,将该方法定义成一个抽象方法,不去做具体实现。

抽象类的特点:
  • 抽象方法一定在抽象类中;
  • 抽象方法和抽象类都必须被abstract关键字修饰;
  • 抽象类不可以用new创建对象,因为调用抽象方法没有意义;
  • 抽象类中的抽象方法要被使用,必须由子类复写父类的所有抽象方法后建立子类对象的调用;如果子类只覆盖了部分的抽象方法,那么该子类还是一个抽象类;
  • 抽象类可以有抽象方法,也可以有非抽象方法;
  • 如果一个类是抽象类,那么,继承该类的子类要么是抽象类,要么重写了所有的抽象方法。
    特殊,如果一个类被定义成抽象类,但是在该类中并没有抽象方法,这样做仅仅是为了不让该类创建对象。
抽象类的注意事项:
  • final:如果方法被抽象,在子类中该法规就会被覆盖,而final不可以被覆盖,所以冲突。
  • private:如果函数被私有化,子类就无法直接访问,而抽象方法就是为了让子类访问并复写。
  • static:static方法不需要对象,可以用类名直接调用抽象方法,而抽象方法本身是没有实现的,所以调用抽象方法本身并没有意义。

2.覆盖方法

前面例子中的代码以及涉及到了覆盖方法的例子。就是,当我们父类定义好的方法并不适用于子类时,子类需要提供一个新的方法来覆盖(override)父类中的这个方法。根据定义Employee的getSalary方法只返回固定薪资salary的值,而Manager类生成的对象是“经理”,经理所获得薪资定义为固定薪资salary加上奖金bonus。此时Employee类中的getSalary方法就不在适用于当前的子类Manager类。
应该如何实现?

public double getSalary(){
    return salary+bonus;   //don't work编译时报错
}

这是因为Manager类并不能访问父类中的私有域。虽然每个Manager对象拥有一个salary域,但是只能通过Employee类中的非私有方法getSalar方法获取返回值,并不能直接访问到salary域。
如果将对salary域的访问替换成调用父类中公共方法getSalary是不是就能正常运行了呢?

/**运行时报错**/
public double getSalary(){
    double salary = getSalary()
    return salary+bonus; 
}

如果Manager类一定要访问父类的私有域,只能借助于公有的接口getSalary()。虽然,借助getSalary能够访问到父类的私有域,但是上面的代码仍不能正确执行。这是因为,在Manager类中也有一个getSalary方法(就是我们需要实现的这个方法),所以这条语句会无限次的调用自己,知道程序崩溃。这里需要指出,我们希望调用Employee类中的getSalary方法时,需要使用特定的关键字super

super.getSalary()

上述语句调用的就是父类中的getSalary方法。

/**运行时报错**/
public double getSalary(){
    double salary = super.getSalary()
    return salary+bonus; 
}

super和this有什么区别?
super是一个关键字,代表父类的存储空间标识。(可以理解为父亲的引用)
super和this的用法相似。
this代表对象的引用(谁调用就代表谁);
super代表当前子类对父类的引用。

3.接口的实现

定义

接口是抽象方法和常量值的集合。从本质上讲,接口是一种特殊的抽象类,这种抽象类只包含常量和方法的定义,没有变量和方法的实现。

定义接口
interface Instrument{
    int VALUE = 5;
    void play(Note n);
    void adjust();
}

下面是用关键字implements实现接口的格式:

class Wind implements Instrument{
    public void play(Note n){
        print(this + ".play() " + n);
    }
    public void adjust(){
        print(this + ".adjust()");
    }
}

class Percussion implements Instrument{
    public void play(Note n){
        print(this + ".play() " + n);
    }
    public void adjust(){
        print(this + ".adjust()");
    }
}

......

特点

  • 接口不能被实例化;
  • 一个类如果实现了接口,要么是抽象类(实现接口的部分方法),要么实现接口中的所有方法。

总结

继承与实现的区别:
  • 类与类之间称为继承关系:因为该类无论是抽象还是非抽象,它的内部都可以定义非抽象方法,这个方法可以被子类直接使用。只能单继承,可以多层继承。
  • 类与接口直接是实现关系:因为接口中的方法都是抽象的,必须由子类实现才可以实例化。可以单实现也可以多实现,还可以在继承一个类的同时实现多个接口。
抽象类与接口的区别

我们将接口视为一个特殊的抽象类。

成员变量
  • 抽象类能有变量,也可以有常量
  • 接口只有常量
成员方法
  • 抽象类可以有非抽象方法,也可以有抽象方法
  • 接口只能有抽象方法
构造方法
  • 抽象类有构造方法
  • 接口没有构造方法
构造方法

类与抽象类和接口关系
- 类与抽象类的关系是继承 extends
- 类与接口的关系是实现 implements

接口的思想特点
  • 接口是对外暴露的规则;
  • 接口是程序的功能扩展;
  • 接口的出现降低耦合性;(实现了模块化开发,定义好规则,每个人实现自己的模块,大大提高了开发效率)
  • 接口可以用来多实现;
  • 多个无关的类可以实现同一个接口;
  • 一个类可以实现多个相互直接没有关系的接口;
  • 与继承关系类似,接口与实现类之间存在多态性。

4.对象的多态

在Java程序语言中,对象变量是多态的,一个Employee变量既可以引用一Employee类对象,也可以引用一个Employee类的任何一个子类的对象,如Manager类。父类对象可以用子类替换。

Manager boss =  new Manager(...);
Employee[] staff = new Employee[3];
staff[0] = boss;

在例子中staff[0]和boss引用了同一个对象。但编译器将staff[0]看作是Employee对象,所以

boss.setBonus(5000);//it's ok
staff[0].setBonus(5000);//Error

staff[0]的声明的类型是Employee,而setBonus是Manager类的方法。多态至始至终都是子类再发生变化。

多态的前提:
  • 要有继承或者实现关系。
  • 要有方法的重写。
  • 要有父类引用指向子类对象。

三、继承的实例 —— Object

Object类是Java中所有类的超类(父类)。在Java中每个类都是由Object类扩展来的。我们可以看下Object类被抽象出来的几个方法:
这里写图片描述

1.equal()方法

Object类中的equals方法用于检测两个对象是否相等,看下equals方法源码是如何实现的:
这里写图片描述
通过源码可以看出,equals方法是在比较两个对象是不是同一个对象。而且equals方法不是Object类的私有方法,在定义新类时,可以重写equals()方法。以下,是JDK规定重写equals()方法应该遵守的约定:

  • 自反性:x.equals(x)必须返回true。

  • 对称性:x.equals(y)与y.equals(x)的返回值必须相等。

  • 传递性:x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)必须为true。
  • 一致性:如果对象x和y在equals()中使用的信息都没有改变,那么x.equals(y)值始终不变。
  • 非null:x不是null,y为null,则x.equals(y)必须为false。

它在Object类中的实现是判断两个对象是否指向同一块内存区域。这中测试用处不大,因为即使内容相同的对象,内存区域也是不同的。如果想测试对象是否相等,就需要覆盖此方法,进行更有意义的比较。例如

class Employee{
    ... 
    public boolean equals(Object otherObj){
        //快速测试是否是同一个对象
        if(this == otherObj) return true;
        //如果显式参数为null,必须返回false
        if(otherObj == null) reutrn false;
        //如果类不匹配,就不可能相等
        if(getClass() != otherObj.getClass()) return false;

        //现在已经知道otherObj是个非空的Employee对象
        Employee other = (Employee)otherObj;

        //测试所有的字段是否相等
        return name.equals(other.name)
            && salary == other.salary
            && hireDay.equals(other.hireDay);
    }
}
2.hashCode()方法

这里写图片描述
可以看出,hashCode()是一个native方法,而且返回值类型是整形;实际上,该native方法将对象在内存中的地址作为哈希码返回,可以保证不同对象的返回值不同。

与equals()方法类似,hashCode()方法可以被重写。JDK中对hashCode()方法的作用,以及实现时的注意事项做了说明:

  • hashCode()在哈希表中起作用,如java.util.HashMap。

  • 如果对象在equals()中使用的信息都没有改变,那么hashCode()值始终不变。

  • 如果两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等。

  • 如果两个对象使用equals()方法判断为不相等,则不要求hashCode()也必须不相等;但是开发人员应该认识到,不相等的对象产生不相同的hashCode可以提高哈希表的性能。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值