JavaSE笔记——抽象类和接口

news2024/11/27 6:26:23

文章目录

  • 前言
  • 一、抽象类和方法
  • 二、接口创建
    • 1.默认方法
    • 2.多继承
    • 3.接口中的静态方法
  • 三、抽象类和接口
  • 四、完全解耦
  • 五、使用继承扩展接口
  • 六、接口适配
  • 七、接口字段
  • 八、接口和工厂方法模式
  • 总结


前言

接口和抽象类提供了一种将接口与实现分离的更加结构化的方法。


一、抽象类和方法

在前面例子中,基类 Instrument 中的 play() 方法往往是不会用到的方法。其创建它的目的是为派生类创建一个通用方法。在前面例子中,创建这个通用方法的唯一理由是,不同的子类可以用不同的方式来实现。这个通用方法建立了一个基本形式,以此表达所有派生类的共同部分。另一种说法把 Instrument 称为抽象基类,或简称抽象类。

对于像 Instrument 那样的抽象类来说,它的对象几乎总是没有意义的。创建一个抽象类是为了通过通用行为操纵一系列类。因此,Instrument 只是表示一些列相同的行为(方法),不是具体实现,所以创建一个 Instrument 的对象毫无意义,我们可能希望阻止用户这么做。通过让 Instrument 所有的方法产生错误,就可以达到这个目的,但是这么做会延迟到运行时才能得知错误信息,并且需要用户进行可靠、详尽的测试。最好能在编译时捕捉问题。

Java 提供了一个叫做抽象方法的机制,这个方法是不完整的:它只有声明没有方法体。下面是抽象方法的声明语法:

abstract void f();

包含抽象方法的类叫做抽象类。如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,使用 abstract 修饰。否则,编译器会报错。当试图创建抽象类的对象时,编译器也会报错。

public abstract class Basic {
    abstract void unimplemented();

    public static void main(String[] args) {
        Basic basic = new Basic();
    }
}

在这里插入图片描述

如果一个类继承了抽象类并为之创建对象,那么就必须为基类的所有抽象方法提供实现方法。如果不这么做(可以选择不做),这个必须加上 abstract 关键字申明为一个抽象类。

public abstract class Basic1 extends Basic{
    
}

可以将一个不包含任何抽象方法的类指明为 abstract,在类中的抽象方法没啥意义但想阻止创建类的对象时,这么做就很有用。在将一个类指明为 abstract 并不强制类中的所有方法必须都是抽象方法。为了创建可初始化的类,就要继承抽象类,并实现所有抽象方法。

public class Basic2 extends Basic {

    @Override
    void unimplemented() {
        System.out.println("我实现了抽象方法");
    }

    public static void main(String[] args) {
        Basic2 basic2 = new Basic2();
        basic2.unimplemented();
    }
}

留意 @Override 的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。

二、接口创建

使用 interface 关键字创建接口,interface 和 class 一样随处常见。

public interface PureInterface {
    int m1();

    void m2();

    double m3();
}

在 Java 8 之前我们可以这么说:interface 关键字产生一个完全抽象的类,没有提供任何实现。我们只能描述类应该像什么,做什么,但不能描述怎么做,即只能决定方法名、参数列表和返回类型,但是无法确定方法体。接口只提供形式,通常来说没有实现,尽管在某些受限制的情况下可以有实现。

一个接口表示:所有实现了该接口的类看起来都像这样。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。所以,接口被用来建立类之间的协议。

Java 8 中接口稍微有些变化,因为 Java 8 允许接口包含默认方法和静态方法,接口的基本概念仍然没变,介于类型之上、实现之下。

接口同样可以包含属性,这些属性被隐式指明为 static 和 final,用 implements 关键字使一个类遵循某个特定接口(或一组接口)。

public class Implementation implements PureInterface {
    @Override
    public int m1() {
        return 0;
    }

    @Override
    public void m2() {

    }

    @Override
    public double m3() {
        return 0;
    }
}

你可以选择显式地声明接口中的方法为 public,但是即使你不这么做,它们也是public 的。所以当实现一个接口时,来自接口中的方法必须被定义为 public。

1.默认方法

Java 8 为关键字 default 增加了一个新的用途(之前只用于 switch 语句和注解中)。当在接口中使用它时,任何实现接口却没有定义方法的时候可以使用 default 创建的方法体。默认方法比抽象类中的方法受到更多的限制,但是非常有用。

