Java继承及接口

继承

  • 什么时候继承?
  • 当类中存在所属关系时,就定义继承。狗和狼都是犬科,所以他们都可以继承犬科
  • 所属关系:is a 关系 谁是谁中的一种

优点

  • 提高代码复用性
  • 让类与类之间产生了联系,为多态提供了前提

缺点

  • 打破封装性
  • 解决方法看下面的final关键字

注意

  • Java中支持单继承,不直接支持多继承,但对C++中多继承进行了改良
  • 单继承:一个子类只能有一个父类
  • 多继承:一个子类可以有多个父类(Java不直接支持,但是改良了)
  • 因为如果多个父类中有相同成员,会产生调用的不确定性(Java中通过”多实现”的方式)
  • Java支持多重继承

继承体系

  • C继承B,B继承A,就会出现继承体系
  • 如何使用
    • 创建该子类中的顶层类,了解该体系的基本使用功能
    • 创建体系中的最子类对象,完成功能的使用

子父类中成员特点

  • 子父类中,成员的特点体现
    • 成员变量
    • 成员函数
    • 构造函数

成员变量

  • 本类中成员和局部变量同名用this区分
  • 子父类中的成员变量用super区分
  • this super 用法很相似
    • this 代表一个本类对象的引用
    • super 代表一个父类空间
  • 子类不能直接访问父类中私有的内容
成员变量内存图解
  • 以如下代码为例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Father{
    int num = 4;
    }
    public class extends Son{
    int num = 5;
    void show(){
    System.out.println(this.num+super.num);
    }
    }
    public class Demo{
    public static void main(Sting[] args){
    Son s = new Son();
    s.show();
    }
    }

  • 运行Demo类,main方法进栈,执行下一句话,创建一个子类对象,因为它是继承的父类,所以方法区需要先加载父类函数及构造方法,再加载子类构造方法。接下来在堆中开辟空间,给予地址,num值在父类子类中都有体现,所以存储时num值系统会通过关键字标注好。show方法入栈,this关键字指向son的num,super关键字指向father的num。

成员函数特点(覆盖)

  • 当子父类中出现成员函数一模一样的情况,会运行子类的函数,这种现象就是覆盖操作,这是函数在子父类中的特性。
  • 函数两个特性

    • 重载:同一个类中 overload
    • 覆盖:子类中,也成为重写,复写 override
  • 注意事项:

    • 子类方法覆盖父类方法时,子类权限必须大于等于父类权限才可以引发覆盖
    • 静态只能覆盖静态,或被静态覆盖
  • 什么时候使用覆盖操作?

    • 当对一个类进行子类的扩展时,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时,就使用覆盖操作完成
成员函数特点应用
  • 观察下面的代码,Phone类拥有两个方法,现在需要在他的基础上更改增加show方法的功能,就可以采用成员函数覆盖的方法
  • 为什么不更改原代码的函数?为了安全起见,如果出错,那样很难复原。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Phone{
    public void call(){}
    public void show(){
    System.out.println("number");
    }
    }
    public class newPhone extends Phone{
    public void call(){
    super.call();
    }
    public void show(){
    super.show();
    System.out.println("name");
    System.out.println("picture");
    }
    }
    public class Demo{
    public static void main(){
    newPhone p = new newPhone();
    p.call();
    p.show();
    }
    }

子父类中的构造函数

子类的实例化过程

  • 看下图代码,运行主函数后会是怎么样的结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Father {
    Father(){
    System.out.print("Father");
    }
    }
    public class Son extends Father {
    public Son(){
    System.out.print("Son");
    }
    }
    public class test {
    public static void main(String[] args){
    new Student();
    }
    }
  • 运行结果FatherSon,有Son我们理解,调用的是Son类,可是为什么有Father啊

特点

  • 在子类构造对象时,发现访问子类构造参数时,父类也运行了,Why?
  • 在子类的构造函数第一行有一个默认的隐式语句。super();
  • 子类中所有的构造函数默认都会访问父类中的空参构造函数

