• 抽象类 or 接口


    抽象类 or 接口



    1.回顾 前文 要点

    学到这里 面向对象 的 三 大特征 我们 已经 学习过 了。

    1.封装

    2.继承

    3.多态

    下面我们 就先来复习一下。

    1.啥是 封装 ?

    封装 就是 将 对象 的 属性 和 实现的 细节 通过 关键字 private 给 隐藏 起来 ,对外 提供 特有的 get 和 set 方法来进行 访问,

    2.啥是 继承 ?

    继承 就是 通过 一个 已有 的 类的 基础上 派生 处 新类(如: 动物 类 就能 派生出 猫 类 和 狗 类 等等) 子类 继承 父类 的 特征 和 行为 ,使得 子类 对象 拥有 父类 的 实例 域 和 方法,

    (简单来说 就是 对 共性 的 抽取, 使 子类 具有 父类 相同的 行为 ) , 使用 关键字 extends 来实现

    如: A extends B A 就 是 子类 , B 就是 父类 属于 A is B 的 关系。

    继承的 意义 : 提高 了 代码的 复用 性 。

    3.啥是 多态 ?

    多态是 一种 思想, 表示的 是 同一个行为具有 多个不同的表现形式 或 形态能力 。

    如:黑白打印机和彩色打印机相同的打印行为却有着不同的打印效果,

    但 光有 这句话 是 不能行 的 我们 还需要理解 啥是 向上转型,动态绑定 (重写)。

    1.向上转型: 父类引用 引用 了 子类 对象。

    2.动态绑定:通过 父类引用 调用 子类和 父类 同名 的 覆盖(重写) 方法。

    注意: 在发生 动态 绑定时 一定 会 涉及到 重写 , 这个 也需要我们 重点理解.

    重写特征
    1.方法 名 相同
    2. 参数类型 和 个数 相同,
    3.返回值相同 (特例 : 返回值 构成 父子类 )。


    重写 注意 事项:

    1.static 修饰的 方法 不能 重写
    2.final 修饰的 方法 不能 重写
    3. private 修饰的 方法 不能重写
    4.子类 重写 的 方法 访问 修饰 限定 需要 大于 等于 父类 访问 修饰 限定 (子类方法的访问权限要大于等于父类方法的访问权限)


    动态 除了 上面 这些 比较 重要的 我们 还学习 了

    静态 绑定 :通过方法的重载实现的。编译的时候,会根据你调用方法时,所给参数的类型和个数,来决定调用哪个同名方法

    向下 转型 : 子类 引用父类 对象 , 因为 不太 安全 所以 不推荐 使用。

    注意事项:

    1.子类 继承 父类 , 子类 需要 在 构造方法中 加入 super(…) 来显示 调用 父类构造方法 , 帮助 父类 完成 构造 。

    2.super 和 this 的 区别 : super 是 针对父类的引用, 而 this 针对 当前 对象的 引用 。

    3.重写 和 重载 的 区别 :

    重载重写
    相同点:1.方法名相同1.方法名相同
    不同点:2.返回值 可以不同2.返回值必须相同
    特例:返回值构成父子类
    不同点:3.参数列表不同
    数据类型 ,顺序个数都不一样
    3.参数列表相同
    数据类型,顺序个数都相同
    不同点:重载是没有权限要求的5.重写是有权限要求的
    子类的 权限要大于等于父类
    另外:父类被private修饰不能够重写


    4.四种方法 权限 :

    1.private被 private 修饰的 字段 或 方法,只能在 同一个类中使用,
    如果 这个 类 被 继承, 被 private 修饰的 方法 或 字段 , 是 被 继承下来的 ,但无法访
    2.default (包访问 权限)只要在 同一个包底下 都能够使用。
    3.protected在 同一个包底下 随便使用, 但 其他包中需要是子类 才能 够 使用
    4.public不管是同一个包 还是 不同 的 包 都能 够使用 。
    注意上面 说的使用 是 被 这几种 修饰限定符 给 修饰的 字段 或 方法。

    到此我们 就 回顾 完 了 , 下面 我们 就 来 使用 多态 来 引出 我们 接下来 学习 的 抽象类 。

    抽象类

    先来看 这段 代码 :

    class Shape{
        public void drow(){
            System.out.println("Shape :: drow()");
        }
    }
    // Rect : 矩形
    class Rect extends Shape{
        @Override
        public void drow() {
            System.out.println("矩形");
        }
    }
    // Flower : 花
    class Flower extends Shape{
        @Override
        public void drow() {
            System.out.println("❀ 花");
        }
    }
    public class Test {
    
        public static void func(Shape shape){
            shape.drow();
        }
        public static void main(String[] args) {
            Rect rect = new Rect();
            func(rect);
            Flower flower = new Flower();
            func(flower);
        }
    }
    
    • 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

    在这里插入图片描述

    这里 我们 就 实现了 我们的 多态 , 通过 shape 这 一个 引用 , 表现出 了不同 中 状态 ,如 : 矩形 , 花 。

    这里 你 有没有 想过 只要 父类 引用 了 子类 对象 , 调用 drow 方法 ,都会 发生 动态 绑定 ,原本 自己 的 drow 方法 实现的 功能 就没有 用 了 ,我们是否 可以 将 这个 方法 实现 省略 掉 呢?

    这里就 可以 将 drow() 这个 方法 设置 为 抽象 方法 ,此时 包含抽象 方法的 类 我们 就称为 抽象 类 。

    抽象类语法规则

    在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用
    给出具体的实现体。

    abstract class Shape{
     abstract public void drow();
    }
    
    • 1
    • 2
    • 3


    此时 我们 被 abstract 修饰 的 Shape就 是 抽象类,

    而 被 abstract 修饰 的 方法就 是 抽象 方法, 这个 方法就可以事项具体 类容。

    下面我们 来 对比一下 抽象 类 普通类 的 区别:

    1.抽象类 不能实例化

    在这里插入图片描述


    2.抽象类 与 普通 类 一样 可以 定义 普通 方法 和 成员变量。

    在这里插入图片描述

    这我们 虽然 不能 通过 new 这个 类来 创建 对象,但 抽象类 ,发明出来 就是 用来 继承的 , 我们 可以 通过 继承 , 来 调用 这些 普通 方法 或 成员变量。

    如:

    在这里插入图片描述

    当我们尝试 继承 后 会发现 ,报红线 了 ,不要着急,这里 是因为 我们没有 重写 抽象 方法 。

    注意: 当 继承 抽象类 的同时 我们 需要 重写 所有的抽象 方法。

    也挺好想 :我们抽象 方法本来 什么 就没实现 ,如果 你不重写 ,别人 调用 这个 方法 有什么 用 呢? 还不如 不写。

    在这里插入图片描述


    重写 完 就没有 报错了 ,下面 继续 。

    通过 子类 访问 ,父类 (父类 为 抽象 类 )的 普通 方法 :

    abstract class Shape{
        abstract public void drow();
        public int a;
        public String b;
        public void func(){
            System.out.println("测试 抽象类 是否能 定义普通方法");
        }
    }
    class A extends Shape{
        @Override
        public void drow() {
            System.out.println("重写 的 抽象 方法");
        }
    }
    public class Test {
    
        public static void main(String[] args) {
            A a = new  A();
            a.func();
            a.drow();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22


    在这里插入图片描述

    同样 的 我们 的 抽象类 ,也 是 可以 发生 向上绑定 ,和 多态的 , 这里 就 来 看一下。

    1.向上 转型:

    在这里插入图片描述

    2.多态:

    abstract class Shape{
        abstract public void drow();
        public int a;
        public String b;
        public void func(){
            System.out.println("测试 抽象类 是否能 定义普通方法");
        }
    }
    class A extends Shape{
        @Override
        public void drow() {
            System.out.println(" A  中 重写 的 抽象 方法");
        }
    }
    class B extends Shape{
        @Override
        public void drow() {
            System.out.println(" B 中 重写 的 抽象 方法");
        }
    }
    public class Test {
        
        public static void func(Shape shape){
            shape.drow();
        }
    
        public static void main(String[] args) {
            A a = new A();
            B b = new B();
            func(a);
            func(b);
        }
    }
    
    • 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

    在这里插入图片描述


    另外 : 我们 一个 抽象类 继承 一个 抽象 类 可以 不重写 抽象 方法 。


    如: 抽象类 B 继承 了 抽象 类 A 此时 抽象 类 B 是 不需要 重写 抽象 A 的 抽象方法 (对比 一个 普通类 C 继承 抽象类 A ,就必须 重写抽象方法, 要不然 报错)
    在这里插入图片描述


    俗话 说的 父债子还 , 如果此时 一个 普通的 类 继承 了 这个抽象 类 B ,此时 这个子类 不仅 需要 重写 抽象类 A 的 抽象 方法 , 还需要 重写抽象 类B的 抽象方法。

    1.只重写 了 抽象类 B 的 抽象 方法

    在这里插入图片描述

    2.重写了 抽象类 A 和 抽象 类 B 的 抽象 方法 。

    在这里插入图片描述

    如果 我们 在 拿一个 抽象类 继承 呢 ?

    这样 就会 形成 套娃 ,这个 类 也 不需要 重写 父类 的 抽象 方法 ,知道 有 一个 普通类 继承 了 ,那么 就得 全部 重写 ,
    在这里插入图片描述

    在 来 一个 注意事项:

    抽象 类是不能 被 final修饰的

    之前我们 学过final,说过 其中 的 一种用法 , 被 final 修饰 的 类 是 不能 被 继承 的 ,我们的 抽象类 本 身就是 用来 被继承的 ,

    final 就好比 与 火 , 而 抽象类 就好比水 , 两者 本身 就不 合 , 强行 放在 一起 肯定 会出问题。

    在这里插入图片描述

    既然 抽象 类 不能 被 final 修饰 ,那么 我们的抽象 方法 同理 也是 不能 被 final修饰的, 之前 还 提过 被final修饰的 方法 是不能 重写的 ,这样与 抽象方法 必须 重写 ,又有 冲突 。

    在这里插入图片描述

    看完上面这些 内容 你 能 总结 出什么 ?

    下面 就 直接 抛出 总结。


    总结 :

    1、包含抽象方法的类,叫做抽象类。

    2、什么是抽象类,一个没有具体实现方法,被abstract修饰

    3、抽象类是不可以被实例化的。不能 new

    4、因为不能被实例化,所以,这个抽象类,其实只能被继承

    5、抽象类当中,也可包含和普通类一样的成员和方法。

    6、一个普通类,继承了一个抽象类,那么这个普通类当中,需要重写这个抽象类的所有的抽象方法。

    7.抽象类的最大的作用,就是为了被继承

    8、一个抽象类A,如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法。

    9、结合第8点,当A类再次被一个普通类继承后,那么A和B这两个抽象类当中的抽象方法,必须重写

    10、抽象类不能被final修饰,抽象方法也不可以被 final 修饰

    同理 抽象 方法 不能 被 privatestatic 修饰 (导致 这个方法无法被重写)。

    抽象类的 作用

    抽象类 最大 的意义 就是为了 继承 ,

    抽象类本身不能被实例化, 要想使用, 只能创建该抽象的子类. 然后让子类重写抽象类中的抽象方法.

    有些小伙伴 可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法
    呢?

    其实 使用 抽象类 相当于多了一重编译器的校验(如果你没有重写抽象方法,编译器会报错,提醒你)

    我们 充分利用编译器的校验, 在实际开发中是非常有意义的。


    抽象类 完了,接下来 学习 我们的 接口 。

    接口


    什么是 接口 ?

    答: 接口是 对 公共的 行为规范 标准 (再实现 接口 时, 只要 符合 规范 标准 , 就可以 通用)。

    再java 中 ,接口 可以看成 是 :多个类 的公共规范,是 一种 引用 数据类型 。

    看完 上面 这些 概述 是不是 很 懵逼 下面 就通过 例子 来 解释 一下。

    笔记本 上的 USB 接口 , 电源插座等 。

    在这里插入图片描述

    在这里插入图片描述

    电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备

    电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备

    当满足 满足 这两个 接口 的规范 协议 ,就 能够 插入 其中使用 , 如果 你 强行 插入当我没说,

    如: 有线鼠标 键盘 , 如果 他们的 线 的 插头 与 电脑 规定 的 USB接口 相同 此时 就能够 使用 ,不同那么 就不能使用。这里 就是 接口 规定了标准 范围,而我们的 鼠标 和 键盘 就 满足 这种 规范 ,就可以使用 这个 接口 。

    知道 了 概念 下面就来 了解 一下 语法 规则 。

    接口的 语法 规则



    使用 关键字 interface 修饰 的类 就 是 接口

    创建 完 了 接口,来 了解 一下我们的 接口 。



    1.接口 当中 ,不能创建普通 方法

    在这里插入图片描述


    如果这 非要 创建 普通方法 需要 再 方法 前 加上 关键字 default ,表示 该方法 是此 接口 的 默认 方法

    在这里插入图片描述


    注意 : 在 JDK 1.8 开始 才 允许 接口 可以 实现 方法 , 但 必须是 被关键字 default 修饰的 方法 。

    另外 : 在 接口 中 是 可以 存在 静态 方法 的 但 静态 方法 不能 被重写

    在这里插入图片描述

    除了 默认方法 , 静态 方法 , 接口 还 包括 抽象方法 ,

    在这里插入图片描述

    接口 与 抽象类 相比 之下, 接口 只能 包括 抽象 方法 或 接口 默认方法 , 字段 只能 包括 静态 常量, 而 抽象类 除了 抽象方法 , 还可以包含非抽象方法, 和字段. , 所以 说 接口 相比 抽象类 更进 一步 。

    观察 上面 几张配图 ,有没有 发现 , 我们 创建 的 方法 中 的 关键字 public 都是 灰色 的 ,这里 意味 这 在 接口中 所有的 方法 ,都是默认 被 public 修饰 的。


    2.在 接口当中所有 方法 默认 都是 被 public 修饰的 ,抽象方法 默认 是 被 public abstract 修饰的

    在这里插入图片描述


    尝试 使用 其他 访问 修饰 限定符 :

    在这里插入图片描述

    可以 看到 只有 public 修饰 , 或者 不写 (此时 会默认 为 是 public 修饰 )才能 够不报错 。


    3.接口 同样不能实例 化对象

    刚刚 说过 ,接口 是 抽象 类 的更进一步 , 抽象类 不能 实例化对象, 接口 也应该 不能 实例化对象。

    下面演示 :

    在这里插入图片描述


    下面 就来 使用 一下我们的接口。

    4.类 与 接口 之间 使用关键字 implements 来实现


    模拟 场景: 皮卡丘使用 技能 A

    1.通过 implements 实现 接口 ,同样需要 将 接口 中 所有的 抽象类 方法, 全部重写 ,(注意:默认方法,或 static 修饰的 方法 就 不需要 重写, 但 默认 方法 是 可以按照 自己的 意愿选着 重写 的 ,而static 修饰 的方法 无法被 重写)。

    在这里插入图片描述


    重写 完 drow 方法。

    在这里插入图片描述

    // 技能 A
    interface A {
        void drow();
    }
    
    // 技能 B
    interface B {
        void drow();
    }
    
    // 皮卡丘
    class Pikachu implements A {
        @Override
        public void drow() {
            System.out.println("皮卡丘使用十万伏特");
        }
    }
    
    // 喷火龙
    class Charizard {
    
    }
    
    public class Test {
        public static void main(String[] args) {
            Pikachu pikachu = new Pikachu();
            pikachu.drow();
        }
    }
    
    • 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


    在这里插入图片描述

    此时 就完成 了 我们 的 场景 模拟 。

    同样我们 的接口 是可以 完成 我们 的向上 转型 的 , 既然 有 了 向上 转型 (父类 引用 引用 了 子类 对象) ,那么 子类 发生 重写 ,调用 重写 的方法 ,发生 动态绑定 ,最后 也 能够 完成 我们 的 多态 。

    1.向上转型 和 动态绑定:

    在这里插入图片描述

    2.多态:

    // 技能 A
    interface A {
        void drow();
    }
    
    // 技能 B
    interface B {
        void Skill();
    }
    
    // 皮卡丘
    class Pikachu implements A {
        @Override
        public void drow() {
            System.out.println("皮卡丘使用十万伏特");
        }
    }
    
    // 喷火龙
    class Charizard implements A,B {
        @Override
        public void drow() {
            System.out.println("喷火龙使用了 撞击 ");
        }
    
        @Override
        public void Skill() {
            System.out.println("喷火龙 使用了 吐息");
        }
    }
    
    public class Test {
        public static void func(A a){
            a.drow();
        }
        public static void main(String[] args) {
            Pikachu pikachu  = new Pikachu();
            Charizard charizard = new Charizard();
            func(pikachu);
            func(charizard);
        }
    }
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述


    5.一个类 是 可以 拥有 多个 接口 的


    另外 : 在 上面 代码 中我们 的喷火龙 是 实现了 两个接口 的 ,这里 我们 通过 implements 实现 接口 多个 接口时 , implements 只需要 一个 , 接口之间使用 , 逗号隔开 即可,

    在这里插入图片描述

    上面 一直 将的 是 方法, 在 接口 中 同样 是 包含字段 的 只不过这个 字段 必须是 静态常量 static final 修饰的 变量 。


    6.接口 中 的 字段 只能 是 被 static final修饰 。

    在这里插入图片描述


    7. 接口 之间 是 可以 继承的


    相比 于 类 继承 类 , 类 继承 抽象类, 类 虽然 继承不了我们的 接口 ,但 是 可以实现 多个 接口 , 但 我们的 接口 之间 是 可以 继承。

    在这里插入图片描述


    此时 我们的 接口 B 就 继承 了 接口 A , 当一个 普通类, 实现 了 这个 B 接口 就需要 重写 ,A 和 B 的 抽象方法 ,(这里 B继承 A 此时 就先当与 B接口 扩展 了 A 接口 的功能)。

    interface A{
        int a = 10;
        void drow();
    }
    
    interface C {
        int c = 30;
        void func();
    }
    interface B extends A{
        int b = 20;
        void swap();
    
    }
    class Test2 implements B{
        @Override
        public void swap() {
            System.out.println("重写 B 的 方法");
        }
    
        @Override
        public void drow() {
            System.out.println("重写 A 的 方法");
        }
    }
    
    • 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


    下面 我们 就来 整点 好玩的 。

    以前我们 说过 一个类 只能 继承 一个 类 或 抽象 方法 。

    此时 让我们的 接口去 继承 多个 接口 试试

    在这里插入图片描述


    总结:

    接口与接口之间,可以使用 extends来 操作它们的关系,此时,extends 在这里意为 拓展。(一个接口A 通过 extends 来拓展 另一个接口B的功能,此时当一个类 通过 implements 实现 接口A,此时重写的方法,不仅仅是 A 的抽象方法,还有从B接口,拓展来的功能【方法】)


    最后 我们 将 上面 所说的来一次 大总结 :

    总结:
      
    1.使用interface来定义一个接口,例:interface IA{},这就是一个接口
      

    2.接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字 default 来修饰 这个方法。
      
    3.接口当中,可以有static的方法。


    4.接口 中 所有方法都是public的。

    5.抽象方法,默认是 public abstract 的。

    6.接口是不可以被实例化的(不可以被new的)。

    7.类和接口之间的关系是通过 implements(工具)实现的。

    8.当一个类 实现了 一个接口,就必须要重写接口当中的抽象方法。

    9.在调用的 接口的 时候 可以创建一个接口的引用 (如 实现接口的 类 ), 对应到一个子类的实例

    10.接口当中的字段/属性/成员变量,默认是public static final 修饰的。


    11. 当一个类实现接口之后,重写抽象方法的时候,重写方法的前面必须加上public,因为接口中方法默认都是public的,而且重写方法的访问权限,必须要大等于父类当中方法的访问权限

    补充 : 之前没演示这里 演示 一下:
    在这里插入图片描述


    12.一个类可以通过关键字extends继承一个抽象类或者普通类。但是只能继承一个类。同时也可以通过implements实现多个接口。接口之间使用逗号隔开。
      

    13.接口与接口之间,可以使用 extends来 操作它们的关系,此时,extends 在这里意为 拓展,而不是继承,(一个接口 通过 extends 来拓展 另一个接口的功能)

    本文 完 : 下文 预告 三种 常用 接口 。

  • 相关阅读:
    JavaScript(五):优雅的async/await
    基于springboot实现民宿管理平台项目【项目源码+论文说明】计算机毕业设计
    猿创征文|那些少见但好用的软件开发工具
    在 TypeScript 中declare module 关键字用法
    最新Python大数据之Excel进阶
    .NET周报【10月第3期 2022-10-25】
    11.16~11.19绘制图表,导入EXCEL中数据,进行拟合
    java基于RestTemplate的微服务发起http请求
    Windows API hooking.简单的C++例子
    14.linux线程
  • 原文地址:https://blog.csdn.net/mu_tong_/article/details/126252883