Java中的抽象类和接口


前言

在之前的文章中已经解释了多态,同样的方法,根据传入对象的不同,表现出不同的行为模式称之为多态性。说到底,多态就是依赖继承和重写实现的,本篇文章将进一步,浅谈一下抽象类和接口,为什么要提一嘴多态,是因为它的本质就在于多态!


一、抽象类

1、什么是抽象类&&为什么需要抽象类?

先谈为什么:上一篇文章提到了,多态方式的本质需要继承+方法重写共同组成多态。但有一个漏洞:子类可以选择性的覆写方法,父类无法对子类覆写哪些方法做出约束,若需要强制子类覆写一些方法,就可以使用抽象类与抽象方法。
再说说什么是抽象方法:在java中使用abstract定义抽象方法和抽象类抽象方法所在的类一定是抽象类抽象方法没有方法体(但是注意,没有方法体的不一定是抽象方法,native本地方法也没有方法体)。
在这里插入图片描述
像这种没有具体方法实现的都应该定义为抽象方法。
在这里插入图片描述

需要注意的是,抽象类无法直接实例化子类,可以理解为抽象类被定义出来就是做父类的,而子类继承抽象类必须覆写抽象类中的所有抽象方法
抽象类是普通类的超集,即 普通类有的抽象类全都有,普通类中的普通方法、构造方法抽象类中全部可以存在(但不必须)。换句话说,抽象类就是比普通类多了一些抽象方法。

2、使用原则

1.被abstract定义的类称之为抽象类,抽象类无法直接实例化对象,可以通过向上转型进行实例化。

比如,定义了一个抽象的Sharp类,子类Rect继承了这个抽象类,在new(实例化对象)的时候,可以创建Rect对象向上转型为Sharp,但不能实例化一个抽象类。具体可以看下图:
在这里插入图片描述
这样就可以了👇
在这里插入图片描述

2.抽象类中可以没有抽象方法,但只要有一个抽象方法,那这个类必须被定义为抽象类,并且继承它的子类中必须重写所有的抽象方法

Rect作为普通类,继承了抽象类,必须覆写其中的抽象方法:
在这里插入图片描述
这就是上文提到的,可以用抽象类来限制子类,强制要求子类覆写某些方法,将这些方法定义为抽象方法。

但是,当子类也是抽象类时,就可以不覆写任何的抽象方法。
在这里插入图片描述
3.abstract和final以及private不能同时出现

abstract 能否和final关键字一块写出来一定编译出错!原因在于:

  1. 被final修饰的方法不能重写,抽象方法子类必须重写—>矛盾
    2)被final修饰的类不能继承,抽象类生来就是要被继承做父类的—>矛盾
    抽象方法 abstract 能否和私有权限private一起出现也是坚决不行的!原因在于:
  2. private abstract一定编译出错!私有方法无法覆写,抽象方法必须覆写—>矛盾!

4.抽象类是普通类的超集,抽象类中仍然可以存在普通方法和构造方法,且子类实例化时仍然满足继承的原则,先调用父类的构造方法,在调用子类的构造方法!
在这里插入图片描述
抽象类和抽象方法存在的意义就在于强制要求子类覆写某些方法,但是抽象类仍然满足 is a原则,子类必须是满足继承关系才能继承抽象类,且一个子类只能继承一个抽象类!!!(单继承局限
抽象类的使用和设计是比较复杂的,一般都是作为一个模板存在的(java中的Servlet就是抽象类的使用)

二、接口

接口,顾名思义,要么表示一种规范(Type C接口 USB 接口),要么表示一种能力(脑机接口)
接口可以理解为 更加纯粹的抽象类(但不是抽象类),因为接口中只有全局常量和抽象方法(JDK8之前)

1、接口的定义与使用方法

1.在java中,使用interface关键字定义接口
在这里插入图片描述
在java中 子类使用implements表示实现一个接口,同样的,必须覆写接口中所有的抽象方法
在这里插入图片描述

2.因为接口中普遍只存在全局常量和抽象方法,因此接口中 public、abstract、static、final统统可以省略不写。不写abstract也默认是抽象方法,定义的东西都默认是public访问权限。
在这里插入图片描述
需要注意的是,只有接口可以省略这些关键字,抽象类不写public就是默认的default(包)权限

3.接口同样无法直接实例化对象,必须通过子类向上转型将其实例化(这里说的子类就是实现了该接口的类)。
在这里插入图片描述

4.一个子类可以使用implements关键字同时实现多个父接口!(接口优先原则,优先考虑使用接口)

一般来说,除了类似于USB这种特殊接口外,接口一般采用大写的I开头,子类使用impl结尾表示是一个接口的实现子类。

//定义一个IRun接口   这里每条注释下都是不同的文件,需要新建
public interface IRun {
    // 抽象方法,没有方法体
    void run();
}
//定义一个ISwim接口
public interface ISwim {
    void swim();
}
//定义一个IFly接口
public interface IFly {
    void fly();
}
//分别定义三个子类,分别实现若干个接口
//猫猫
public class Cat implements IRun{
    @Override
    public void run() {
        System.out.println("小猫只会跑!");
    }
}
//修勾
public class Dog implements IRun,ISwim{

    @Override
    public void run() {
        this.testRun();
        System.out.println("小狗正在跑");
    }

    @Override
    public void swim() {
        System.out.println("小狗正在狗刨");
    }
}
//鸭鸭
public class Duck implements IRun,ISwim,IFly{
    @Override
    public void fly() {
        System.out.println("鸭子飞起来了");
    }

    @Override
    public void run() {
        System.out.println("鸭子正在陆地上吃虫子");
    }

    @Override
    public void swim() {
        System.out.println("鸭子正在惬意的游泳");
    }
}

上述代码定义了三个接口,并定义了三个子类分别实现了若干个接口。此时写一个Test文件去测试

public class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        Duck duck = new Duck();
        runTest(cat);
        runTest(dog);
        runTest(duck);
        swimTest(dog);
        swimTest(duck);
        flyTest(duck);
    }
    // 定义这个方法时,采用的都是接口参数,接收所有该接口的子类
    // 都属于向上转型(多态)
    public static void runTest(IRun iRun) {
        iRun.run();
    }

    public static void swimTest(ISwim iSwim) {
        iSwim.swim();
    }

    public static void flyTest(IFly iFly) {
        iFly.fly();
    }
}

