初识Java 8-1 接口和抽象类

news2024/11/26 19:58:57

目录

抽象类和抽象方法

接口定义

默认方法

多重继承

接口中的静态方法

作为接口的Instrument


本笔记参考自: 《On Java 中文版》


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

抽象类和抽象方法

        抽象类,其介于普通类和接口之间。在构建具有字段而未实现方法的类时,抽象类是重要且必要的工具。以Instrument类为例:

package music;

public class Instrument {
    public void play(Note n) {
    }
}

        由这个类可以衍生出很多子类:WindBrass等。在这里,Instrument存在的目的就是为了为它的子类创建一个公共接口。换言之,Instrument建立了一种基本形式,用于抽象出所有子类的共同之处。在这里,Instrument可以被称为抽象基类,简称抽象类

    创建抽象类的目的,就是通过一个公共接口来操作一组的类。因此,Instrument只需要提供一个接口就可以了。

        Java提供了一种抽象方法的机制,这是一个不完整的方法:只有声明,而没有方法体(类似于C++的纯虚函数)。其声明语法如下:

abstract void f();

        包含抽象方法的类就是抽象类。若一个类包含有一个或以上的抽象方法,则该类就必须被定义为抽象类,否则就会报错。

abstract class Basic {
    abstract void unimplemented();
}

        因为抽象类是不完整的,为了防止其被误用,当试图创建一个抽象类的对象时,就会收到报错:

        若想要继承一个抽象类,那么这个新类就必须为基类中的所有抽象方法提高方法定义。否则,认为子类也是抽象的,此时编译器会强制要求使用abstract关键字来限定这个子类:

abstract class Basic2 extends Basic {
    int f() {
        return 111;
    }

    abstract void g();
    // 仍然没有实现unimplemented()方法
}

        一个抽象类可以不包含任何的抽象方法。这种用法主要用于阻止对于该方法的任何实例化:

abstract class Basic3 {
    int f() {
        return 111;
    }

    // 可以不需要抽象方法
}

public class AbstractWithoutAbstracts {
    // 实例化依旧会报错:Basic3是抽象类
    // Basic3 b3 = new Basic3();
}

        使用一个继承了抽象类的子类,若想要实例化这个子类,就需要为抽象类的所有抽象方法提供定义:

abstract class Uninstantiable {
    abstract void f();

    abstract void g();
}

public class Instantiable extends Uninstantiable {
    @Override
    void f() {
        System.out.println("f()");
    }

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

    public static void main(String[] args) {
        Uninstantiable ui = new Instantiable();
    }
}

        在上述程序中,即使@Override不标注,只要没有使用相同的方法名称或是方法签名,抽象机制仍然可以知道程序员有没有实现抽象方法。在这里,@Override主要用于提示,表示该方法已经被重写了。

        抽象类对于访问权限没有过多限制,其的默认访问权限就是包访问权限。但是,抽象方法是不允许private的:

abstract class AbstractAccess {
    private void m1() {
    }
    // private abstract void m1a();

    protected void m2() {
    }
    protected abstract void m2a();

    void m3() {
    }
    abstract void m3a();

    public void m4() {
    }
    public abstract void m4a();
}

        不允许private abstract是因为,这种抽象方法无法在子类中得到一个合法的定义

        抽象类不会要求其所有的方法都是抽象的,将需要使用的公共接口声明为abstract即可。依据这些已知的知识,以乐器(Instrument)为例:

package music4;

import music.Note;

abstract class Instrument {
    private int i; // 这一变量在每个对象中都会被分配储存

    public abstract void play(Note n);

    public String what() {
        return "Instrument";
    }

    public abstract void adjust();
}

class Wind extends Instrument {
    @Override
    public void play(Note n) {
        System.out.println("Wind.play(): " + n);
    }

    @Override
    public String what() {
        return "Wind";
    }

    @Override
    public void adjust() {
        System.out.println("对Wind进行调整");
    }
}

class Percussion extends Instrument {
    @Override
    public void play(Note n) {
        System.out.println("Percussion.play(): " + n);
    }

    @Override
    public String what() {
        return "Percussion";
    }

    @Override
    public void adjust() {
        System.out.println("对Percussion进行调整");
    }
}

