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
的操作日志的方法,不同的实现对日志的处理方式有所不同。通过上面的实例,我们可以得出一下结论:
- 抽象类不允许被实例化。抽象类只能被继承,不能通过
new
的方式创建出来 - 抽象类可以包含属性和方法。其中属性包括了静态属性,成员变量等。方法既可以包含实现方法和抽象方法
- 子类继承抽象类,必须实现抽象类中的所有抽象方法。也就是所有继承
Logger
类的实现类,必须实现doLog
方法 - 一个实现类只能有实现一个抽象类
如何定义接口?
我们再来看看在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
关系,表示了具有某些功能。因此对于接口而言,有一个更加形象的名字叫做协议。
抽象类和接口解决了什么问题?
- 在上面的分析中,可以得知,抽象类表达的是一种
is-a
的关系,而接口表达了has-a
一种关系,属于通用的实现 - 从
Logger
例子中可以看出,抽象类主要解决的是代码复用的目的。抽象类更多的是表达的是一类具有相似特征和行为的实现集合。 - 而接口就更加侧重于解耦。接口是对行为的一种抽象,相当于一组协议或者契约。接口最大的有点在于,调用者不需要关注接口的实现。接口实现了约定和实现分离,可以降低代码的耦合行,提高代码的可扩展性。
如何选择抽象类和接口?
实际上,判断标准很简单:
- 如果我么表示一种
is-a
的关系,并且是为了解决代码服用的问题,我们就可以使用抽象类 - 如果我们是表示
has-a
的关系,并且是为了解决抽象而非代码复用的问题,那就使用接口
从类的继承层次上来看,抽象类是一种自下而上
的设计思路,现有子类代码的重复,然后再抽象成上层的父类。
而接口恰好相反,接口是一种自上而下
的设计思路。一般在使用中,我们都是先设计接口,在考虑实现。