public interface InterfaceWithDefault {
    void firstMethod();

    void secondMethod();

    default void newMethod() {
        System.out.println("newMethod");
    }
}

public class Implementation2 implements InterfaceWithDefault {
    @Override
    public void firstMethod() {
        System.out.println("firstMethod");
    }

    @Override
    public void secondMethod() {
        System.out.println("secondMethod");
    }

    public static void main(String[] args) {
        Implementation2 implementation2 = new Implementation2();
        implementation2.firstMethod();
        implementation2.secondMethod();
        implementation2.newMethod();
    }
}

增加默认方法的极具说服力的理由是它允许在不破坏已使用接口的代码的情况下,在接口中增加新的方法。默认方法有时也被称为守卫方法或虚拟扩展方法。

2.多继承

多继承意味着一个类可能从多个父类型中继承特征和特性。Java 过去是一种严格要求单继承的语言:只能继承自一个类(或抽象类),但可以实现任意多个接口。

public interface One {
    default void first() {
        System.out.println("first");
    }
}

public interface Two {
    default void second() {
        System.out.println("second");
    }
}

public class MI implements One, Two{
    public static void main(String[] args) {
        MI mi = new MI();
        mi.first();
        mi.second();
    }
}

在这里插入图片描述

如果实现的多个接口中方法名和参数类型一样,需要覆写冲突的方法。

3.接口中的静态方法

Java 8 允许在接口中添加静态方法。这么做能恰当地把工具功能置于接口中,从而操作接口,或者成为通用的工具:

public interface Operations {
    void execute();

    static void show(String msg) {
        System.out.println(msg);
    }
}

public class Operation1 implements Operations {
    @Override
    public void execute() {
        Operations.show("Operation1");
    }
}

public class Operation2 implements Operations{
    @Override
    public void execute() {
        Operations.show("Operation2");
    }
}

public class Machine {
    public static void main(String[] args) {
        new Operation1().execute();
        new Operation2().execute();
    }
}

在这里插入图片描述

三、抽象类和接口

尤其是在 Java 8 引入 default 方法之后,选择用抽象类还是用接口变得更加令人困惑。下面做了明确的区分:

参数抽象类接口
默认的方法实现它可以有默认的方法实现接口完全是抽象的。它根本不存在方法的实现
实现子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器抽象类可以有构造器接口不能有构造器
与正常Java类的区别除了你不能实例化抽象类之外,它和普通Java类没有任何区别接口是完全不同的类型
访问修饰符抽象方法可以有public、protected和default这些修饰符接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法抽象方法可以有main方法并且我们可以运行它接口没有main方法,因此我们不能运行它。(java8以后接口可以有default和static方法,所以可以运行main方法)
多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其它接口
速度它比接口速度要快接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

四、完全解耦

当方法操纵的是一个类而非接口时,它就只能作用于那个类或其子类,没法把方法应用于那个继承层级结构之外的类。接口在很大程度上放宽了这个限制,因而使用接口可以编写复用性更好的代码。

例如有一个类 Process 有两个方法 name() 和 process()。process() 方法接受输入,修改并输出。把这个类作为基类用来创建各种不同类型的 Processor。

public class Processor {

    public String name() {
        return getClass().getSimpleName();
    }

    public Object process(Object input) {
        return input;
    }
}

public class Upcase extends Processor {
    @Override
    public String process(Object input) {
        return ((String) input).toUpperCase();
    }
}

public class Downcase extends Processor{
    @Override
    public String process(Object input) {
        return ((String) input).toLowerCase();
    }
}

public class Splitter extends Processor{
    @Override
    public String process(Object input) {
        return Arrays.toString(((String) input).split(" "));
    }
}

public class Applicator {
    public static void apply(Processor p, Object s) {
        System.out.println("Using Processor " + p.name());
        System.out.println(p.process(s));
    }

    public static void main(String[] args) {
        String s = "We are such stuff as dreams are made on";
        apply(new Upcase(), s);
        apply(new Downcase(), s);
        apply(new Splitter(), s);
    }
}

在这里插入图片描述

Applicator 的 apply() 方法可以接受任何类型的 Processor,并将其应用到一个Object 对象上输出结果。像本例中这样,创建一个能根据传入的参数类型从而具备不同行为的方法称为策略设计模式。方法包含算法中不变的部分,策略包含变化的部分。策略就是传入的对象,它包含要执行的代码。在这里,Processor 对象是策略,main() 方法展示了三种不同的应用于 String s 上的策略。

