当前位置: 首页 > news >正文

Java学习之旅第二季-16:接口

16.1 接口概述

接口是声明了一系列抽象方法的抽象数据类型,其设计旨在支持在运行时进行动态方法解析。通常情况下,要使一个方法能够从一个类中访问到另一个类,那么这两个类在编译时就必须都存在,以便 Java 编译器能够进行检查,确保方法的声明是正确的、兼容的。这一要求本身就会导致类的结构变得僵化且无法扩展。

接口的设计就是为了避免这种问题。它们将方法的定义与继承层次结构分离开来。由于接口处于与类不同的层次结构中,因此即使在类层次结构上毫无关联的类也可以实现相同的接口,这就是接口真正威力的体现所在。

在实际开发中,接口主要用于以下几点:

  • 约定调用方式,实现解耦:在复杂的项目中,通常需要进行分层设计,层之间的调用采用针对接口编程就是最佳实践
  • 表示某种能力:这种JDK的API中有很多例子,最典型的莫过于序列化接口了(java.io.Serializable)

从感官上接口可以认为是纯粹的抽象类,它没有实例变量、构造方法、代码块;在Java 8之前,接口只能只包含静态常量及抽象方法,但随着 Java 版本的更新,接口中允许更多形式的成员。

16.2 接口声明

声明接口使用 interface 关键字,语法形式如下:

<修饰符> [abstract] interface 接口名{常量属性声明方法声明嵌套类型声明
}

语法说明:

  • 接口的命名规范与类的命名规范相同,采用 Pascal 命名法
  • abstract关键字是可省的,默认就是抽象的,所以也不能使用 final 修饰接口
  • 修饰符中如果是访问控制修饰符,则与类声明时可用的修饰符一致,只能是 public 或默认
  • 接口中的成员包括:常量属性(final修饰),方法(抽象方法,默认方法,静态方法,私有方法),嵌套类型(内部类/接口等)

接口声明示例如下:

public interface Interface1 {
}

上面的接口中没有声明任何成员,也没有从父接口中继承成员,这种接口可以称为标记接口,主要作用是:

  • 统一子类的类型,从类型层次上看,所有实现了接口的子类,其类型都是该接口类型
  • 用于标记子类在特定的场景中具有特定的意义(能力)。JDK中比较常见的标记接口包括:java.lang.Cloneable,java.io.Serializable,java.rmi.Remote。不过Java 5中引入的注解可以实现相同的功能。

16.3 接口成员

常量属性

在接口中所有的属性都是 public static final 修饰的常量,就算声明时没有显式使用这几个关键字。如:

public interface Interface1 {int num = 0;public static final String name = "";
}

上述成员中声明的 num,修饰符显式写出来与下面的name声明是相同的。

这里有几个注意点:

  • 在 Java 中常量的命名规范是全部字母大写,多个单词则使用下划线分隔,所以应该以NUM、NAME命名
  • 由于属性是final修饰的,所以必须赋初始值,且接口中没有其他成员,只能在声明的同时赋初始值
  • 接口中的成员是static的,访问时使用接口名加点运算符即可,但要注意接口的修饰符可能是包访问级别的,所以public常量有可能访问不到

这种在接口中声明常量的做法之前非常流行,但是随着 Java 5 引入了枚举,推荐使用枚举保存一组相关的值

抽象方法

接口中声明的方法大部分都是抽象的,abstract可省,其默认是public的。在 Java 8之前只有这一种方法。它们是留给子类重写实现的。

public interface Interface1 {/*** 计算两个整数之和** @param num1 第一个整数* @param num2 第二个整数* @return 两个整数之和*/public abstract int add(int num1, int num2);/*** 判断一个整数是否是偶数** @param num 整数* @return 如果给定的整数是偶数,返回true;否则返回false*/boolean isEven(int num);
}

对于上述的两个方法,第一个方法前的修饰符是全的,第二个方法前没有修饰符,但是这两个方法是一样的声明,都是public abstract 修饰,在IDEA中会有提示说它们是多余的且颜色显示为灰色。所以开发中基本都采用第二种较为简单的写法。

静态方法

接口中的静态方法是 Java 8 增加的语法,使用 static 关键字修饰方法,默认是 public 修饰,也只能是 public 修饰。