class Stringed extends Instrument {
    @Override
    public void play(Note n) {
        System.out.println("Stringed.play(): " + n);
    }

    @Override
    public String what() {
        return "Stringed";
    }

    @Override
    public void adjust() {
        System.out.println("对Stringed进行调整");
    }
}

class Brass extends Wind {
    @Override
    public void play(Note n) {
        System.out.println("Brass.play(): " + n);
    }

    @Override
    public void adjust() {
        System.out.println("对Brass进行调整");
    }
}

class Woodwind extends Wind {
    @Override
    public void play(Note n) {
        System.out.println("Woodwind.play(): " + n);
    }

    @Override
    public String what() {
        return "Woodwind";
    }
}

public class Music4 {
    static void tune(Instrument i) { // 新的类型也可以使用tune()方法
        // ...
        i.play(Note.MIDDLE_C);
    }

    static void tuneAll(Instrument[] e) {
        for (Instrument i : e)
            tune(i);
    }

    public static void main(String[] args) {
        Instrument[] orchestra = {
                new Wind(),
                new Percussion(),
                new Stringed(),
                new Brass(),
                new Woodwind()
        };
        tuneAll(orchestra);
    }
}

        程序执行的结果如下:

        抽象类和抽象方法明确了类的抽象性,并且告诉用户和编译器自己的预期用途,这种工具也常被用在重构之中。

接口定义

        接口通过interface进行定义。在Java 8之前,它只被运行使用抽象方法:

public interface PureInterface {
    int m1();
    void m2();
    double m3();
}

在接口中定义抽象方法不需要使用abstract关键字,因为在接口中不会存在方法体。

        总结一下:在Java 8之前,interface可以创建一个完全抽象的类,不代表任何实现。接口仅负责描述,它确定方法名、方法体和返回类型,但不提供方法体。接口只提供一种形式,而使用了接口的代码会知道可以为接口调用哪些方法。

        Java 8开始,接口中允许默认方法和静态方法。此时接口的基本概念依旧成立,即接口是一个类型的概念,而非实现。

        接口与抽象类的区别:

  • 接口通常暗示“类的类型”或作为形容词使用。
  • 抽象类通常是类层次结构的一部分。

    接口的方法默认是public的,而不是包访问权限。

        接口可以包含字段,这些字段是隐式的staticfinal

        使用implement关键字,可以创建一个符合特定接口(或一组接口)的类。例如:

interface Concept { // 类前没有使用public修饰,是包访问权限
    void idea1();
    void idea2();
}

class ImplementingAnInterface implements Concept {
    @Override
    public void idea1() {
        System.out.println("idea1");
    }

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

        注意:当实现一个接口时,来自接口的方法必须被定义为public。因为Java编译器不会允许将接口方法的访问权限设为包访问权限,这会降低继承期间方法的可访问性。

默认方法

        Java 8之后,default关键字有了一个额外的用途:在接口中,default会允许方法创建一个方法体。实现了该接口的类,可以不定义default修饰的方法而直接使用方法。

interface InterfaceWithDefault {
    void firstMethod();

    void secondMethod();

    default void defaultMethod() {
        System.out.println("这是一个由default修饰的方法");
    }
}

        只要实现上述接口的firstMethod方法和secondMethod方法,就可以使用这个接口了:

// 和InterfaceWithDefault.java处于同一文件夹中

public class Implementation implements InterfaceWithDefault {
    @Override
    public void firstMethod() {
        System.out.println("方法一");
    }

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

    public static void main(String[] args) {
        InterfaceWithDefault i = new Implementation();

        i.firstMethod();
        i.secondMethod();
        i.defaultMethod();
    }
}

        程序执行的结果是:

        之所以添加默认方法,原因之一是:这允许向现有接口中添加方法,而不会破坏已经在使用该接口的所有方法(默认方法也称防御方法或虚拟扩展方法)

    JDK 9中,接口的defaultstatic方法都可以是private的。


多重继承

        多重继承,即一个类可以从多个基类型继承特性和功能。但Java在严格意义上是一种单继承语言:Java只允许继承一个类(或抽象类)。在默认方法出现之后,Java才拥有了一些多重继承的特性。

        现在,我们可以通过把接口和默认方法结合起来,来结合多个基类型的行为