假设现在发现了一组电子滤波器,它们看起来好像能使用 Applicator 的 apply() 方法,如下:

public class Waveform {
    private static long counter;
    private final long id = counter++;

    @Override
    public String toString() {
        return "Waveform " + id;
    }
}

public class Filter {
    public String name() {
        return getClass().getSimpleName();
    }

    public Waveform process(Waveform input) {
        return input;
    }
}

public class LowPass extends Filter {
    @Override
    public Waveform process(Waveform input) {
        return input;
    }
}

public class HighPass extends Filter{
    @Override
    public Waveform process(Waveform input) {
        return input;
    }
}

public class BandPass extends Filter{
    @Override
    public Waveform process(Waveform input) {
        return input;
    }
}

Filter 类与 Processor 类具有相同的接口元素,但是因为它不是继承自 Processor,Filter 类的创建者根本不知道你想将它当作 Processor 使用,因此你不能将 Applicator 的 apply() 方法应用在 Filter 类上。主要是因为 Applicator 的 apply() 方法和 Processor 过于耦合,这阻止了 Applicator 的 apply() 方法被复用。

但如果 Processor 是一个接口,那么限制就会变得松动,只要是实现该接口的所有类都能作为方法参数,这就实现 apply() 方法的复用。Applicator 不需要改变,把 Processor 子类修改如下:

public interface Processor {

    default String name() {
        return getClass().getSimpleName();
    }

    Object process(Object input);
}

public class Upcase implements Processor {
    @Override
    public String process(Object input) {
        return ((String) input).toUpperCase();
    }
}

public class Downcase implements Processor{
    @Override
    public String process(Object input) {
        return ((String) input).toLowerCase();
    }
}

public class Splitter implements Processor{
    @Override
    public String process(Object input) {
        return Arrays.toString(((String) input).split(" "));
    }
}

在这里插入图片描述
我们得到了相同的结果,然而我们经常会遇到的情况是已存在且无法修改类。例如在电子滤波器的例子中,类库已经存在而不是我们创建的,在这些情况下,可以使用适配器设计模式。适配器允许代码接受已有的接口产生需要的方法,如下:

public class FilterAdapter implements Processor{

    private Filter filter;

    FilterAdapter(Filter filter) {
        this.filter = filter;
    }

    @Override
    public String name() {
        return filter.name();
    }

    @Override
    public Waveform process(Object input) {
        return filter.process((Waveform) input);
    }
}

public class Applicator {
    public static void apply(Processor p, Object s) {
        System.out.println("Using Processor " + p.name());
        System.out.println(p.process(s));
    }

    public static void main(String[] args) {
//        String s = "We are such stuff as dreams are made on";
//        apply(new Upcase(), s);
//        apply(new Downcase(), s);
//        apply(new Splitter(), s);

        Waveform w = new Waveform();
        Applicator.apply(new FilterAdapter(new LowPass()), w);
        Applicator.apply(new FilterAdapter(new HighPass()), w);
        Applicator.apply(new FilterAdapter(new BandPass()), w);
    }
}

在这里插入图片描述
在这种使用适配器的方式中,FilterAdapter 的构造器接受已有的接口 Filter,继而产生需要的 Processor 接口的对象。你可能还注意到 FilterAdapter 中使用了委托。协变允许我们从 process() 方法中产生一个 Waveform 而非 Object 对象。将接口与实现解耦使得接口可以应用于多种不同的实现,因而代码更具可复用性。

五、使用继承扩展接口

通过继承,可以很容易在接口中增加方法声明,还可以在新接口中结合多个接口。这两种情况都可以得到新接口。

public interface Monster {
    void menace();
}

public interface DangerousMonster extends Monster{
    void destroy();
}

public class DragonZilla implements DangerousMonster{
    @Override
    public void destroy() {
        System.out.println("destroy方法");
    }

    @Override
    public void menace() {
        System.out.println("menace方法");
    }
}

六、接口适配

接口最吸引人的原因之一是相同的接口可以有多个实现。在简单情况下体现在一个方法接受接口作为参数,该接口的实现和传递对象给方法则交由你来做。因此,接口的一种常见用法是前面提到的策略设计模式。编写一个方法执行某些操作并接受一个指定的接口作为参数。可以说:“只要对象遵循接口,就可以调用方法” ,这使得方法更加灵活,通用,并更具可复用性,前面的 Processor 接口就是如此。