public interface Interface1 {/*** 将一个整数翻倍** @param num 整数* @return 翻倍后的结果*/static int doubleNum(int num) {return num * 2;}
}

可以在静态方法中实现一些处理逻辑,它不能使用 abstract 或 final 修饰,且该方法不会被实现类或子接口继承。

访问此方法使用接口名加点运算符即可。

默认方法

默认方法也是 Java 8 新增,使用 default 关键字修饰方法,默认是 public 修饰,也只能是 public 修饰。

/*** 将一个整数减半** @param num 整数* @return 减半后的结果,取整*/
default int halfNum(int num) {return num / 2;
}

默认方法对所有实现了该接口的类都提供了默认实现,保证了向后兼容,在子类的实例中可以访问到接口中的默认方法。

不能使用 abstract、final 和 static 修饰。

在子类中可以重写默认方法,子类使用"接口名.super.方法名"访问接口中的默认方法。

默认方法不能与Object中的方法相同,如:不能使用 toString( )。

私有方法

私有方法是 Java 9 新增的,使用 private 修饰的非抽象方法,但可以是静态方法。

/*** 判断一个整数是否是奇数** @param num 整数* @return 如果给定的整数是奇数,返回true;否则返回false*/
private boolean isOdd(int num) {return num % 2 != 0;
}

私有方法顾名思义只能在本接口被访问,静态私有方法可以被本接口中的默认方法及静态方法访问,非静态私有方法则只能被本接口中的默认方法访问。可以减少本接口多个方法中的重复代码。

lc修饰

l可以重写默认方法,子类使用"接口名.super.方法名"访问接口中的默认方法

l不能与Object中的方法相同

16.4 函数式接口

函数式接口(Functional Interface)是 Java 8 新增的语法,与Lambda表达式及Stream API关系密切,本小节只关注其语法特征。

从语法上讲函数式接口是指只有一个抽象方法的接口;接口声明时可以使用可选的@FunctionalInterface注解来表示该接口是否是一个合法的函数式接口,与@Override类似,它并不是必须的,就算没有@FunctionalInterface注解,只有声明是合法则不会有任何语法问题。比如下面的接口就是函数式接口:

@FunctionalInterface
public interface Interface2 {void method1();
}

就算其中的成员有多个,但是有且只有一个抽象方法才是唯一判断标准,如下面的接口也是函数式接口:

@FunctionalInterface
public interface Interface2 {int num = 0;void method1();default void method2() {}static void method3() {}private void method4() {}
}

但是这个不算:

public interface Interface2 {
}

或者有多个抽象方法的也不算函数式接口:

public interface Interface2 {void method1();void method1(int num);
}

另外接口是可以被继承的,所以在继承的接口中方法可能有父接口中的抽象方法,那么算下来如果超过一个也不能算是函数式接口。

16.5 接口的使用

声明完接口之后,如何使用接口有两种不同的场景,第一种是继承接口,第二种是实现接口。至于将接口作为数据类型使用,我们后续再介绍。

继承接口

接口可以被接口继承,在声明子接口时同样使用 extends 关键字继承一个或多个接口,多个接口之间使用逗号分隔。语法如下:

interface 接口名 extends [接口1 [,接口2]]{}

可以被继承成员包括:常量属性,抽象方法和默认方法;其他的则不可被继承,包括:静态方法及私有方法。示例:

public interface ParentInterface1 {void method1();
}
public interface ParentInterface2 {void method2();
}
public interface ChildInterface extends ParentInterface1, ParentInterface2 {
}

上述的 ChildInterface 接口就同时继承了两个接口,也同时拥有了两个不同的抽象方法,在它的具体子类实现该接口时,要同时实现这两个方法。

实现接口

抽象类和具体类都可以使用 implements 关键字实现一个或多个接口,多个接口之间使用逗号分隔。语法如下:

[abstract] class 类名 implements [接口1[,接口2…]]{}

实现了接口的具体类需要重写接口中所有的抽象方法:

public class Class1 implements ParentInterface1,ParentInterface2{@Overridepublic void method1() {}@Overridepublic void method2() {}
}

实现了接口的抽象类可以重写接口中的部分抽象方法,也可以不重写任何抽象方法。

public abstract class Class2 implements ParentInterface1, ParentInterface2 {@Overridepublic void method1() {}
}

那么抽象子类的具体子类就要实现抽象方法了。

