为什么看了这么多源码,接口和抽象类还是不会用?

共 4856字,需浏览 10分钟

 ·

2022-03-09 19:58

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

juejin.cn/post/6916775109227200526

推荐:https://www.xttblog.com/?p=5319

我们在面试中可能经常会被问到何谓接口?何谓抽象类? 接口与抽象类差别是什么?接口与抽象类该如何使用?同时我们在工作中或者阅读源码时会发现接口与抽象类经常出现在我们眼前。今天我们就一起来讨论一下接口与抽象类。

接口

官方对于接口的解释是一系列方法的声明,是一些方法特征的集合。那么我们就根据这句话聊一聊接口的特征。

首先从上面这句话可以看出接口中定义了一系列方法的声明,而没有方法的实现,那么从这个角度来看接口就是一种规则的制定者,它不关心你如何实现,只是约束你必须具备这些行为,符合这些规则。而我们的方法中需要定义了一系列业务逻辑代码,完成各种特定的行为,因此方法就是行为在系统中的一种表现形式,而接口约束了我们必须具备哪些行为,那么总结一句话,接口就是对行为的抽象。

比如公司接口规定人应该具备加班的行为(手动调皮),不加班是不行的,总不能不提倡奋斗吧。那么不同的人可能通过不同的方式完成这种行为,有的人可能完成工作快一点,有的人可能完成工作慢一点,有的人手头任务多一点,有的人手头任务少一点,接口对行为进行了抽象,先不关心你到底干了什么,但你必须具备加班这种行为。

必须具备加班这种行为

抽象类

抽象类直观来看就是对类的抽象,而我们又知道类是对同属于一个类的所有对象的抽象,Java是一门面向对象的程序设计语言,它将现实世界的客观事物抽象为对象,因此我们又知道对象是对现实世界的客观事物的抽象。那么总结一句话,抽象类是对类的抽象,是对现实世界的客观事物的进一步抽象。

抽象类是对类的抽象

接口与抽象类的比较

这个问题应该是经常被问到的一个问题,笔者认为我们可以从三个角度来对二者进行比较:(1)类(2)属性(3)方法

从类的角度

  • 一个类要使用抽象类需要使用extends关键字,也就是需要继承抽象类,并且由于Java是单继承,所以只能继承一个抽象类。
  • 一个类要使用接口需要使用implements关键字,称为实现该接口,同时由于Java是多实现机制,所以可以实现多个接口。
  • 抽象类与接口均不能实例化,也就是不能创造接口或者抽象类对象。

从属性的角度

首先我们看以下代码

abstract class AbstractClass
{
    private int a;//可以
    int b;//可以
    protected int c;//可以
    public int d;//可以
    private final int e=1;//可以
    private static int f=2;//可以
    private static final int g=3;//可以
}

interface  Interface
{
    private int a=1;//不可以
    int b;//不可以
    int c=1;//可以
    protected int d=2;//不可以;
    public int e=3;//可以
    public  static int f=4;//可以
    public static final int g=5;//可以
}

从上面的代码我们可以看出

  • 在抽象类中定义属性与普通的类定义属性没有任何的区别,定义类的属性如何定义,抽象类的属性就可以如何定义。
  • 在接口中我们只能使用不带任何修饰符,带public ,带static,带final这几个修饰符,实际上通过反编译之后可以看到接口中的属性全部是使用public static final修饰的。也就是说接口中定义的属性就是以常量的形式存在。

从方法的角度

方法的角度应该抽象类与接口之间差别最大的地方,尤其是现在各个版本都在对接口中的方法不断的进行改变,这里以JDK1.8为例,比较一下接口与抽象类中的方法具有哪些差别。

  • 存在抽象方法的类一定是抽象类,但是抽象类并不需要全部是抽象方法,也可以有普通方法,甚至全部是普通方法(不过如何没有抽象方法,又为何要定义成抽象类呢?)对于抽象类中的普通方法,定义与普通的类中的方法定义一致,可以使用private,不修饰,protected,public 修饰。对于抽象方法,不可以使用private修饰(因为抽象方法本身就是需要子类继承重写的)。普通方法可以使用static修饰,抽象方法不可以使用static修饰。
  • 接口中的方法可以不使用修饰符,或者使用public修饰,通过反编译之后可知,接口中的方法都是用public abstract修饰的,这与接口中的属性类似。不同的是在JDK1.8中,接口可以定义静态方法与default方法,并需要给出默认实现。

二者如何配合使用

