java · 16 3 月, 2022 0

java中接口vs抽象类, 两者都有哪些区别?

Java本身属于面向对象编程语言,面向对象的四个特性: 继承,封装,抽象,多态。其实这里的抽象与今天所说的抽象类其实不是一个意思。特性中的抽象是对实现业务的抽象,可以通过抽象类,接口或者其他的方式对实现抽象。所以抽象类更多的是强调具体的实现。而特性抽像更多的是一种思想。

Java中同时支持了抽象类和接口的语法实现,在工作中也常常会用到。那么我们思考以下几个问题:

  • 接口和抽象类的区别是什么?
  • 什么时候用抽象类?社么时候用接口?
  • 抽象类和接口存在的意义是什么?解决了那些编程问题?

抽象类vs接口,区别在哪里?

这里主要通过实例的方式来观察抽象类与接口的区别。

如何定义抽象类?

在我们日常的开发中,其实常常会用到模板模式,这个模式其实不同的实现的流程基本一致,但是在某些细节上回有些差别, 我们通过代码的方式看一下:

public abstract class Logger {
    private boolean isLoggable;
    private String level;

    public Logger(boolean isLoggable, String level) {
        this.isLoggable = isLoggable;
        this.level = level;
    }

    public void log(String level, String log) {
        if (!isLoggable) {
            return;
        }

        this.doLog(level, log);
    }
    
    protected abstract void doLog(String level, String log) ;
}

在上面的例子中,Logger类型是记录日志的抽象类,因为我们一般记录日志可以向控制台输出,也可以输出到文件,也可以写出到消息队列,因此对于不同的方式有了不同的实现。因此我们查看具体的实现类:

public class FileLogger extends Logger {

    public FileLogger(boolean isLoggable, String level) {
        super(isLoggable, level);
    }

    @Override
    protected void doLog(String level, String log) {
        System.out.println("输出日志到文件: >>> " + log);
    }
}
public class StdInLogger extends Logger {
    public StdInLogger(boolean isLoggable, String level) {
        super(isLoggable, level);
    }

    @Override
    protected void doLog(String level, String log) {
        System.out.println("输出日志到标准控制台: " + log);
    }
}

在上面的实现中,我们实现了Logger的操作日志的方法,不同的实现对日志的处理方式有所不同。通过上面的实例,我们可以得出一下结论:

  1. 抽象类不允许被实例化。抽象类只能被继承,不能通过new的方式创建出来
  2. 抽象类可以包含属性和方法。其中属性包括了静态属性,成员变量等。方法既可以包含实现方法和抽象方法
  3. 子类继承抽象类,必须实现抽象类中的所有抽象方法。也就是所有继承Logger类的实现类,必须实现doLog方法
  4. 一个实现类只能有实现一个抽象类

如何定义接口?

我们再来看看在JAVA中的接口如何定义,下面我们定义两个接口。

public interface Action {
    
}
public interface Filter {

    String NAME = "FILTER";

    default void filter(Object p) {
        if (checkParam(p)) {
            this.doFilter(p);
        }
    }

    void doFilter(Object p);

    default boolean checkParam(Object p) {
        return p != null;
    }

}

在上面中,定义了一个标识接口Action以及过滤接口Filter,具体实现如下:

public class AuthFilter implements Filter, Action {
    @Override
    public void doFilter(Object p) {
        System.out.println("实现权限的过滤: " + p);
    }
}
public class RateLimitFilter implements Filter {
    @Override
    public void doFilter(Object p) {

    }
}

结合一下代码,我们可以得出一下结论:

  • 接口中定义的属性都是常量,默认为public final修饰
  • 接口中可以声明方法,在JDK1.7之后,接口支持了方法的实现
  • 类实现接口时,必须实现接口中声明的所有方法
  • 类可以实现多个接口

区别

抽象类实际上就是一个特殊的类,这种类不能为实例化为对象,只能被继承,同时继承关系表达是一种is-a的关系,那么抽象类也是一种is-a的关系。

相当于is-a而言,还包含了has-a关系,表示了具有某些功能。因此对于接口而言,有一个更加形象的名字叫做协议。

抽象类和接口解决了什么问题?

  1. 在上面的分析中,可以得知,抽象类表达的是一种is-a的关系,而接口表达了has-a一种关系,属于通用的实现
  2. Logger例子中可以看出,抽象类主要解决的是代码复用的目的。抽象类更多的是表达的是一类具有相似特征和行为的实现集合。
  3. 而接口就更加侧重于解耦。接口是对行为的一种抽象,相当于一组协议或者契约。接口最大的有点在于,调用者不需要关注接口的实现。接口实现了约定和实现分离,可以降低代码的耦合行,提高代码的可扩展性。

如何选择抽象类和接口?

实际上,判断标准很简单:

  • 如果我么表示一种is-a的关系,并且是为了解决代码服用的问题,我们就可以使用抽象类
  • 如果我们是表示has-a的关系,并且是为了解决抽象而非代码复用的问题,那就使用接口

从类的继承层次上来看,抽象类是一种自下而上的设计思路,现有子类代码的重复,然后再抽象成上层的父类。

而接口恰好相反,接口是一种自上而下的设计思路。一般在使用中,我们都是先设计接口,在考虑实现。