今天就来随便讲讲:Java 接口和抽象类的区别吧。(详解)
cac55 2024-09-20 12:42 23 浏览 0 评论
在面向对象编程中,抽象类和接口是两个经常被用到的语法概念,是面向对象四大特性,以及很多设计模式、设计思想、设计原则编程实现的基础。下面就来讲讲二者的区别。
什么是抽象类和接口? 区别在哪里?
不同的编程语言对接口和抽象类的定义方式可能有些差别,但是差别并不大。本文使用 Java 语言。
抽象类
下面我们通过一个例子来看一个典型的抽象类的使用场景。
Logger 是一个记录日志的抽象类,FileLogger 和 MessageQueueLogger 继承Logger,分别实现两种不同的日志记录方式:
- 记录日志到文件中
- 记录日志到消息队列中
FileLogger 和 MessageQueuLogger 两个子类复用了父类 Logger 中的name、enabled 以及 minPermittedLevel 属性和 log 方法,但是因为两个子类写日志的方式不同,他们又各自重写了父类中的doLog方法。
父类
import java.util.logging.Level;
/**
* 抽象父类
* @author yanliang
* @date 9/27/2020 5:59 PM
*/
public abstract class Logger {
private String name;
private boolean enabled;
private Level minPermittedLevel;
public Logger(String name, boolean enabled, Level minPermittedLevel) {
this.name = name;
this.enabled = enabled;
this.minPermittedLevel = minPermittedLevel;
}
public void log(Level level, String message) {
boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValue());
if(!loggable) return;
doLog(level, message);
}
protected abstract void doLog(Level level, String message);
}
FileLogger
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.logging.Level;
/**
* 抽象类Logger的子类:输出日志到文件中
* @author yanliang
* @date 9/28/2020 4:44 PM
*/
public class FileLogger extends Logger {
private Writer fileWriter;
public FileLogger(String name, boolean enabled, Level minPermittedLevel, String filePath) throws IOException {
super(name, enabled, minPermittedLevel);
this.fileWriter = new FileWriter(filePath);
}
@Override
protected void doLog(Level level, String message) {
// 格式化level 和 message,输出到日志文件
fileWriter.write(...);
}
}
MessageQueuLogger
import java.util.logging.Level;
/**
* 抽象类Logger的子类:输出日志到消息队列中
* @author yanliang
* @date 9/28/2020 6:39 PM
*/
public class MessageQueueLogger extends Logger {
private MessageQueueClient messageQueueClient;
public MessageQueueLogger(String name, boolean enabled, Level minPermittedLevel, MessageQueueClient messageQueueClient) {
super(name, enabled, minPermittedLevel);
this.messageQueueClient = messageQueueClient;
}
@Override
protected void doLog(Level level, String message) {
// 格式化level 和 message,输出到消息队列中
messageQueueClient.send(...)
}
}
通过上面的例子,我们来看下抽象类有哪些特性。
- 抽象类不能被实例化,只能被继承。(new 一个抽象类,会报编译错误)
- 抽象类可以包含属性和方法。方法既可以包含实现,也可以不包含实现。不包含实现的方法叫做抽象方法
- 子类继承抽象类,必须实现抽象类中的所有抽象方法。
接口
同样的,下面我们通过一个例子来看下接口的使用场景。
/**
* 过滤器接口
* @author yanliang
* @date 9/28/2020 6:46 PM
*/
public interface Filter {
void doFilter(RpcRequest req) throws RpcException;
}
/**
* 接口实现类:鉴权过滤器
* @author yanliang
* @date 9/28/2020 6:48 PM
*/
public class AuthencationFilter implements Filter {
@Override
public void doFilter(RpcRequest req) throws RpcException {
// 鉴权逻辑
}
}
/**
* 接口实现类:限流过滤器
* @author yanliang
* @date 9/28/2020 6:48 PM
*/
public class RateLimitFilter implements Filter{
@Override
public void doFilter(RpcRequest req) throws RpcException {
// 限流逻辑
}
}
/**
* 过滤器使用demo
* @author yanliang
* @date 9/28/2020 6:48 PM
*/
public class Application {
// 过滤器列表
private List<Filter> filters = new ArrayList<>();
filters.add(new AuthencationFilter());
filters.add(new RateLimitFilter());
public void handleRpcRequest(RpcRequest req) {
try {
for (Filter filter : filters) {
filter.doFilter(req);
}
} catch (RpcException e) {
// 处理过滤结果
}
// ...
}
}
上面的案例是一个典型的接口使用场景。通过Java中的 interface 关键字定义了一个Filter 接口,AuthencationFilter 和 RetaLimitFilter 是接口的两个实现类,分别实现了对Rpc请求的鉴权和限流的过滤功能。
下面我们来看下接口的特性:
- 接口不能包含属性(也就是成员变量)
- 接口只能生命方法,方法不能包含代码实现
- 类实现接口时,必须实现接口中生命的所有方法。
综上,从语法上对比,这两者有比较大的区别,比如抽象类中可以定义属性、方法的实现,而接口中不能定义属性,方法也不能包含实现等。
除了语法特性的不同外,从设计的角度,这两者也有较大区别。抽象类本质上就是类,只不过是一种特殊的类,这种类不能被实例化,只能被子类继承。属于is-a的关系。接口则是 has-a 的关系,表示具有某些功能。对于接口,有一个更形象的叫法:协议(contract)
PS:发个美图看看 不然眼睛会瞎
抽象类和接口解决了什么问题?
下面我们先来思考一个问题~
抽象类的存在意义是为了解决代码复用的问题(多个子类可以继承抽象类中定义的属性哈方法,避免在子类中,重复编写相同的代码)。
那么,既然继承本身就能达到代码复用的目的,而且继承也不一定非要求是抽象类。我们不适用抽象类,貌似也可以实现继承和复用。从这个角度上讲,我们好像并不需要抽象类这种语法呀。那抽象类除了解决代码复用的问题,还有其他存在的意义吗?
这里大家可以先思考一下哈~
我们还是借用上面Logger的例子,首先对上面的案例实现做一些改造。在改造之后的实现中,Logger不再是抽象类,只是一个普通的父类,删除了Logger中的两个方法,新增了 isLoggable()方法。FileLogger 和 MessageQueueLogger 还是继承Logger父类已达到代码复用的目的。具体代码如下:
/**
* 父类:非抽象类,就是普通的类
* @author yanliang
* @date 9/27/2020 5:59 PM
*/
public class Logger {
private String name;
private boolean enabled;
private Level minPermittedLevel;
public Logger(String name, boolean enabled, Level minPermittedLevel) {
this.name = name;
this.enabled = enabled;
this.minPermittedLevel = minPermittedLevel;
}
public boolean isLoggable(Level level) {
return enabled && (minPermittedLevel.intValue() <= level.intValue());
}
}
/**
* 抽象类Logger的子类:输出日志到文件中
* @author yanliang
* @date 9/28/2020 4:44 PM
*/
public class FileLogger extends Logger {
private Writer fileWriter;
public FileLogger(String name, boolean enabled, Level minPermittedLevel, String filePath) throws IOException {
super(name, enabled, minPermittedLevel);
this.fileWriter = new FileWriter(filePath);
}
protected void log(Level level, String message) {
if (!isLoggable(level)) return ;
// 格式化level 和 message,输出到日志文件
fileWriter.write(...);
}
}
package com.yanliang.note.java.abstract_demo;
import java.util.logging.Level;
/**
* 抽象类Logger的子类:输出日志到消息队列中
* @author yanliang
* @date 9/28/2020 6:39 PM
*/
public class MessageQueueLogger extends Logger {
private MessageQueueClient messageQueueClient;
public MessageQueueLogger(String name, boolean enabled, Level minPermittedLevel, MessageQueueClient messageQueueClient) {
super(name, enabled, minPermittedLevel);
this.messageQueueClient = messageQueueClient;
}
protected void log(Level level, String message) {
if (!isLoggable(level)) return ;
// 格式化level 和 message,输出到消息队列中
messageQueueClient.send(...)
}
}
以上实现虽然达到了代码复用的目的(复用了父类中的属性),但是却无法使用多态的特性了。
像下面这样编写代码就会出现编译错误,因为Logger中并没有定义log()方法。
Logger logger = new FileLogger("access-log", true, Level.WARN, "/user/log");
logger.log(Level.ERROR, "This is a test log message.");
如果我们在父类中,定义一个空的log()方法,让子类重写父类的log()方法,实现自己的记录日志逻辑。使用这种方式是否能够解决上面的问题呢? 大家可以先思考下~
这个思路可以用使用,但是并不优雅,主要有一下几点原因:
- 在Logger中定义一个空的方法,会影响代码的可读性。如果不熟悉Logger背后的设计思想,又没有代码注释的话,在阅读Logger代码时就会感到疑惑(为什么这里会存在一个空的log()方法)
- 当创建一个新的子类继承Logger父类时,有时可能会忘记重新实现log方法。之前是基于抽象类的设计思想,编译器会强制要求子类重写父类的log方法,否则就会报编译错误。
- Logger可以被实例化,这也就意味着这个空的log方法有可能会被调用。这就增加了类被误用的风险。当然,这个问题 可以通过设置私有的构造函数的方式来解决,但是不如抽象类优雅。
抽象类更多是为了代码复用,而接口更侧重于解耦。接口是对行为的一种抽象,相当于一组协议或者契约(可类比API接口)。调用者只需要关心抽象的接口,不需要了解具体的实现,具体的实现代码对调用者透明。接口实现了约定和实现相分离,可以降低代码间的耦合,提高代码的可扩展性。
实际上,接口是一个比抽象类应用更加广泛、更加重要的知识点。比如,我们经常提到的 ”基于接口而非实现编程“ ,就是一条几乎天天会用到的,并且能极大地提高代码的灵活性、扩展性的设计思想。
如何模拟抽象类和接口
在前面列举的例子中,我们使用Java的接口实现了Filter过滤器。不过,在 C++ 中只提供了抽象类,并没有提供接口,那从代码的角度上说,是不是就无法实现 Filter 的设计思路了呢? 大家可以先思考下 ~
我们先会议下接口的定义:接口中没有成员变量,只有方法声明,没有方法实现,实现接口的类必须实现接口中的所有方法。主要满足以上几点从设计的角度上来说,我们就可以把它叫做接口。
实际上,要满足接口的这些特性并不难。下面我们来看下实现:
class Strategy {
public:
-Strategy();
virtual void algorithm()=0;
protected:
Strategy();
}
抽象类 Strategy 没有定义任何属性,并且所有的方法都声明为 virtual 类型(等同于Java中的abstract关键字),这样,所有的方法都不能有代码实现,并且所有继承了这个抽象类的子类,都要实现这些方法。从语法特性上看,这个抽象类就相当于一个接口。
除了用抽象类来模拟接口外,我们还可以用普通类来模拟接口。具体的Java实现如下所示:
public class MockInterface {
protected MockInteface();
public void funcA() {
throw new MethodUnSupportedException();
}
}
我们知道类中的方法必须包含实现,这个不符合接口的定义。但是,我们可以让类中的方法抛出 MethodUnSupportedException 异常,来模拟不包含实现的接口,并且强迫子类来继承这个父类的时候,都主动实现父类的方法,否则就会在运行时抛出异常。
那又如何避免这个类被实例化呢? 实际上很简单,我们只需要将这个类的构造函数声明为 protected 访问权限就可以了。
如何决定该用抽象还是接口?
上面的讲解可能偏理论,现在我们就从真实项目开发的角度来看下。在代码设计/编程时,什么时候该用接口?什么时候该用抽象类?
实际上,判断的标准很简单。如果我们需要一种is-a关系,并且是为了解决代码复用的问题,就用抽象类。如果我们需要的是一种has-a关系,并且是为了解决抽象而非代码复用问题,我们就用接口。
从类的继承层次来看,抽象类是一种自下而上的设计思路,先有子类的代码复用,然后再抽象成上层的父类(也就是抽象类)。而接口则相反,它是一种自上而下的设计思路,我们在编程的时候,一般都是先设计接口,再去思考具体实现。
好了,你是否掌握了上面的内容呢。你可以通过一下几个维度来回顾自检一下:
- 抽象类和接口的语法特性
- 抽象类和接口存在的意义
- 抽象类和接口的应用场景有哪些
2021最新完整面试题及答案(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、JVM、源码、算法,详细的学习规划图等资料,需要获取这些内容的朋友请私信我哦~
这篇文章就讲到这里啦,谢谢各位看官看到这里,谢谢大家支持!!老子爱你们!最后再送一张美图给你们!都是我珍藏的宝贝啊呜呜呜不三连就过分了奥!!
相关推荐
- 让组策略保护Windows XP的安全
-
默认安装完WindowsXP之后,我们的WindowsXP并不很安全。因此,我们有必要对系统进行一些修修补补,一般情况下我们都要动用到注册表。诚然,修改注册表是一种非常有效的方法,但是它需要一定的...
- 你造吗?十种方式保护你免受"零日攻击"
-
|责编:王迪WindowsXP的寿终正寝,数据安全问题又再一次成为人们关注的焦点。近日,微软透漏,一个基于InternetExplorer的“零日攻击”给用户带来了严重破坏。“零日攻击”一种利用...
- 特立独行——打造游戏专用独立系统
-
大部分人的电脑是为了学习和工作用的,所以,如果你是一个游戏迷,那么推荐你安装一个独立系统专用于游戏,做到工作娱乐两不相扰。方案1:游戏专用移动WindowsXP目的:解决游戏兼容性问题喜欢玩游戏的都...
- 驰为VX8 3G Win8入门教程篇
-
距离Win8.1的正式发布也将近1年了,凭借着Win8.1在移动便携以及娱乐办公上的优势,现在的Win8平板越来越受到消费者的追捧,而驰为VX83G就是其中一款,搭载了卓越的英特尔Z3735G四核芯,...
- 易淘收银软件说明
-
易淘收银系统,简称易淘收银,专为小型及连锁零售、餐饮行业打造。基于SaaS模式,智能便捷,无需维护,轻量级设计却功能强大,简约而不失专业,助力门店高效管理收银。1、前台系统:收银客户端;2、后台系...
- CAD打不开怎么办?原因可能是电脑中毒了,6步就能完美解决问题
-
一、问题描述我的CAD安装后无法打开,安装过程中没有出现任何问题,但是安装后打开就出现一个对话框“DBXCAS0”点击后又出现“FATALERROR:UnhandledAccessViola...
- 腾讯QQ6.1正式版发布更新
-
2014-07-2405:12:00作者:张林【中关村在线软件资讯】7月24日消息:腾讯QQ官网小幅更新了QQ6.1正式版,最新版本号升级至11905,继续主打扁平化、炫酷登录窗口、支持同步最近一...
- Win10等网页版OneDrive无法登陆怎么办?
-
IT之家(www.ithome.com):Win10等网页版OneDrive无法登陆怎么办?Win10之家报道,微软OneDrive云网盘是跨平台的数据同步和存储服务,支持WindowsPC(如Wi...
- 经典回顾:折戟沉沙的Windows Longhorn有着惊艳的登录屏幕
-
尽管微软原先计划让WindowsLonghorn继承WindowsXP操作系统的衣钵,但这个充满雄心壮志的操作系统项目最终还是未能迎来曙光,而是被微软用WindowsVista取而代...
- 电脑怎么优化
-
电脑配置和宽带流量也是硬件,但这些要求其实并不需要很高,关键还是怎么去安全使用电脑并进行有效的优化。电脑的应用和优化处理一、电脑的应用和优化处理二、目前,大家使用的个人电脑,配置方面均没多大问题,比如...
- 怎么安装usb驱动
-
USB驱动主要是针对WIN98时代的说法,如今WINXP已集成大部分USB驱动,通常都能识别。只有极少数情况下,例如手机、打印机或扫描仪等办公设备的USB驱动可能无法自动识别。1、USB驱动偶尔无法...
- 普通话考试多名考生信息被泄露,接投诉后涉事网站被限制访问
-
“陕西普通话成绩查询网(sxpth.cn)”泄露个人信息网站截图网传图片显示,407名普通话考试考生的姓名、身份证号码等个人信息疑似被泄露。9月26日下午,涉事网站sxpth.cn的域名注册商——成...
- 电脑伪技巧——个人电脑无需设置登录密码
-
默认情况下,我们每次登录系统都要输入登录账户对应的密码才能进入桌面。有些朋友觉得这样很麻烦,由于电脑只是自己使用,还不如不要设置密码,这样每次可以自动登录。大家知道,账户密码是系统验证用户合法性的唯一...
- Windows 10/11 自带远程桌面:实用技巧与操作指南
-
Windows10/11自带远程桌面:实用技巧与操作指南在当今快节奏的数字时代,远程访问和控制计算机的需求日益增长。微软在Windows10和Windows11中内置了远程桌面功能,为用户提供...
- 不升级系统的5大原因造吗?
-
2015-01-2405:54:00作者:陈占伟Windows10系统的发布,让人们重新将目光聚焦到生命力长久的Windows系统之上。如今操作系统越来越多,似乎Windows升级的获得的关注度...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 如何绘制折线图 (52)
- javaabstract (48)
- 新浪微博头像 (53)
- grub4dos (66)
- s扫描器 (51)
- httpfile dll (48)
- ps实例教程 (55)
- taskmgr (51)
- s spline (61)
- vnc远程控制 (47)
- 数据丢失 (47)
- wbem (57)
- flac文件 (72)
- 网页制作基础教程 (53)
- 镜像文件刻录 (61)
- ug5 0软件免费下载 (78)
- debian下载 (53)
- ubuntu10 04 (60)
- web qq登录 (59)
- 笔记本变成无线路由 (52)
- flash player 11 4 (50)
- 右键菜单清理 (78)
- cuteftp 注册码 (57)
- ospf协议 (53)
- ms17 010 下载 (60)