我们学习抽象类与接口,最终都是为了能够更好的使用抽象类与接口为我们服务,二者没有孰好孰坏,孰强孰弱,如果是这样的话那其中一个就没有存在的必要了,我们需要学习的是如何让两者配合使用,我们可以从Java世界大量的代码与第三方框架中思考这一点。

我们从前面的讨论可以看到接口是对行为的约束,它提供了一种机制,要求各种不同的类应该具备哪些行为,但是不对行为的具体实现进行限制,所以说接口的抽象层次应该是相当高的。

而抽象类前面我们说是对类的抽象,其设计目的,笔者认为更重要的一方面在于代码的复用,另一方面在于减轻接口实现的负担。当不同的类具备了其中的某些公共的行为,并且这种行为的实现方式一致的时候,这时我们就可以让这些类派生于一个抽象类,避免所有的子类都需要实现接口中的全部方法,这样就实现了代码的复用,同时降低了类实现接口的负担(不需要实现全部接口方法)。

一个例子

例子

现在我们对现实世界中的小车车进行抽象,通过一个例子想一下抽象类与接口可以如何配合使用?

  1. 首先小车车应该可以行驶,同时需要能量驱动发动机行进,可以开门,可以关门。这样我们将小车车的四个行为抽象为接口中的方法,因为这些方法是所有的小车车都必须具备的。
interface Car
{
    //行进
    public void move();

    //补充能量
    public void addEnergy();

    //开门
    public void openTheDoor();

    //关门
    public void closeTheDoor();
}
  1. 这样我们在造小车车的时候,首先必须要实现这四个行为,也就是要实现这四个功能,但是每造一种小车车就需要实现这些功能,各种小车车之间是存在很多差异的,但是这些基本的功能实现都是相似的或者相同的,那这个时候我们想到现在的小车车可以分为燃油车与电车,那么我们可以抽象一下实现这些方法。这样可以降低造小车车的负担(降低实现接口的负担),提高代码的复用率。
abstract class AbstractFuelCar implements Car
{
    public void move()
    
{
        System.out.println("油车突突突的向前冲!!!");
    }
    public void addEnergy()
    
{
        System.out.println("加油。。。");
        //调用子类重写的加油方法。
        refuel();
    }
    public void openTheDoor()
    
{
        System.out.println("燃油车开门。。。");
    }
    public void closeTheDoor()
    
{
        System.out.println("燃油车关门。。。");
    }
    public abstract void refuel();
}

abstract class AbstractEnergyCar implements Car
{
    public void move()
    
{
        System.out.println("电车滋滋滋的向前冲!!!");
    }
    public void addEnergy()
    
{
        System.out.println("加电。。。");
        //调用子类重写的加油方法。
        charge();
    }
    public void openTheDoor()
    
{
        System.out.println("电车开门。。。");
    }
    public void closeTheDoor()
    
{
        System.out.println("电车关门。。。");
    }
    public abstract void charge();
}
  1. 我们在造具体的小车车的时候,只需要继承对应的抽象类实现模板方法以及一些每种小车车特有的功能方法即可,这样不但提高了代码复用,同样也降低了实现接口的负担。
class BMW extends AbstractFuelCar
{

    @Override
    public void refuel()
    
{
        System.out.println("BMW加油了!!!!");
    }

    public void someOtherFunction()
    
{
        System.out.println("我有点小贵。。。");
    }
}

class Tesla extends  AbstractEnergyCar
{

    @Override
    public void charge()
    
{
        System.out.println("特斯拉充电了。。。。");
    }
    public void someOtherFunction()
    
{
        System.out.println("俺加速快!!!");
    }
}

所以一般接口用于规定业务逻辑应该实现什么样的功能,具备什么样的行为,而抽象类在接口之下主要用于代码复用,抽取一些公共行为,降低实现接口的负担,通过模板方法圈定业务逻辑流程,降低子类的实现复杂度。如下图所示:

接口抽象类

在Java世界里学习

下面我们看一下Java类库中以及一些常用的第三方开源框架中是如何配合使用接口与抽象类的。

「1、StringBuilder与StringBuffer」

StringBuilder与StringBuffer的继承结构如下所示:

StringBuilder与StringBuffer
继承结构

「2、Java中集合框架」

ArrayList

ArrayList

HashMap

HashMap

「3、线程池ThreadPoolExecutor」

「4、第三方开源框架」

Spring中的BeanFactory实现类,ApplicationContext实现类,BeanPostProcessor,AOP方面PointCutAdvisor实现等,SpringJDBC,事务管理,SpringMVC,MyBatis中SqlSession等有大量的这样的结构,由于其继承结构一般都比较复杂,这里不再附上图片,感兴趣的同学可以查看Spring,MyBatis等框架的相关源码。

浏览 29
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报