我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java 接口和抽象类的区别详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java 接口和抽象类的区别详解

什么是抽象类和接口? 区别在哪里?

不同的编程语言对接口和抽象类的定义方式可能有些差别,但是差别并不大。本文使用 Java 语言。

抽象类

下面我们通过一个例子来看一个典型的抽象类的使用场景。

Logger 是一个记录日志的抽象类,FileLogger 和 MessageQueueLogger 继承Logger,分别实现两种不同的日志记录方式:

  • 记录日志到文件中
  • 记录日志到消息队列中

FileLogger 和 MessageQueuLogger 两个子类复用了父类 Logger 中的name、enabled 以及 minPermittedLevel 属性和 log 方法,但是因为两个子类写日志的方式不同,他们又各自重写了父类中的doLog方法。

父类


import java.util.logging.Level;


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;


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;


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 一个抽象类,会报编译错误)
  • 抽象类可以包含属性和方法。方法既可以包含实现,也可以不包含实现。不包含实现的方法叫做抽象方法
  • 子类继承抽象类,必须实现抽象类中的所有抽象方法。

接口

同样的,下面我们通过一个例子来看下接口的使用场景。



public interface Filter {
    void doFilter(RpcRequest req) throws RpcException;
}


public class AuthencationFilter implements Filter {

    @Override
    public void doFilter(RpcRequest req) throws RpcException {
        // 鉴权逻辑
    }
}


public class RateLimitFilter implements Filter{

    @Override
    public void doFilter(RpcRequest req) throws RpcException {
        // 限流逻辑
    }
}


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)

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

下面我们先来思考一个问题~

抽象类的存在意义是为了解决代码复用的问题(多个子类可以继承抽象类中定义的属性哈方法,避免在子类中,重复编写相同的代码)。

那么,既然继承本身就能达到代码复用的目的,而且继承也不一定非要求是抽象类。我们不适用抽象类,貌似也可以实现继承和复用。从这个角度上讲,我们好像并不需要抽象类这种语法呀。那抽象类除了解决代码复用的问题,还有其他存在的意义吗?

这里大家可以先思考一下哈~

我们还是借用上面Logger的例子,首先对上面的案例实现做一些改造。在改造之后的实现中,Logger不再是抽象类,只是一个普通的父类,删除了Logger中的两个方法,新增了 isLoggable()方法。FileLogger 和 MessageQueueLogger 还是继承Logger父类已达到代码复用的目的。具体代码如下:



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());
    }

}


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;


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关系,并且是为了解决抽象而非代码复用问题,我们就用接口。

从类的继承层次来看,抽象类是一种自下而上的设计思路,先有子类的代码复用,然后再抽象成上层的父类(也就是抽象类)。而接口则相反,它是一种自上而下的设计思路,我们在编程的时候,一般都是先设计接口,再去思考具体实现。

好了,你是否掌握了上面的内容呢。你可以通过一下几个维度来回顾自检一下:

  • 抽象类和接口的语法特性
  • 抽象类和接口存在的意义
  • 抽象类和接口的应用场景有哪些

以上就是Java 接口和抽象类的区别的详细内容,更多关于Java 接口和抽象类的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java 接口和抽象类的区别详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C#抽象类和接口的区别

这篇文章主要介绍“C#抽象类和接口的区别”,在日常操作中,相信很多人在C#抽象类和接口的区别问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#抽象类和接口的区别”的疑惑有所帮助!接下来,请跟着小编一起来学习吧
2023-06-18

java抽象类和接口的区别是什么

这篇文章主要介绍“java抽象类和接口的区别是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java抽象类和接口的区别是什么”文章能帮助大家解决问题。区别1、抽象类和子类是is的关系,接口是li
2023-06-30

java接口和抽象类的区别有哪些

1. 实现方式不同:接口是用关键字interface定义的,抽象类是用关键字abstract定义的。2. 继承方式不同:类可以实现多个接口,但只能继承一个抽象类。3. 成员变量不同:接口中只能声明常量,不能声明实例变量;抽象类可以包含实例变
2023-09-15

java接口和抽象类有什么区别

Java接口和抽象类都是用来实现多态性的机制,但是它们有一些重要的区别:1. 实现方式:接口是通过实现关键字"implements"来实现的,而抽象类是通过继承关键字"extends"来实现的。2. 继承:一个类可以实现多个接口,但是只能继
2023-08-15

JAVA中接口和抽象类有哪些区别

这篇文章给大家分享的是有关JAVA中接口和抽象类有哪些区别的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。接口和抽象类的区别:相同点: l 都位于继承的顶端,用于被其他类实现或继承;l 都不能直接实例化对象;l
2023-06-02

java中的抽象类和接口有哪些区别

小编给大家分享一下java中的抽象类和接口有哪些区别,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!在Java语言中,abstract class 和interfa
2023-06-17

Java中抽象类和接口有哪些区别

这篇文章主要为大家展示了“Java中抽象类和接口有哪些区别”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Java中抽象类和接口有哪些区别”这篇文章吧。1、抽象类 vs 接口 方法类型: 接口只能
2023-06-21

Java中抽象类和接口的区别是什么

本篇内容介绍了“Java中抽象类和接口的区别是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在Java语言中, abstract cla
2023-06-17

java中抽象类和接口有什么区别?

在java中抽象类和接口都是用于实现抽象的,那么它们之间有什么区别?下面本篇文章就来给大家介绍一下。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。抽象类抽象类是用来捕捉子类的通用特性,即共同点的;比如:汽车、自行车、电瓶车,它们都是车子,可以
java中抽象类和接口有什么区别?
2021-09-13

java抽象类与接口的区别有哪些

一、抽象类在Java中,被关键字abstract修饰的类称为抽象类;被abstract修饰的方法称为抽象方法,抽象方法只有方法声明没有方法体。抽象类有以下几个特点:1、抽象类不能被实例化,只能被继承。2、包含抽象方法的类一定是抽象类,但抽象类不一定包含抽象方法
java抽象类与接口的区别有哪些
2019-10-06

java 抽象和接口的区别有哪些

什么时候使用抽象类和接口如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。如果基本功能在不断改变,那么
java 抽象和接口的区别有哪些
2020-05-10

php抽象类和接口有什么区别

本篇文章给大家分享的是有关php抽象类和接口有什么区别,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。区别:1、对接口的使用是通过关键字implements。对抽象类的使用是通过
2023-06-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录