        但Java只允许结合“行为”,换句话说,接口中不允许存在字段(除非是静态字段)。字段依旧只能来自单个基类或抽象类,所以我们无法获得状态的多重继承。例如:

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

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

interface Three {
    default void third() {
        System.out.println("方法Three.third()");
    }
}

class MI implements One, Two, Three { // 结合了多个接口
}

public class MultipleInheritance {
    public static void main(String[] args) {
        MI mi = new MI();
        mi.first();
        mi.second();
        mi.third();
    }
}

        程序执行的结果如下:

        只要所有基类方法都有不同名称和参数列表,就可以组合多个来源。否则,编译器就会报错:

interface Bob1 {
    default void bob() {
        System.out.println("Bob1::bob");
    }
}

interface Bob2 {
    default void bob() {
        System.out.println("Bob2::bob");
    }
}

// class Bob implements Bob1, Bob2 { // 不可以,会发生报错
// }

interface Sam1 {
    default void sam() {
        System.out.println("Sam1::sma");
    }
}

interface Sam2 {
    default void sam(int i) {
        System.out.println("Sam2::sma = " + i * 2);

    }
}

class Sam implements Sam1, Sam2 { // 可以,因为方法的参数列表不同
}

interface Max1 {
    default void max() {
        System.out.println("Max1::max");
    }
}

interface Max2 {
    default int max() {
        System.out.println("Max2::max");
        return 1;
    }
}

// class Max implements Max1, Max2 { // 不可以,参数列表不足以区分方法
// }

        编译器会通过方法签名来区分不同的方法,因为方法签名具有唯一性:签名包括名称和参数类型。但是,返回类型不是方法签名的一部分

        如果发生如上注释中的冲突,就需要通过重写冲突的方法来解决问题:

interface Coco1 {
    default void coco() {
        System.out.println("Coco1::coco");
    }
}

interface Coco2 {
    default void coco() {
        System.out.println("Coco2::coco");
    }
}

public class Coco implements Coco1, Coco2 {
    @Override
    public void coco() { // 重写存在冲突的方法
        Coco2.super.coco();
    }

    public static void main(String[] args) {
        new Coco().coco();
    }
}

        上述程序最终会输出:Coco2::coco 。在Coco类中进行重写方法时,通过super关键字选择了基类Coco2进行实现。除此之外,也可以通过其他任何可行的方式进行实现。


接口中的静态方法

        Java 8还允许接口包含静态方法。这种设计使得我们可以将逻辑上属于接口的方法赋予接口本身。通常,会将用来操作接口的方法,以及通用工具放入接口中:

public interface Operation {
    void execute();

    static void runOps(Operation... ops) { // 用来操作接口
        for (Operation op : ops)
            op.execute();
    }

    static void show(String msg) { // 通用方法
        System.out.println(msg);
    }
}

    其中,runOps()是一个模板方法设计模式的例子。

        借由runOps()这个方法,下面展示的是创建Operation的不同方法:

import operation.Operation;

class Heat implements Operation {
    @Override
    public void execute() {
        Operation.show("Heat");
    }
}

public class MetaWork {
    public static void main(String[] args) {
        Operation twist = new Operation() {
            public void execute() { // 在使用前,必须在静态上下文中对方法进行定义
                Operation.show("Twist");
            }
        };

        Operation.runOps(
                new Heat(), // 【1】:按常规方式创建
                new Operation() { // 【2】:匿名类
                    public void execute() {
                        Operation.show("Hammer");
                    }
                },
                twist::execute, // 使用方法引用
                () -> Operation.show("Anneal")); // Lambda表达式,需要最少的代码
    }
}

        总结上述程序,可以得出各种创建Operation的不同方式:

  1. 常规类Heat
  2. 匿名类
  3. 方法引用
  4. Lambda表达式,需要最少的代码

作为接口的Instrument

        使用接口更新关于乐器的Instrument

        接口一经实现,这个实现就会变成一个可以用常规方式扩展的普通类。接口中,任何方法的默认权限都是public的。Instrument中的play()adjust()都使用default关键字定义。

package music5;

import music.Note;

interface Instrument {
    int VALUR = 5; // 默认是static并且final的,即编译时常量

    default void play(Note n) { // 默认权限是public的
        System.out.println(this + ".play()" + n);
    }