问题来了,为什么这些runTest、swimTest需要使用父接口作为形参进行传参,而不是使用具体的子类?
在这里插入图片描述
因为 当使用IRun这个接口作为形参时,只要这个类实现了这个接口,就可以传递到这个方法中,若使用具体的子类作为形参,则这个方法只能接受这一个类型! 使用父接口作为参数,就是为了参数统一化

2、举一个很形象的栗子:电脑和USB设备

一个类要实现USB接口,目的就是为了能接入到另一个设备上,鼠标实现USB接口,连接到电脑上、键盘实现USB接口、摄像头实现USB接口这些都是为了接入到电脑上,所以打印机、摄像头、麦克风、鼠标键盘这些类,都相当于USB接口的实现者,而电脑是USB接口的使用者!
在这里插入图片描述
jackUSB就是电脑使用USB的方式,相当于电脑上的USB插孔,而鼠标键盘那些设备只要实现了USB接口(有USB插头)就都可以接入电脑,若参数使用子类的话,相当于参数是键盘子类的口只能插键盘
, 需要插鼠标时有需要新开一个插口,显然是不合理的,因此选择父接口作为参数进行传参。

3、接口的继承与实现

一个类是可以实现多个接口的,正如共享充电宝一样,既有Type-C接口,又有USB接口,这也是接口和抽象类的一个很大的区别,即接口是多继承的,而类是单继承的。因此,若子类同时继承一个类并且实现多个接口时,一定要先使用extends单继承父类,再使用implements多实现接口,否则报错。
在这里插入图片描述
同时,接口和接口之间是可以使用extends关键字,接口可以继承父接口,并且是可以多继承的,没有is a原则。
在这里插入图片描述

但是!接口一定是不能继承一个类的,因为接口中只有全局常量和抽象方法,若接口继承类,相当于接口中也有了普通方法和构造方法!

4、拓展内容

从JDK8开始,接口中也允许存在普通方法,接口中的普通方法使用default关键字定义,有方法体,子类实现接口后可以直接使用接口中的普通方法,但不需要强制实现或覆写普通方法
值得一提的是,这里的default关键字只是告知编译器,这个方法是接口中 的普通方法,但其权限仍然是public的!
在这里插入图片描述
通过this.关键字直接调用普通方法:
在这里插入图片描述

为什么JDK8做出了这样的改进呢,因为有些接口从JDK1.0就有了,当时的接口只有抽象方法,假设JDK1.0有一个接口A,到了JDK8,已经有了10w个子类实现了接口A,若JDK8想给A这个接口拓展一个新的方法,千万不敢!之前的接口只有抽象方法,若拓展新方法,则这10w个子类全部需要重写代码。因此才有了接口中的普通方法,将需要拓展的方法定义为普通方法写入接口,之前的10w个子类就不用重写,而之后定义的实现A接口的类也能使用拓展的方法。

三、抽象类和接口的比较

1、语法

抽象类:

1.由abstract关键词修饰的类称之为抽象类。
2.抽象类中没有实现的方法称之为抽象方法,也需要加关键字abstract。
3.抽象类中也可以没有抽象方法,比如HttpServlet方法。
4.抽象类中可以有已经实现的方法,可以定义成员变量。

接口:

1.由interface关键词修饰的称之为接口;
2.接口中可以定义成员变量,但是这些成员变量默认都是public static final的常量。
3.接口中没有已经实现的方法,全部是抽象方法。
4.一个类实现某一接口,必须实现接口中定义的所有方法。
5.一个类可以实现多个接口

2、使用情景

类在继承抽象类的时候,一个儿子一个爹,爹的财产(非abstract方法和属性)我都继承,同时爹的梦想(abstract方法)我帮他去实现。

类在使用接口就像是一个模块化的机器,安上个音频接口,内部就得做个功放功能。我设计好了要按什么接口就得有什么功能。

总结

总的来说,抽象类和接口的使用都可以优化程序,实现高内聚,低耦合。但在后续的工作学习中,其实大部分情况下都是使用接口的(接口优先不是单纯的语法hhh)。
以上就是关于抽象类和接口的全部学习笔记了,如果有写的不对的地方欢迎指正~

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彭彭彭摆鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值