初识Java 8-1 接口和抽象类
目录
抽象类和抽象方法
接口定义
默认方法
多重继承
接口中的静态方法
作为接口的Instrument
本笔记参考自: 《On Java 中文版》
接口和抽象类提供了一种更加结构化的方式分离接口和实现。
抽象类和抽象方法
抽象类,其介于普通类和接口之间。在构建具有字段而未实现方法的类时,抽象类是重要且必要的工具。以Instrument类为例:
package music;public class Instrument {public void play(Note n) {}
}
由这个类可以衍生出很多子类:Wind、Brass等。在这里,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 {@Overridevoid f() {System.out.println("f()");}@Overridevoid 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 {@Overridepublic void play(Note n) {System.out.println("Wind.play(): " + n);}@Overridepublic String what() {return "Wind";}@Overridepublic void adjust() {System.out.println("对Wind进行调整");}
}class Percussion extends Instrument {@Overridepublic void play(Note n) {System.out.println("Percussion.play(): " + n);}@Overridepublic String what() {return "Percussion";}@Overridepublic void adjust() {System.out.println("对Percussion进行调整");}
}class Stringed extends Instrument {@Overridepublic void play(Note n) {System.out.println("Stringed.play(): " + n);}@Overridepublic String what() {return "Stringed";}@Overridepublic void adjust() {System.out.println("对Stringed进行调整");}
}class Brass extends Wind {@Overridepublic void play(Note n) {System.out.println("Brass.play(): " + n);}@Overridepublic void adjust() {System.out.println("对Brass进行调整");}
}class Woodwind extends Wind {@Overridepublic void play(Note n) {System.out.println("Woodwind.play(): " + n);}@Overridepublic 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的,而不是包访问权限。
接口可以包含字段,这些字段是隐式的static和final。
使用implement关键字,可以创建一个符合特定接口(或一组接口)的类。例如:
interface Concept { // 类前没有使用public修饰,是包访问权限void idea1();void idea2();
}class ImplementingAnInterface implements Concept {@Overridepublic void idea1() {System.out.println("idea1");}@Overridepublic 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 {@Overridepublic void firstMethod() {System.out.println("方法一");}@Overridepublic void secondMethod() {System.out.println("方法二");}public static void main(String[] args) {InterfaceWithDefault i = new Implementation();i.firstMethod();i.secondMethod();i.defaultMethod();}
}
程序执行的结果是:
![]()
之所以添加默认方法,原因之一是:这允许向现有接口中添加方法,而不会破坏已经在使用该接口的所有方法(默认方法也称防御方法或虚拟扩展方法)。
JDK 9中,接口的default和static方法都可以是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 {@Overridepublic 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 {@Overridepublic 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的不同方式:
- 常规类Heat
- 匿名类
- 方法引用
- 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 {@Overridepublic String toString() {return "Wind";}
}class Percussion implements Instrument {@Overridepublic String toString() {return "Percussion";}
}class Stringed implements Instrument {@Overridepublic String toString() {return "Stringed";}
}class Brass extends Wind {@Overridepublic String toString() {return "Brass";}
}class Woodwind extends Wind {@Overridepublic 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到底是什么类。
相关文章:
初识Java 8-1 接口和抽象类
目录 抽象类和抽象方法 接口定义 默认方法 多重继承 接口中的静态方法 作为接口的Instrument 本笔记参考自: 《On Java 中文版》 接口和抽象类提供了一种更加结构化的方式分离接口和实现。 抽象类和抽象方法 抽象类,其介于普通类和接口之间。在构…...
微信小程序音频后台播放功能
微信小程序在手机息屏后依旧能播放音频,需要使用 wx.getBackgroundAudioManager() 方法创建后台音乐播放器,并将音乐播放任务交给这个后台播放器。 具体实现步骤如下: 小程序页面中,使用 wx.getBackgroundAudioManager() 方法创…...
NotePad——xml格式化插件xml tools在线安装+离线安装
在使用NotePad时,在某些情形下,需要格式化Xml格式内容,可以使用Xml Tools插件。 一、在线安装 1. 打开Notepad 软件 2. 选择插件,选择“插件管理” 3. 搜索 XML Tools,找到该插件后,勾选该文件ÿ…...
图书管理系统 数据结构先导课暨C语言大作业复习 | JorbanS
问题描述 读取给定的图书文件book.txt中的信息(book.txt中部分图书信息如下图所示),完成一个图书信息管理系统,该系统的各个功能模块要求利用菜单选项进行选择。 系统功能要求 图书浏览 读取book.txt中的文件信息并依次输出所…...
python 爬虫的开发环境配置
1、新建一个python项目 2、在控制台中分别安装下面三个包 pip install requests pip install beautifulsoup4 pip install selenium/ 如果安装时报以下错误: raise ReadTimeoutError(self._pool, None, "Read timed out.") pip._vendor.urllib3.exceptio…...
技术架构图是什么?和业务架构图的区别是什么?
技术架构图是什么? 技术架构图是一种图形化工具,用于呈现软件、系统或应用程序的技术层面设计和结构。它展示了系统的各种技术组件、模块、服务以及它们之间的关系和交互方式。技术架构图关注系统内部的技术实现细节,以及各个技术组件之…...
数据增强
一、数据增强 当你训练一个机器学习模型时,你实际做工作的是调参,以便将特定的输入(一副图像)映像到输出(标签)。我们优化的目标是使模型的损失最小化, 以正确的方式调节优化参数即可实现这一目…...
【Unity】2D 对话模块的实现
对话模块主要参考 【Unity教程】剧情对话系统 实现。 在这次模块的构建将基于 unity ui 组件 和 C#代码实现一个从excel 文件中按照相应规则读取数据并展示的逻辑。这套代码不仅能实现正常的对话,也实现了对话中可以通过选择不同选项达到不同效果的分支对话功能。 …...
laravel安装初步使用学习 composer安装
一、什么是laravel框架 Laravel框架可以开发各种不同类型的项目,内容管理系统(Content Management System,CMS)是一种比较典型的项目,常见的网站类型(如门户、新闻、博客、文章等)都可以利用CM…...
【VS插件】VS code上的Remote - SSH
【VS插件】VS code上的Remote - SSH 目录 【VS插件】VS code上的Remote - SSH获得Linux服务器或者Linux系统的IP地址下载插件远程登录注意如果Linux虚拟机系统无法连接成功可能是没有开启ssh服务优势 作者:爱写代码的刚子 时间:2023.9.12 前言࿱…...
TensorFlow 02(张量)
一、张量 张量Tensor 张量是一个多维数组。与NumPy ndarray对象类似,tf.Tensor对象也具有数据类型和形状。如下图所示: 此外,tf.Tensors可以保留在GPU中。TensorFlow提供了丰富的操作库 (tf.add,tf.matmul,tf.linalg.inv等),它们…...
513. 找树左下角的值
代码链接: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 思路: 万金油层次遍历,保存每一层的第一个元素返回就行了 我的代码: /*** Definition for a binary tree node.* struct TreeNode {* …...
量化:基于支持向量机的择时策略
文章目录 参考机器学习简介策略简介SVM简介整体流程收集数据准备数据建立模型训练模型测试模型调节参数 参考 Python机器学习算法与量化交易 利用机器学习模型,构建量化择时策略 机器学习简介 机器学习理论主要是设计和分析一些让计算机可以自动“学习”的算法。…...
成功解决Selenium 中116版本的chromedriver找不到问题
Selenium 中的Google(谷歌浏览器)最新版本chromedriver 文章目录 Selenium 中的Google(谷歌浏览器)最新版本chromedriver1.当前作者的谷歌浏览器版本2.当前驱动官网的最新版本3.当不想降低浏览器版本继续使用谷歌浏览器的办法 1.当…...
PYQT常用组件--方法汇总
QTimeEdit timeEdit是Qt框架中的一个时间编辑器控件,它提供了以下常用方法: setTime(QTime time): 设置时间编辑器的时间为指定的QTime对象。time(): 返回时间编辑器的当前时间,返回一个QTime对象。setDateTime(QDateTime dateTime): 设置时…...
Linux系统编程(一):文件 I/O
参考引用 UNIX 环境高级编程 (第3版)黑马程序员-Linux 系统编程 1. UNIX 基础知识 1.1 UNIX 体系结构(下图所示) 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境,通常…...
OSM+three.js打造3D城市
对于我在 Howest 的研究项目,我决定构建一个 3D 版本的 Lucas Bebber 的“交互式讲故事的动画地图路径”项目。我将使用 OSM 中的矢量轮廓来挤出建筑物的形状并将它们添加到 3js 场景中,随后我将对其进行动画处理。 一、开发环境 为了使用 Node 和 npm 包,我选择使用 Vite…...
02JVM_垃圾回收GC
二、垃圾回收GC 在堆里面存放着java的所有对象实例,当对象为“死去”,也就是不再使用的对象,就会进行垃圾回收GC 1.如何判断对象可以回收 1.1引用计数器 介绍 在对象中添加一个引用计数器,当一个对象被其他变量引用时这个对象…...
ARM Linux DIY(八)USB 调试
前言 V3s 带有一个 USB 接口,将其设置为 HOST 或 OTG 模式,这样可以用来接入键盘、鼠标等 USB 外设。 USB 简介 USB 有两种设备:HOST 和 USB 功能设备。 在 USB2.0 中又引入了一个新的概念 OTG,即设备角色可以动态切换。 切换方…...
编程小白的自学笔记十四(python办公自动化创建、复制、移动文件和文件夹)
系列文章目录 编程小白的自学笔记十三(python办公自动化读写文件) 编程小白的自学笔记十二(python爬虫入门四Selenium的使用实例二) 编程小白的自学笔记十一(python爬虫入门三Selenium的使用实例详解) …...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