    default void adjust() {
        System.out.println("调整:" + this);
    }
}

class Wind implements Instrument {
    @Override
    public String toString() {
        return "Wind";
    }
}

class Percussion implements Instrument {
    @Override
    public String toString() {
        return "Percussion";
    }
}

class Stringed implements Instrument {
    @Override
    public String toString() {
        return "Stringed";
    }
}

class Brass extends Wind {
    @Override
    public String toString() {
        return "Brass";
    }
}

class Woodwind extends Wind {
    @Override
    public String toString() {
        return "Woodwind";
    }
}

public class Music5 {
    static void tune(Instrument i) {
        // ...
        i.play(Note.MIDDLE_C);
    }

    static void tuneAll(Instrument[] e) {
        for (Instrument i : e)
            tune(i);
    }

    public static void main(String[] args) {
        Instrument[] orchestra = {
                new Wind(),
                new Percussion(),
                new Stringed(),
                new Brass(),
                new Woodwind()
        };
        tuneAll(orchestra);
    }
}

        程序执行的结果是:

        上述程序中,使用根类Object的方法toString()替代了what()方法。

    无论是向上转型为常规类、抽象类或是接口,tune()方法的行为都是一样的。实际上tune()也无从得知Instrument到底是什么类。

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

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

相关文章

华为云云耀云服务器L实例评测|华为云云耀云服务器L实例使用教学+宝塔建站 — 运行Python脚本(保姆级)

目录 文章目录 目录前言一、创建云耀云服务器L实例1、打开购买页面2、找到系统镜像3、进入系统控制台4、重置服务器密码 二、安装宝塔面板1.打开在线安装工具2.复制公网IP3.完成在线安装4.安装完成(记住账密信息)五.开放安全组 三、使用服务器总结 前言 …

Linux系统编程(一):文件 I/O

参考引用 UNIX 环境高级编程 (第3版)黑马程序员-Linux 系统编程 1. UNIX 基础知识 1.1 UNIX 体系结构(下图所示) 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境,通常…

4.linux的RPM和YUM

一、RPM 1.rpm包的管理 1.1介绍 Linux互联网下载包,类似于windows的setup.exe 1.2rpm简单查询已安装的rpm rpm -qa | grep xxx 当前linux有没有安装火狐 rpm -qa | grep fox 1.3rpm包的格式 一个 rpm 包名:firefox-45.0.1-1.el6.centos.x86_64.…

学习记忆——方法篇——连锁拍照、情景故事和逻辑故事法

三大方法速记这些内容 1、连锁拍照法速记重要事件 2、情景故事速记速记购物信息 3、逻辑故事法速记客户档案 一、连锁拍照法速记重要事件 例:女朋友在出差之前嘱咐男朋友几件事 1、把房间收拾干净,最重要的是要把书架整理了,垃圾倒了 2、记…

软件设计师_计算机组成与体系结构

计算机组成与体系结构 文章目录 1.1 数据的表示1.1.1 进制的转换1.1.2 原码 反码 补码 移码1.1.3 浮点数运算 1.2 计算机结构1.3 Flynn分类法1.4 CISC和RISC1.5 流水线技术1.6 存储系统1.7 总线系统1.8 可靠性1.9 校验码 1.1 数据的表示 1.1.1 进制的转换 R进制转十进制 --&g…

Linux:keepalived + ipvsadm

介绍 Linux:keepalived 双热备份(基础备份web)_鲍海超-GNUBHCkalitarro的博客-CSDN博客https://blog.csdn.net/w14768855/article/details/132815057?spm1001.2014.3001.5501 环境 一台 centos7 keepalived ipvsadm (主…

技术架构图是什么?和业务架构图的区别是什么?

技术架构图是什么? ​技术架构图是一种图形化工具,用于呈现软件、系统或应用程序的技术层面设计和结构。它展示了系统的各种技术组件、模块、服务以及它们之间的关系和交互方式。技术架构图关注系统内部的技术实现细节,以及各个技术组件之…

SpringBoot项目--电脑商城【显示勾选的购物车数据】

1.持久层[Mapper] 1.规划SQL语句 用户在购物车列表页中通过随机勾选相关的商品,在点击“结算”按钮后,跳转到结算页面,在这个页面中需要展示用户在上个页面所勾选的购物车对应的数据,列表的展示,而展示的内容还是在于…

OpenCV学习笔记(6)_由例程学习高斯图像金字塔和拉普拉斯金字塔

1 图像金字塔 图像金字塔是图像多尺度表达的一种。 尺度,顾名思义,可以理解为图像的尺寸和分辨率。处理图像时,经常对源图像的尺寸进行缩放变换,进而变换为适合我们后续处理的大小的目标图像。这个对尺寸进行放大缩小的变换过程…

Python的get请求报错Error: Unexpected status code 400

一句话导读: 最近在做研发效能提升的事情,其中有一块就是要对项目管理相关数据做统计,我们使用的是ones做的项目管理,ones本身带的那些报表满足不了我们的需求,就想着看这些数据是不是能自己拿出来做统计,有…

代码随想录--数组--长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。 示例: 输入:s 7, nums [2,3,1,2,4,3]输出:2…

华为3面已过,面议薪资要价10K,面试官说我不尊重华为?

在不知道一个公司的普遍薪资水平的时候,很多面试者不敢盲目的开价,但就因为这样可能使得面试官怀疑你的能力。一位网友就在网上诉说了自己的经历,男子是一位测试员,已经有九年的工作经历了,能力自己觉得还不错。 因为…

2002-2020年地级市各类制造业企业进入数量数据

2002-2020年地级市各类制造业企业进入数量数据 1、时间:2002-2020年 2、指标:地区、年份、城市代码、所属省份、省份代码、高技术行业企业数量、中高技术行业企业数量、中低技术行业企业数量、低技术行业企业数量 3、样本量:1万多条 4、来…

mac使用squidMan设置代理服务器

1,下载squidMan http://squidman.net/squidman/ 2, 配置SquidMan->Preference 3, mac命令窗口配置 export http_proxy export https_porxy 4,客户端配置(centos虚拟机) export http_proxyhttp://服务器ip:8080 export https…

肖sir__mysql之单表__004

mysql之单表 一、建表语句 1、show databases 查看所有的数据库 2、create databaes 数据库名 创建数据库 3、use 数据库名 指定使用数据库 4、show tables ; 5、创建表 格式:create table 表名 (字段名1 数据类型1(字符长度),字段名2 数据类型2(字…

【鸿蒙(HarmonyOS)】UI开发的两种范式:ArkTS、JS(以登录界面开发为例进行对比)

文章目录 一、引言1、开发环境2、整体架构图 二、认识ArkUI1、基本概念2、开发范式(附:案例)(1)ArkTS(2)JS 三、附件 一、引言 1、开发环境 之后关于HarmonyOS技术的分享,将会持续使…

学习记忆——宫殿篇——记忆宫殿——数字编码——记忆数字知识点

面对错综复杂的数字信息,我们想要记住可以通过以下三点: 1、首先找到关键词 2、数字编码牢记 3、关键词跟编码链接 案例:会计考试-时间期限为 3、7、10 日、1 年的知识点 3 天 (1)托收承付的承付期验单付款为 3 天。 (2)失票人应当在通…

考研英语笔记:经济学人特别一篇,看到此,流口水

文 / 谷雨 考研是一件非常痛苦的事情。 你可能在一个漫长时间当中,看不到任何回报。但是成功和失败之间就在于你是否能够坚持走完这段路。 无数备考的朋友,无不走过这段路。 很多人可能复习了好几个月,上考场之后还是发现没有一点进步&#x…

嘉泰实业:真实低门槛,安全有保障

在互联网金融大行其道的当下,无论用户是多么的青睐、喜爱这种便捷的理财方式,也一定得把资金安全放在心上。要投就投那些实力背景雄厚,诚信经营的平台,可以选择投资用户基数庞大的理财老品牌,也可以选择发展势头迅猛的…

学习记忆——宫殿篇——记忆宫殿——数字编码——三十六计

案例:中国古代兵书《三十六计》 第1计 瞒天过海 第2计 围魏救赵 第3计 借刀杀人 第4计 以逸待劳 第5计 趁火打劫 第6计 声东击西 第7计 无中生有 第8计 暗渡陈仓 第9计 隔岸观火 第10计 笑里藏刀 我们可以这样记忆: 一、先熟悉1-10的编码:…