细节

  • 为什么子类实例化的时候,要访问父类中的构造函数那?
  • 那是因为子类继承了父类,获取到了父类中的内容(属性),所以在使用父类内容之前要先看父类是如何对自己的内容进行初始化的。
    所以子类在构造对象时,就必须访问父类中的构造函数。为了完成这个必须的动作,就在子类的构造函数中加入了super语句。
    如果父类中没有定义空参数构造函数,那么子类的构造函数必须用super明确要调用父类中的那个构造函数。

  • super语句必须要定义子类构造函数第一行。因为父类的初始化动作要先完成

  • 如下面代码所示,子类构造函数中如果使用this调用了本类构造函数时,那么super没有了,因为super和this都只能定义在第一行,不过可以保证的是,子类中肯定会有其他构造函数访问父类的构造函数和数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Father {
    Father(){
    System.out.print("F");
    }
    }
    public class Son extends Father {
    Public Son(){
    super();
    System.out.print("Son1");
    }
    public Son(int x){
    this();
    //super();
    System.out.print("Son2");
    }
    }
  • 所有的类在创建时其实都是继承的object类,如下图的Demo类,注释掉的内容都是自动生成的隐式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Demo //extends object()
    {
    /*
    Demo(){
    super();
    return;
    }
    */
    }

图解

  • 以下面代码为示,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Father {
    Father(){
    //super();
    show();
    //return;
    }
    public void show(){
    System.out.println("Father show");
    }
    }
    public class Son extends Father{
    int num = 8;
    public Son(){
    super();
    //->通过super初始化父类内容时,子类的成员变量并未显示初始化。
    //等super()父类初始化结束后,才对子类成员变量初始化
    return;
    }
    public void show() {
    System.out.println("Son show "+num);
    }
    }

    public class test {
    public static void main(String[] args){
    Son s = new Son();
    s.show();
    }
    }
    /*
    运行结果:Son show 0
    Son show 8
    */

过程

  • 一个对象的实例化过程
    • 1、JVM会读取指定路径下的Person.class文件,并加载进内存,并会加载Person的父类(如果有直接父类的情况下)
    • 2、在堆内存中开辟空间,分配地址
    • 3、并在对象空间中,对对象中的属性进行默认初始化
    • 4、调用相应的构造函数进行初始化
    • 5、在构造函数中,第一行会先调用父类中构造函数进行初始化
    • 6、父类在初始化完毕后,再对子类的属性进行显示初始化
    • 7、再进行子类函数的构造初始化
    • 8、初始化完毕后,将地址值赋给引用变量

final关键字

  • 1、final是一个修饰符,可以修饰类,方法,变量
  • 2、final修饰的类不可以被继承,该类为最终类
  • 3、final修饰的方法不可以被覆盖
  • 4、final修饰的变量是一个常量,只能赋值一次

  • 为什么使用final修饰变量?

  • 其实在程序中如果一个数据是固定的,那么直接使用这个数据就可以了,但是这样阅读性差,所以应该给这个数据起个名称。而且这个变量名称的值不能变化,所以加上final固定

final应用

  • 如下面的圆周率想定义后不被人修改,即可加上final修饰
  • 注:被修饰后变量即是常量,表示方法为 “所有单词都大写,如果多个单词,中间用_连接”
  • 注:final修饰的变量必须手动进行初始化,且前面一般加上static,方便调用
    1
    2
    3
    4
    5
    6
    class Circle{
    static final double PI = 3.14;
    void method(){
    System.out.println(PI);
    }
    }

抽象类

  • 抽象:笼统,模糊,不具体

特点

  • 1、方法只有声明没有实现时,该方法就是抽象方法,需要被abstract修饰。抽象方法必须定义在抽象类中,该类也必须被abstract修饰
  • 2、抽象类不可以被实例化,不可以被new 创建对象。因为调用抽象方法无意义
  • 3、抽象类必须有其子类覆盖了所有的抽象方法后,该子类才可以实例化,否则这个子类还是抽象类