public interface Processor {

    default String name() {
        return getClass().getSimpleName();
    }

    Object process(Object input);
}

七、接口字段

因为接口中的字段都自动是 static 和 final 的,所以接口就成为了创建一组常量的方便的工具。

public interface Months {
    int JANUARY = 1;
    int FEBRUARY = 2;
    int MARCH = 3;
    int APRIL = 4;
    int MAY = 5;
    int JUNE = 6;
    int ULY = 7;
    int AUGUST = 8;
    int SEPTEMBER = 9;
    int OCTOBER = 10;
    int NOVEMBER = 11;
    int DECEMBER = 12;
}

注意 Java 中使用大写字母的风格定义具有初始化值的 static final 变量。接口中的字段自动是 public 的,所以没有显式指明这点。

自 Java 5 开始,我们有了更加强大和灵活的关键字 enum,那么在接口中定义常量组就显得没什么意义了。

八、接口和工厂方法模式

接口是多实现的途径,而生成符合某个接口的对象的典型方式是工厂方法设计模式。不同于直接调用构造器,只需调用工厂对象中的创建方法就能生成对象的实现——理论上,通过这种方式可以将接口与实现的代码完全分离,使得可以透明地将某个实现替换为另一个实现。

public interface Service {
    void method1();
    void method2();
}

public class Service1 implements Service {
    @Override
    public void method1() {
        System.out.println("Service1 method1");
    }

    @Override
    public void method2() {
        System.out.println("Service1 method2");
    }
}

public interface ServiceFactory {
    Service getService();
}

public class Service1Factory implements ServiceFactory {
    @Override
    public Service getService() {
        return new Service1();
    }
}

总结

认为接口是好的选择,从而使用接口不用具体类,这具有诱惑性。几乎任何时候,创建类都可以替代为创建一个接口和工厂。

很多人都掉进了这个陷阱,只要有可能就创建接口和工厂。这种逻辑看起来像是可能会使用不同的实现,所以总是添加这种抽象性。这变成了一种过早的设计优化。

任何抽象性都应该是由真正的需求驱动的。当有必要时才应该使用接口进行重构,而不是到处添加额外的间接层,从而带来额外的复杂性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/51753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

传奇外网架设教程

外网架设前需准备: 准备工具:传奇版本源码,服务器,备案域名,DBC数据库,周年客户端 服务器和备案域名需要自备或者租用,这东西自己造不出来!!! 其他的工具,…

Flink被阿里收购4年,最开心的却是Spark背后的Databricks

最近,Flink Forward Asia(FFA)峰会成功举行,有关Flink的讨论,又开始在国内热闹起来。 2022 年,Apache Flink 社区保持快速发展:GitHub Star 数突破 2 万,单月下载量突破 1400 万次&…

学习总结 | 下一代人工智能

文章目录 一、前言二、底层逻辑三、六大维度今后发展的方向是第三代人工智能,最主要的措施就是把第一代人工智能知识驱动的方法和第二代人工智能数据驱动的方法结合起来,发展安全、可信、可靠和可扩展的人工智能技术,从而推动人工智能的创新应用。 一、前言 中国科学院院士…

iTOP3A5000开发板多路PCIE、SATA、USB3.0等

iTOP3A5000开发板多路PCIE、SATA、USB3.0等 桥片:支持PCIE3.0、USB3.0、SATA3.0、显示接口2路、HDMI和1路VGA、可直接连显示器,另外内置一个网络PHY,片内集成了自研GPU、搭配32位DDR4显存接口,支持16GB显存容量。 底板引出多路PCI…

第3关:添加数据、删除数据、删除表

为了完成本关任务,你需要掌握:1.如何使用HBase shell命令添加数据、2.如何使用命令删除表。 首先启动HBASE 启动HBASEshell 添加数据 我们来给上一关创建的test表的列data添加一些数据: hbase(main):002:0> create test,data Created t…

在 Python 中构建一体化音频分析工具包,在一个地方分析您的音频文件

语言构成了人类之间每次对话的基础。因此,自然语言处理(或简称 NLP)领域无疑在帮助人类日常生活方面具有巨大潜力。 简而言之,NLP 领域包含一组旨在理解人类语言数据并完成下游任务的技术。 NLP 技术涵盖许多领域,例如问答 (QA)、命名实体识别 (NER)、文本摘要、自然语言…