上面两种场景下,有以下的特殊情况

1、多个父接口中有完全相同的方法声明:子接口/子类就认一个,无所谓从哪里继承的

2、多个父接口中有相同的属性,比如都声明了属性 name:这在声明子接口/子类时没有什么问题,但是在使用子接口/子类加点运算符访问该属性时会出现问题,因为不知道 name 属性从哪个父接口继承而来。IDEA中提示如下:

Reference to 'name' is ambiguous, both 'ParentInterface1.name' and 'ParentInterface2.name' match

3、多个父接口中有相同的默认方法,则子接口或子类必须重写该方法,否则会有语法错误。

4、当多个接口有相同方法名但不同参数的默认方法时,按照方法重载的规则调用

5、当多个接口有除返回值之外都完全相同的方法声明时,不允许同时实现多个接口。如下面两个接口就不能被同时继承或实现:

public interface ParentInterface1 {void method1();
}
public interface ParentInterface2 {int method1();
}

16.6 混合使用类和接口

经过前面的语法介绍,我们知道接口不能使用 extends 继承类,类不能使用 extends 继承接口。但是类可以同时继承一个类和实现若干接口,示例如下:

public interface ParentInterface3 {void method1();
}
public interface ParentInterface4 {void method2();
}
public class ParentClass {}
public class Child extends ParentClass implements ParentInterface3, ParentInterface4 {@Overridepublic void method1() {}@Overridepublic void method2() {}
}

这种语法中 extends 在前,且只能继承一个类,implements在后,可以任意实现接口。

如果父类已经实现了某接口的方法,则子类可以不用重写该方法。

当父类与接口中有相同的方法时,父类中的方法优先。

16.7 小结

本小节详细介绍了Java接口的概念、特性及使用方法。接口作为一种抽象数据类型,通过声明抽象方法支持动态方法解析,有效解决类层次结构僵化问题。文章阐述了接口的声明语法、成员类型(包括常量属性、抽象方法、静态方法、默认方法和私有方法)。最后介绍了接口的两种使用方式:接口继承(extends)和类实现(implements),并说明具体类和抽象类在实现接口时的不同要求。全文系统性地梳理了Java接口的核心知识点,为理解面向对象编程中的接口机制提供了完整参考。

http://www.dtcms.com/a/465028.html

相关文章:

  • 147、【OS】【Nuttx】【周边】效果呈现方案解析:$PATH 隔离
  • 前端笔试复盘 | 知识点总结
  • 哪个地区的网站建设最好免费发群二维码的网站
  • GitHub 热榜项目 - 日榜(2025-10-10)
  • MySQL聚合查询的进阶技巧用WITHROLLUP实现多维度数据汇总分析
  • 用 PyQt5 + FFmpeg 打造批量视频音频提取器
  • 华为 Mate80 要来了,或搭载最新麒麟芯片
  • Frida辅助分析OLLVM虚假控制流程(下)
  • MySQL(二) - 数据表管理
  • 商丘网站建设大全网站改版 大量旧页面
  • 简单网站编写
  • 用AI写的【实时文件搜索引擎】python源码【找资源】
  • 无为建设局网站深圳东门解封了吗
  • 【Linux】入门指南:基础指令详解Part Two
  • 如何下载 Git 仓库中的所有分支到本地并实现Python自动化操作
  • 掌握前后端数据交互的3种核心方式
  • 网络、主机安全扫描工具
  • 论文笔记 -《MUON IS SCALABLE FOR LLM TRAINING》
  • soular入门到实战(6) - soular+kanass+sward+postin实现sso单点登录
  • 建设农垦网站赣州网站制作
  • vue3 el-date-picker 日期选择器校验规则-选择日期范围不能超过七天
  • 【论文阅读】Debating with More Persuasive LLMs Leads to More Truthful Answers
  • Windows系统中部署GeoServer全流程
  • 成都专做婚介网站的公司温州做外贸网站
  • OpenWrt开发第13篇:OpenWrt上读取带USB接口的GPS设备信息
  • 公司如何建设网站首页品牌建设网站公司排名
  • MySQL的查询操作语法要点
  • 打工人日报#20251010
  • ARMv8系统的安全性(二):TrustZone架构如何重塑移动设备安全生态
  • Vivado 时序约束的完整作战地图(二)