细节

  • 1、抽象类中有构造函数吗?
    • 有,用于给子类对象初始化
  • 2、抽象类可以不定义抽象方法吗?
    • 可以,但是很少见,目的是不让该类创建对象。AWT的适配器对象就是这种类。通常这个类中的方法有方法体,但是没有内容
  • 3、抽象关键字不可以和哪些关键字共存?
    • private 抽象方法需要被覆盖,加上私有不能覆盖
    • static 加上静态后可以用类调用方法,但是抽象方法运行无意义
    • final final修饰后不能被继承,抽象类或方法就没有用了
  • 4、抽象类与一般类的异同点?
    • 抽象类和一般类都是用来描述事物的,都在内存定义了成员
    • 一般类有足够信息描述事物,抽象类描述事物信息一般都不足
    • 一般类中不能定义抽象方法,抽象类中可定义抽象方法或非抽象方法
    • 一般类能被实例化,抽象类不可以被实例化
  • 5、抽象类一定是父类吗?
    • 是的,因为需要子类覆盖其方法后,才可以对子类实例化

接口

定义

  • 当一个抽象类中的方法都是抽象类的时候,可以将该抽象类用另一种形式定义和表示,就是接口 interface。
  • 定义接口使用的关键字不是class 是interface
  • 接口当中常见的成员:(这些成员都有固定的修饰符,都是公共权限)
    • 全局常量:public static final
    • 抽象方法:public static

实现

  • 类与类之间是继承方式,类与接口之间是实现方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public interface Demo{
    public static final int NUM = 1;
    public static void show1();
    public static void show2();
    }
    public class DemoImpl implements Demo{
    public void show1()
    {}
    public void show2()
    {}
    }
    public class Test{
    public static void main(String[] args){
    DemoImpl d = new DemoImpl();
    //下面三行语句都正确
    System.out.println(d.NUM);
    System.out.println(DemoImpl.NUM);
    System.out.println(Demo.NUM);
    }
    }
  • 接口不可以实例化,只能由实现了接口的子类并覆盖了接口中所有的抽象方法后,该子类才可以实例化,否则,这个就是一个抽象类

多实现

  • Java中不直接支持多继承,因为会出现调用的不确定性,所以Java将多继承机制进行改良,在Java中变成了多实现
  • 一个类支持多个接口
  • 一个类在继承另一个类的同时,还可以实现多个接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public interface A{
    public abstract void show1();
    }
    public interface B{
    public abstract void show2();
    }
    public class Q{
    public void method()
    {}
    }
    //Test类继承Q类意味着他们属于一个体系
    //实现A,B接口是为了扩展功能
    class Test extends Q implements A,B{
    public void show1()
    {}
    public void show2()
    {}
    }
  • 接口的出现避免了单继承的局限性,一个类

注意

  • 接口与接口之间是继承关系,而且接口之间可以多继承
  • Java不能多继承的原因是因为方法体导致的运行不确定性,不过接口无方法体,所以接口可以多继承
  • 接口类型的引用,都是指向自己类型子类的对象

特点

  • 接口是对外暴露的规则
  • 接口是程序的功能扩展
  • 接口的出现降低耦合性
  • 接口可以用来多实现
  • 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口
  • 接口与接口之间可以有继承关系

接口抽象类区别

  • 相同点:都是向上不断抽取而来
  • 不同点:
  • 抽象类:
    • 抽象类需要被继承,且只能单继承
    • 抽象类可以定义抽象方法和非抽象方法,子类继承后可以直接使用抽象方法
    • 抽象类继承是 is a 关系,是在定义该体系的基本共性内容
  • 接口:
    • 接口需要被实现,可以多实现
    • 接口只能定义抽象方法,必须由子类去实现
    • 借口实现是 like a 关系,在定义体系的额外功能