0111 栈与队列Day1

剑指offer09.用两个栈实现队列 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) 示例 1&#x…

音视频开发入门小知识

什么是视频 视频就是由一系列图片构成的,当画面快速切换时,人眼看起来就感觉是连贯的动作。 视频帧 帧,表示一张画面,就是一帧。一个视频就是由许许多多帧组成的。 帧率 帧率,表示单位时间内帧的数量,…

KingbaseES数据库 kdb_schedule 自动定时任务

KingbaseES数据库 kdb_schedule 自动定时任务 文章目录KingbaseES数据库 kdb_schedule 自动定时任务前言一 安装插件 kdb_schedule1. 添加kdb_schedule2. 修改kdb_schedule所需参数:3. 重启数据库4. 加载kdb_schedule插件二 dbms_scheduler2.1 创建program创建progr…

(四) 共享模型之管程【Monitor 概念】

一、Java 对象头(P75) 二、原理之 Monitor(锁) Monitor 被翻译为监视器或管程。 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设…

Cookie Session JSP

这里写目录标题1 Cookie1.1 会话介绍1.2 Cookie 介绍1.3 Cookie 属性1.4 Cookie 方法1.4.1 Cookie 添加和获取1.5 Cookie 的使用1.6 Cookie 的细节2 Session2.1 HttpSession 介绍2.2 HttpSession 常用方法2.3 HttpSession 获取2.4 HttpSession 的使用2.5 HttpSession 的细节3 J…

高压功率放大器在超声驻波声场的听声器中的应用

实验名称:高压功率放大器在超声驻波声场的听声器声压测量中的应用 研究方向:3D打印 测试目的:利用听声器对声场的测量是一种基于对声压的采集,利用CPB分析及FFT分析处理,得到涉入点声压的方法。介于听声器采集信号为时…

单字段纵向分栏

【问题】 Hi, I’m trying to display BIRT report Data (only one field) first vertically till the page ends and then it should continue in the next column of the same page. For example as A E I B F J C G D HBy using list element I’m able to get the data …

opencv上设置摄像头曝光参数的经验

实际应用中我们需要调整摄像头的参数比如曝光,由于opencv的后端是一般编译是支撑多种插件,详细信息请参考OpenCV: Video I/O with OpenCV Overview,这里引用里面的图: 对于VideoCaputure,后端有ffmpge,V4L&…

SpringMVC入门

SpringMVC 一、SpringMVC简介 1、什么是MVC MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分 M:Model,模型层,指工程中的JavaBean,作用是处理数据 JavaBean分为两类: 一类称为实体类Bea…

软件工程SSM毕设项目 - 基于SSM的中药店商城网站(含源码+论文)

文章目录1 项目简介2 实现效果2.1 界面展示3 设计方案3.1 概述3.2 系统流程3.3 系统结构设计4 项目获取1 项目简介 Hi,各位同学好呀,这里是M学姐! 今天向大家分享一个今年(2022)最新完成的毕业设计项目作品,【基于SSM的中药店商…

微信小程序能给花店带来哪些作用_分享花店微信小程序开发优势

在开发过小程序的线下实体店铺中,有不少花店。开发了小程序的花店纷纷表示:"小程序提供了非常大的帮助,现在越来越离不开小程序了"。那么,小程序能给花店带来哪些帮助? 1、提升店铺曝光半径挖掘更多流量 对…

哈希表题目:键盘行

文章目录题目标题和出处难度题目描述要求示例数据范围解法思路和算法代码复杂度分析题目 标题和出处 标题:键盘行 出处:500. 键盘行 难度 2 级 题目描述 要求 给你一个字符串数组 words\texttt{words}words,只返回可以使用在美式键盘…

Scala集合习题Ⅱ

行是知之始,知是行之成。——陶行知 目录 练习题 3 :求出各城市的平均温度 练习题4:请用scala得出以下的结果 练习题 3 :求出各城市的平均温度 val d1 Array(("bj", 28.1), ("sh", 28.7), ("gz"…

RK3588平台开发系列讲解(PWM篇)PWM及backlight的使用方法

平台内核版本安卓版本RK3588Linux 5.10Android12🚀返回专栏总目录 文章目录 一、PWM驱动二、DTS配置三、PWM在user space的使用四、PWM在背光中的使用4.1 Backlight DTS4.2 PWM Backlight 调试沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍PWM以及backli…