文章目录
- 一、概念
- (1)引入
- (2)类比
- (3)举例
- 1. 举例1
- 2. 举例2
- 3. 举例3
- 4. 举例4
- (4) 定义格式及重点举例
- 1. 接口的声明格式
- 2. 接口的成员说明
- 3. 接口内部结构的说明
- 4. 举例
- 4.1 举例1--接口
- 4.2 举例2--类实现接口
- 4.3 举例3--接口与接口
- 4.4 举例4--接口与多态性
- 4.4.1 案例1--虚方法调用
- 4.4.2 案例2--创建接口实现类的对象
- 4.4.3 案例3--创建接口实现类的匿名对象
- 4.4.4 案例4--创建接口匿名实现类的对象
- 4.4.5 案例5--创建接口匿名实现类的匿名对象
- 5 . 总结
- 二、 接口的使用规则
- (1)类实现接口(implements)
- (2)接口的多实现(implements)
- (3)接口的多继承(extends)
- (4)接口与实现类对象构成多态引用
- (5)使用接口的静态成员
- (6)使用接口的非静态方法
- 三、JDK8与JDK9中接口新特性
- (1)说明
- (2)举例
- 1. 案例1--JDK8 静态方法
- 2. 案例2--JDK8 默认方法
- 2.1 默认方法
- 2.2 接口冲突
- 2.3 类优先原则
- 2.4 在实现类中调用接口中被重写的方法
- 3. 案例3--JDK9 定义私有方法
- (3)整体代码
- 四、JDK8中相关冲突问题
- (1) 默认方法冲突问题
- 1. 类优先原则
- 2. 接口冲突(左右为难)
- (2) 常量冲突问题
- 五、接口的总结与面试题
- (1)总结
- (2)面试题
- 六、接口与抽象类之间的对比
- 七、 练习
- (1)练习1
- (2)练习2
- (3)练习3
一、概念
(1)引入
从三条主线的角度来说的话,这儿属于第三条主线,叫做interface
关键字的使用。
从另外一个角度来说的话,它是跟类并列的一个结构,这样讲的话,它的地位一下子就提升了。
可以看一下API
。
比如说就看一下这个long包下的base。这些结构里边,我们再来看一下这个long包:
可以看到Interface和Class是并列的结构:
平常新建java文件的时候,也可以看到类与接口:
那这个接口到底是一个什么样的结构?跟类并列的是吧?前面我们讲的面向对象特征说类里边都能声明什么,其实都是类的,这个体系跟接口没关系。
(2)类比
生活中大家每天都在用USB接口,那么USB接口与我们今天要学习的接口有什么相同点呢?
USB,(Universal Serial Bus,通用串行总线)是Intel公司开发的总线架构,使得在计算机上添加串行设备(鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等)非常容易。
其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范
的一种具体设备而已。
只要设备遵循USB规范的,那么就可以与电脑互联,并正常通信。至于这个设备、电脑是哪个厂家制造的,内部是如何实现的,我们都无需关心。
Java的软件系统会有很多模块组成,那么各个模块之间也应该采用这种面向接口
的低耦合
,为系统提供更好的可扩展性和可维护性。
接口用interface
这个关键字去修饰,就好比类用class
来修饰是一样的。首先,要想理解接口,我们先跟生活中的一个例子做一个类比。
生活当中,我们通常提到这个USB,通常就会说USB接口。其实这块呢,就提到了接口这个词,那么这个USB接口跟我们现在讲的这个接口是不是一个概念呢?其实还真可以理解成就是一个概念。
生活当中,大家每天都在用USB接口,首先看USB
,它叫通用串行总线。在电脑上,有一个或者有多个USB的接口,我们就可以跟很多外部设备进行连接了。
连接上以后的话,跟电脑就可以进行数据的传输了,这就是我们所谓的USB接口,你会发现全世界的USB接口的标准都是一样的。
比如说里边有几个金属点啊,这个尺寸是什么样子的,全都是一样的,所以我们都可以去做连接。
那么这其实就涉及到了一个USB的规范问题。具体的,不管是英特尔也好,或者它又交给了哪个组织也好,这个组织的话,就来负责制定USB的这个规范。
这个规范呢,咱们作为普通人,能够看到的就是一个长啊宽啊,或者里边有几个金属点(进行数据传输),这都是面儿上能看到的。
那没有看到,其实里边,就会涉及到很多功能的一些规范。
然后要求所有的外部设备,都应该具备相关的一些功能,比如说一连接上电脑以后能够进行,比如电脑对外开放了USB,连接上它以后,首先有提示“可以进行数据传输”了,包括所有的外部设备,我们最后不用的时候,这块儿有对应的弹出。
传输的时候,都会有相应的一些方法的支持。像这些的话,其实都是一些对应的规范,这个规范,在USB这个规范里面,它就定义好了。要求这些外部设备都得去实现。
其实这里边,提到这个USB接口就是我们现在要讲的这样的一种接口的概念,其实是类似的。
那我们这块儿来看一看JAVA当中这个接口是什么意思,回头让大家比对一下,你就知道,其实确实差不多。
(3)举例
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的is-a
关系,而接口实现则是 "能不能"的has-a
关系。
1. 举例1
电脑都预留了可以插入USB设备的USB接口,USB接口具备基本的数据传输的开启功能和关闭功能。你能不能用USB进行连接,或是否具备USB通信功能,就看你能否遵循USB接口规范。
比如说图中这个电脑,一个相机、打印机,或者这个U盘,现在想跟电脑进行数据库的传输。
那么,你现在是有没有啊?你要是想进行数据传输了,你能不能有这样的一个规范,这样一个标准呢去遵循呢?如果你遵循了这样的标准了,那我就让你跟电脑进行数据传输;你要没有遵循这个标准,我就不让你进行数据的传输。
所以这个标准的话,就体现为JAVA层面的一个接口,就是可以定义一个接口,假设就叫USB。然后要求、几个设备都得遵循这个规范,这个遵循怎么体现呢?
注意不是继承了,不能说这个打印机的父类是一个USB是吧?那这时候就涉及到类和这个USB接口之间的关系,我们称为实现关系
。一旦实现了这个USB接口,它就具备了这个规范里边定义的一些功能。
2. 举例2
再比如说这里,篮球运动员、足球运动员,大学生、小学生。篮球足球他们继承于叫运动员的类,学生这块儿继承学生类,这个是现有的继承关系
。
现在有一个叫学英语这样的一个接口
,这个接口它封装了一定的功能,或者说这个接口定义了学英语的一些规范。
比如说让足球运动员,大学生,不管是谁,如果你们想学英语,想遵循这样的一个接口的规范,你就实现这个接口就可以了。这是我们用的这个关系叫实现
。
实现以后,足球运动员和大学生,他们就能够去学英语了。父类该是谁还是谁,不受影响。
3. 举例3
再比如,看下面的图:
这个飞机也好,子弹也好,风筝也好,热气球也好,这块儿都是不同场景的具体的类。
这呢,我们只考虑实现的关系。
现在呢,有一个接口,它表示的就是可以飞这样的功能,或者叫规范,我们定义到这个接口里了。
然后你发现飞机也好,子弹也好,风筝也好,热气球,它们都可以飞,那我就可以让它们去实现这个接口。或者换句话说,当你实现这个接口以后,体现为就是你们可以飞了。
然后这还有一个接口叫攻击性,其实可以看到这里都不是名词了,以前讲父类的话,其实都是一个具体的载体,是一个名词,现在都是一种功能的封装。
这个子弹具备攻击性,那我就让子弹也实现这个接口,实现这个接口以后,它就具备了这样的一个功能。
所以从这个角度来讲呢,实现接口跟上一个例子继承的父类,它确实是不同场景。
4. 举例4
刚才提到了规范的问题,只要你实现了这个规范,你就具备这个规范里边定义的一些相关的功能。
那么关于规范这个事儿,再唠叨两句,怎么体现叫规范
呢?
刚才我们也说了,USB呢是定义了跟电脑通信的一种规范。谁实现了这个规范,谁就能够跟电脑进行数据传输。
这块呢又举了个例子。
这个例子的话,是在讲完这个java基础以后,大家可以学的一门技术,叫做jdbc
,什么叫jdbc呢?它实际上是一波api。说白了就是一些类库,这个类库里边,主要的就是接口。
那这个接口是干什么用的呢?它就是规范了我们用JAVA程序操作不同数据库的一组规范。
以前的话,比如没有JDBC,直接用JAVA程序去操作具体的数据库就可以了,但是这样写程序的话,会不具备通用性。像MySQL跟Oracle是不同的数据库,可能这个细节差别很大,你用JAVA程序连的MySQL,再拿这个代码去连Oracle肯定不好使。所以需要重新再去写一套代码去连Oracle,包括这个DB2也同样的道理。
显然就感觉一致性很差,怎么办呢?现在Sun公司他出面定一套标准,这套标准呢,就是有好多的接口,那么这套标准合在一起就叫做JDBC
,所以对于JAVA程序员来讲,你不要去面向具体的数据库去编程了,你只面向我这套接口编程就行。需要具备什么样功能,我这都定义好了,你直接去实现我这个接口,实现完以后的话,再去提供下边这些实现类。
因为是面向接口编程的,它封装了不同数据库厂商的细节。
比如现在操作的是Oracle,后期想改成MySQL,其实这块儿呢,非常简单,因为不同的数据库厂商都针对这套接口,就是这个标准进行了相关的一些改造了,都满足这套标准了。然后在连接MySQL的时候,这个代码其实变动很小,直接就可以来操作MySQL。
这就使得代码具备很好的移植性。
这就体现了接口它的作用,它就是一套规范。
Java程序是否能够连接使用某种数据库产品,那么要看该数据库产品能否实现Java设计的JDBC规范。
接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大家都要遵守。
比如制定了JDBC这样一套规范,里边主要是接口大,大家都遵循这套接口和规范。那我们再去编程的时候,这个代码就具备一致性、规范性。
USB也同样道理,全世界范围内USB的标准全是一样的,大家都去遵守。你去欧洲买USB的一个设备插到你电脑上照样也能用,因为这个标准都是一样的。
(4) 定义格式及重点举例
接口的定义,它与定义类方式相似,但是使用 interface
关键字。它也会被编译成.class
文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型:数组,类,枚举,接口,注解。
1. 接口的声明格式
[修饰符] interface 接口名{
//接口的成员列表:
// 公共的静态常量
// 公共的抽象方法
// 公共的默认方法(JDK1.8以上)
// 公共的静态方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
示例代码:
package com.atguigu.interfacetype;
public interface USB3{
//静态常量
long MAX_SPEED = 500*1024*1024;//500MB/s
//抽象方法
void in();
void out();
//默认方法
default void start(){
System.out.println("开始");
}
default void stop(){
System.out.println("结束");
}
//静态方法
static void show(){
System.out.println("USB 3.0可以同步全速地进行读写操作");
}
}
2. 接口的成员说明
①在JDK8.0 之前,接口中只允许出现:
(1)公共的静态的常量:其中public static final
可以省略
(2)公共的抽象的方法:其中public abstract
可以省略
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
②在JDK8.0 时,接口中允许声明默认方法
和静态方法
:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
③在JDK9.0 时,接口又增加了:
(5)私有方法
除此之外,接口中没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。
3. 接口内部结构的说明
<1> 接口的理解:接口的本质是契约、标准、规范,就像我们的法律一样。制定好后大家都要遵守。
<2> 定义接口的关键字:interface
<3> 接口内部结构的说明:
-
可以声明:
-
属性:必须使用
public static final
修饰(全局常量–不能改),不能声明变量。 -
方法:
- jdk8之前:声明抽象方法,修饰为
public abstract
- jdk8:声明静态方法、默认方法(自己用的少,一般源码会用)
- jdk9:声明私有方法(自己用的少,一般源码会用)
- jdk8之前:声明抽象方法,修饰为
-
-
不可以声明:构造器、代码块等(接口跟抽象类一样也不能造对象,接口压根没有构造器)
4. 举例
4.1 举例1–接口
①属性
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MIN_SPEED); //可以直接拿结构调用
System.out.println(Flyable.MAX_SPEED); //就算MAX_SPEED前面没有写static,也可以调用
//Flyable.MAX_SPEED=7000; //报错,MAX_SPEED是常量
}
}
interface Flyable{ //接口
//1.全局常量
public static final int MIN_SPEED=0; //能飞行的最低速度
//public static final可以不写
int MAX_SPEED=7900; //能飞行的最高速度
}
final
体现也可以证明,比如修改它的值,会报错,如下:
由于属性一般有 public static final 声明,所以可以省略。
②方法
interface Flyable{ //接口
//2.方法(JDK8之前,只能写抽象方法)
//可以省略 public abstract 声明
public abstract void fly();
}
由于抽象方法一般都有 public abstract声明,所以可以省略。
4.2 举例2–类实现接口
再比如“攻击性”。
interface Attackable{ //接口
public abstract void attack();
}
现在来提供具体接口的实现类。
类实现接口之后,接口里面的属性、方法也都拿过来了。
class Plane implements Flyable{
}
目前会报错,如下:
报错的原因是因为接口的抽象方法,两方面处理,要么写成一个抽象类,要么就将抽象方法实现以下。
比如:
//方法一、抽象类
abstract class Plane implements Flyable{
}
//方法二、重写抽象方法
class Bullet implements Flyable{
@Override
public void fly() {
}
}
若将抽象方法实现了,相对应就可以实例化了。
我们也可以实现两个接口,同样会拥有它们所有的抽象方法,都需要重写,比如:
//重写抽象方法
class Bullet implements Flyable,Attackable{
@Override
public void fly() {
}
@Override
public void attack() {
}
}
那么后续操作就和以前一样了,比如可以通过类造对象并调方法。比如:
package yuyi01;
/**
* ClassName: InterfaceTest
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/26 0026 9:22
*/
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MIN_SPEED); //可以直接拿结构调用
System.out.println(Flyable.MAX_SPEED); //就算MAX_SPEED前面没有写static,也可以调用
//Flyable.MAX_SPEED=7000; //报错,MAX_SPEED是常量
Bullet b1=new Bullet();
b1.fly();
b1.attack();
}
}
interface Flyable{ //接口
//1.全局常量
public static final int MIN_SPEED=0; //能飞行的最低速度
//public static final可以不写
int MAX_SPEED=7900; //能飞行的最高速度
//2.方法(JDK8之前,只能写抽象方法)
//可以省略 public abstract 声明
public abstract void fly();
}
interface Attackable{ //接口
public abstract void attack();
}
//抽象类
abstract class Plane implements Flyable{
}
//重写抽象方法
class Bullet implements Flyable,Attackable{
@Override
public void fly() {
System.out.println("让子弹飞一下");
}
@Override
public void attack() {
System.out.println("子弹可以击穿石头");
}
}
输出结果:
🗳️总结
<1> 接口与类的关系 :实现
关系
<2> 格式:class A extends SuperA implements B,C{}
A相较于SuperA来讲,叫做子类。
A相较于B,C来讲,叫做实现类。
<3> 满足此关系之后的说明:
-
类可以实现多个接口。(实现功能的扩充–多实现)
-
类针对于接口的多实现,一定程度上就弥补了类的单继承的局限性。
-
类必须将实现的接口中的所有的抽象方法都重写(或实现),方可实例化。否则,此实现类必须声明为抽象类。
4.3 举例3–接口与接口
将D实现CC,那么类 D里面就相当于有两个方法,要想实例化,就需要都重写一下。
//测试接口的继承关系
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{ //接口多继承 相较于AA,BB来说,CC是子接口
}
class D implements CC{
@Override
public void method1() {
}
@Override
public void method2() {
} //将D实现CC,那么类 D里面就相当于有两个方法,要想实例化,就需要都重写一下
}
接口与接口的关系:继承关系
,且可以多继承。
4.4 举例4–接口与多态性
接口的多态性: 接口名 变量名 = new 实现类对象;
(new的不是什么子接口,接口本身没有构造器,不能造对象,这里要造对象,所以肯定是“实现类对象”)
4.4.1 案例1–虚方法调用
其实就是虚方法调用
,跟讲类的多态性时候一样。
编译时定位到接口Flyable里面,但是运行的时候是实现类Bullet()里面的。
//接口的多态性
Flyable f1=new Bullet();
f1.fly(); //编译时定位到接口Flyable里面,但是运行的时候是实现类Bullet()里面的
调式:
运行结果:
4.4.2 案例2–创建接口实现类的对象
用接口体现的是“规范”,实际new的时候都是“实现类的对象”。
比如:
🌱代码(场景一)
package yuyi01;
/**
* ClassName: USBTest
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/26 0026 15:59
*/
public class USBTest {
public static void main(String[] args) {
//场景一: 创建接口实现类(Printer)的对象(printer)
//造一个电脑
Computer computer=new Computer(); //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)
//造一个打印机
Printer printer=new Printer();
//在电脑上连打印机
computer.transferData(printer); //transferData()方法里面声明的是USB类型,实际赋值的是Printer类型。即:USB usb=new Printer();
}
}
//电脑
class Computer{
public void transferData(USB usb){
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输细节操作...");
usb.stop();
}
}
//打印机
class Printer implements USB{ //打印机实现USB接口
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
//USB接口
interface USB{
//声明常量,比如USB的长、宽、高...
//方法
public abstract void start();
void stop();
}
🍺输出结果:
编译的时候看似调用的是USB的方法,实际执行的时候是具体打印机里面的方法。
多态性
的体现:
//在电脑上连打印机
computer.transferData(printer); //transferData()方法里面声明的是USB类型,实际赋值的是Printer类型。即:USB usb=new Printer();
接口要是没有多态性就废了,接口本身也没有构造器,无法造对象,也不让实现类的对象直接赋,那么这块就没法赋值了。
class Computer{
public void transferData(USB usb){
//...
}
}
🚗题外话
现在都是win 10 、win 11了,以前用这个XP啊,或者包括WIN 7的时候,你会发现经常一连外部设备,
它这块儿会联网操作,然后会提示一个说安装相关的驱动,这个驱动是什么?
驱动其实就是这个USB接口,它这一套标准已经定义好了,但是你连接这个外部设备,不知道你是什么,你要是连接我这个电脑,想进行数据传输的话,你得提供这一套实现类。我这只有一个接口,实际上的话它有好多接口,你得提供这样的一套实现类,那么这一套实现类的集合就是驱动
。
比如说扫描仪想在电脑上去传输,扫描仪连上电脑之后,到底怎么传呀?这个USB是有一套标准,但是这都是抽象方法,这块儿你得把你的这套驱动先加载进来,或者要是联网的话,去下载一个也行,那就相当于提供了一套,就是实现了接口的这样一套实现类。然后电脑跟你去传的时候,就按照这套实现类里边实现的这种方法去做了。
4.4.3 案例3–创建接口实现类的匿名对象
再写一个照相机:
//照相机
class Camera implements USB{
@Override
public void start() {
System.out.println("照相机开始工作");
}
@Override
public void stop() {
System.out.println("照相机结束工作");
}
}
🌱整体代码(场景二)
package yuyi01;
/**
* ClassName: USBTest
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/26 0026 15:59
*/
public class USBTest {
public static void main(String[] args) {
//造一个电脑
Computer computer=new Computer(); //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)
//场景一:创建接口实现类(Printer)的对象(printer)
//造一个打印机
Printer printer=new Printer();
//在电脑上连打印机
computer.transferData(printer); //transferData()方法里面声明的是USB类型,实际赋值的是Printer类型。即:USB usb=new Printer();
System.out.println();
//场景二:创建接口实现类的匿名对象
computer.transferData(new Camera());
}
}
//电脑
class Computer{
public void transferData(USB usb){
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输细节操作...");
usb.stop();
}
}
//打印机
class Printer implements USB{ //打印机实现USB接口
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
//照相机
class Camera implements USB{
@Override
public void start() {
System.out.println("照相机开始工作");
}
@Override
public void stop() {
System.out.println("照相机结束工作");
}
}
//USB接口
interface USB{
//声明常量,比如USB的长、宽、高...
//方法
public abstract void start();
void stop();
}
🍺输出结果
4.4.4 案例4–创建接口匿名实现类的对象
之前写了两个场景了,现在再来看一个。
public class USBTest {
public static void main(String[] args) {
//场景一: 创建接口实现类(Printer)的对象(printer)
Computer computer=new Computer(); //造一个电脑
Printer printer=new Printer(); //造一个打印机
computer.transferData(printer); //在电脑上连打印机 USB usb=new Printer();
//场景二: 创建接口实现类的匿名对象
computer.transferData(new Camera());
//场景三: 创建接口匿名实现类的对象
computer.transferData();
}
}
computer.transferData();
里面要传一个USB的实现类的对象,场景一是Printer,场景二是Camera,实现类都有名;
现在若实现类没有名了,要想声明一个变量怎么办呢?只能先拿USB顶替一下,对象有名,比如叫usb1:USB usb1=
。
等号右边咋写?new一个啥呢?new一个构造器?构造器要与类同名,比如场景二是new Camera()
,那么类就是Camera()
。
此时对象有个名叫usb1
,但是类没有。
那我们就new
一个USB()
,然后将usb1
当作实参放入computer.transferData();
中。如下:
USB usb1=new USB();
computer.transferData(usb1);
此时是报错状态,如下:
此时报错很正常,因为USB本身没有构造器。
现在这个实现类没有名,但是形式上又得这样写,此时拿USB充当了一下。但是实现类又要保证不是抽象类,这样才能够造对象,USB是充当了,但是它里面有抽象方法啊(抽象方法不能够造对象了)。如下:
//USB接口
interface USB{
//声明常量,比如USB的长、宽、高...
//方法
public abstract void start();
void stop();
}
🍸那咋办呢?
我们可以临时将USB里面的抽象方法给重写一下。
直接在后面整一对大括号,如下:
USB usb1=new USB(){
};
然后在里面重写抽象方法即可,如下:
//场景三: 创建接口匿名实现类的对象
USB usb1=new USB(){
public void start(){
System.out.println("U盘开始工作");
}
public void stop(){
System.out.println("U盘结束工作");
}
};
computer.transferData(usb1);
🌱整体代码(场景三)
package yuyi01;
/**
* ClassName: USBTest
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/26 0026 15:59
*/
public class USBTest {
public static void main(String[] args) {
//造一个电脑
Computer computer=new Computer(); //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)
//场景一:创建接口实现类(Printer)的对象(printer)
//造一个打印机
Printer printer=new Printer();
//在电脑上连打印机
computer.transferData(printer); //transferData()方法里面声明的是USB类型,实际赋值的是Printer类型。即:USB usb=new Printer();
System.out.println(); //换行
//场景二:创建接口实现类的匿名对象
computer.transferData(new Camera());
System.out.println(); //换行
//场景三: 创建接口匿名实现类的对象
USB usb1=new USB(){
public void start(){
System.out.println("U盘开始工作");
}
public void stop(){
System.out.println("U盘结束工作");
}
};
computer.transferData(usb1);
}
}
//电脑
class Computer{
public void transferData(USB usb){
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输细节操作...");
usb.stop();
}
}
//打印机
class Printer implements USB{ //打印机实现USB接口
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
//照相机
class Camera implements USB{
@Override
public void start() {
System.out.println("照相机开始工作");
}
@Override
public void stop() {
System.out.println("照相机结束工作");
}
}
//USB接口
interface USB{
//声明常量,比如USB的长、宽、高...
//方法
public abstract void start();
void stop();
}
🍺输出结果
🗃️絮叨
USB usb1=new USB(){
//...
};
接口的匿名实现类,new后面要放实现类的类名,但现在实现类匿名,所以只能暂时用USB充当一下。
而类要是想造对象,必须要把抽象方法给实现一下,那么就在后面大括号临时实现一下即可。
现在对象是有名的。
4.4.5 案例5–创建接口匿名实现类的匿名对象
再来看一个场景。
此时类匿名,对象也匿名,很显然,在上一个场景的基础上,直接将new的部分当参数即可。
如下:
//场景四: 创建接口匿名实现类的匿名对象
computer.transferData(new USB(){//...});
写一下:
//场景四: 创建接口匿名实现类的匿名对象
computer.transferData(new USB(){
public void start(){
System.out.println("扫描仪开始工作");
}
public void stop(){
System.out.println("扫描仪结束工作");
}
});
这个也是多态性
,可千万别说是new了一个接口USB的对象,是new了一个接口实现类的对象(只不过这个实现类没有名)。
🌱整体代码(场景四)
package yuyi01;
/**
* ClassName: USBTest
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/26 0026 15:59
*/
public class USBTest {
public static void main(String[] args) {
//造一个电脑
Computer computer=new Computer(); //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)
//场景一:创建接口实现类(Printer)的对象(printer)
//造一个打印机
Printer printer=new Printer();
//在电脑上连打印机
computer.transferData(printer); //transferData()方法里面声明的是USB类型,实际赋值的是Printer类型。即:USB usb=new Printer();
System.out.println(); //换行
//场景二:创建接口实现类的匿名对象
computer.transferData(new Camera());
System.out.println(); //换行
//场景三: 创建接口匿名实现类的对象
USB usb1=new USB(){
public void start(){
System.out.println("U盘开始工作");
}
public void stop(){
System.out.println("U盘结束工作");
}
};
computer.transferData(usb1);
System.out.println(); //换行
//场景四: 创建接口匿名实现类的匿名对象
computer.transferData(new USB(){
public void start(){
System.out.println("扫描仪开始工作");
}
public void stop(){
System.out.println("扫描仪结束工作");
}
});
}
}
//电脑
class Computer{
public void transferData(USB usb){
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输细节操作...");
usb.stop();
}
}
//打印机
class Printer implements USB{ //打印机实现USB接口
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
//照相机
class Camera implements USB{
@Override
public void start() {
System.out.println("照相机开始工作");
}
@Override
public void stop() {
System.out.println("照相机结束工作");
}
}
//USB接口
interface USB{
//声明常量,比如USB的长、宽、高...
//方法
public abstract void start();
void stop();
}
🍺输出结果
5 . 总结
🗳️总结
<1> 接口与类的关系 :实现
关系
<2> 格式:class A extends SuperA implements B,C{}
A相较于SuperA来讲,叫做子类。
A相较于B,C来讲,叫做实现类。
<3> 满足此关系之后的说明:
- 类可以实现多个接口。(实现功能的扩充–多实现)
- 类针对于接口的多实现,一定程度上就弥补了类的单继承的局限性。
- 类必须将实现的接口中的所有的抽象方法都重写(或实现),方可实例化。否则,此实现类必须声明为抽象类。
<4> 接口与接口的关系:继承关系
,且可以多继承
<5> 多态性
- 接口的多态性:
接口名 变量名 = new 实现类对象;
(new的不是什么子接口,接口本身没有构造器,不能造对象,这里要造对象,所以肯定是“实现类对象”) - 类的多态性:
父类 变量名 = new 子类对象 ;
<6> 面试题:区分抽象类和接口
-
共性
- 都可以声明抽象方法。(抽象类可能没有抽象方法)
- 都不能实例化。(都不能创建对象)
-
不同
- 抽象类一定有构造器。接口没有构造器。
- 类与类之间继承关系,类与接口之间是实现关系,接口与接口之间是多继承关系。
- 抽象类中的属性可以随意定义,但是接口中只能是常量。
<7> 重点掌握四种场景
public class USBTest {
public static void main(String[] args) {
//场景一: 创建接口实现类(Printer)的对象(printer)
Computer computer=new Computer(); //造一个电脑
Printer printer=new Printer(); //造一个打印机
computer.transferData(printer); //在电脑上连打印机 USB usb=new Printer();
//场景二: 创建接口实现类的匿名对象
computer.transferData(new Camera());
//场景三: 创建接口匿名实现类的对象
USB usb1=new USB(){
public void start(){
System.out.println("U盘开始工作");
}
public void stop(){
System.out.println("U盘结束工作");
}
};
computer.transferData(usb1);
//场景四: 创建接口匿名实现类的匿名对象
computer.transferData(new USB(){
public void start(){
System.out.println("扫描仪开始工作");
}
public void stop(){
System.out.println("扫描仪结束工作");
}
});
}
}
第一种最规范,但通常开发中会使用第三种和第四种,所以需要掌握。
<8> 接口存在多态性
。
以后看到一个方法的形参是一个接口的时候,必然要使用多态。因为接口本身没有构造器,不能造对象,一定要放实现类的对象,即多态性。
//电脑
class Computer{
public void transferData(USB usb){ //多态:USB usb=new Printer();
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输细节操作...");
usb.stop();
}
}
二、 接口的使用规则
(1)类实现接口(implements)
接口不能创建对象,但是可以被类实现(implements
,类似于被继承)。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements
关键字。
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
注意:
- 如果接口的实现类是非抽象类,那么必须
重写接口中所有抽象方法
。 - 默认方法可以选择保留,也可以重写。
重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
- 接口中的静态方法不能被继承也不能被重写
举例:
interface USB{ //
public void start() ;
public void stop() ;
}
class Computer{
public static void show(USB usb){
usb.start() ;
System.out.println("=========== USB 设备工作 ========") ;
usb.stop() ;
}
};
class Flash implements USB{
public void start(){ // 重写方法
System.out.println("U盘开始工作。") ;
}
public void stop(){ // 重写方法
System.out.println("U盘停止工作。") ;
}
};
class Print implements USB{
public void start(){ // 重写方法
System.out.println("打印机开始工作。") ;
}
public void stop(){ // 重写方法
System.out.println("打印机停止工作。") ;
}
};
public class InterfaceDemo{
public static void main(String args[]){
Computer.show(new Flash()) ;
Computer.show(new Print()) ;
c.show(new USB(){
public void start(){
System.out.println("移动硬盘开始运行");
}
public void stop(){
System.out.println("移动硬盘停止运行");
}
});
}
};
(2)接口的多实现(implements)
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的多实现
。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次。
举例:
定义多个接口:
package com.atguigu.interfacetype;
public interface A {
void showA();
}
package com.atguigu.interfacetype;
public interface B {
void showB();
}
定义实现类:
package com.atguigu.interfacetype;
public class C implements A,B {
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
}
测试类
package com.atguigu.interfacetype;
public class TestC {
public static void main(String[] args) {
C c = new C();
c.showA();
c.showB();
}
}
(3)接口的多继承(extends)
一个接口能继承另一个或者多个接口,接口的继承也使用 extends
关键字,子接口继承父接口的方法。
定义父接口:
package com.atguigu.interfacetype;
public interface Chargeable {
void charge();
void in();
void out();
}
定义子接口:
package com.atguigu.interfacetype;
public interface UsbC extends Chargeable,USB3 {
void reverse();
}
定义子接口的实现类:
package com.atguigu.interfacetype;
public class TypeCConverter implements UsbC {
@Override
public void reverse() {
System.out.println("正反面都支持");
}
@Override
public void charge() {
System.out.println("可充电");
}
@Override
public void in() {
System.out.println("接收数据");
}
@Override
public void out() {
System.out.println("输出数据");
}
}
所有父接口的抽象方法都有重写。
方法签名相同的抽象方法只需要实现一次。
(4)接口与实现类对象构成多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
接口的不同实现类:
package com.atguigu.interfacetype;
public class Mouse implements USB3 {
@Override
public void out() {
System.out.println("发送脉冲信号");
}
@Override
public void in() {
System.out.println("不接收信号");
}
}
package com.atguigu.interfacetype;
public class KeyBoard implements USB3{
@Override
public void in() {
System.out.println("不接收信号");
}
@Override
public void out() {
System.out.println("发送按键信号");
}
}
测试类
package com.atguigu.interfacetype;
public class TestComputer {
public static void main(String[] args) {
Computer computer = new Computer();
USB3 usb = new Mouse();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
System.out.println("--------------------------");
usb = new KeyBoard();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
System.out.println("--------------------------");
usb = new MobileHDD();
computer.setUsb(usb);
usb.start();
usb.out();
usb.in();
usb.stop();
}
}
(5)使用接口的静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
package com.atguigu.interfacetype;
public class TestUSB3 {
public static void main(String[] args) {
//通过“接口名.”调用接口的静态方法 (JDK8.0才能开始使用)
USB3.show();
//通过“接口名.”直接使用接口的静态常量
System.out.println(USB3.MAX_SPEED);
}
}
(6)使用接口的非静态方法
- 对于接口的静态方法,直接使用“
接口名.
”进行调用即可- 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
- 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
package com.atguigu.interfacetype;
public class TestMobileHDD {
public static void main(String[] args) {
//创建实现类对象
MobileHDD b = new MobileHDD();
//通过实现类对象调用重写的抽象方法,以及接口的默认方法,如果实现类重写了就执行重写的默认方法,如果没有重写,就执行接口中的默认方法
b.start();
b.in();
b.stop();
//通过接口名调用接口的静态方法
// MobileHDD.show();
// b.show();
Usb3.show();
}
}
三、JDK8与JDK9中接口新特性
(1)说明
- 接口内部结构的说明:
-
可以声明:
-
属性:必须使用
public static final
修饰 -
方法:jdk8之前:声明抽象方法,修饰为
public abstract
jdk8:声明静态方法、默认方法
jdk9:声明私有方法 -
不可以声明:构造器、代码块等
- 传统中,就按照这样来写:
但是从JDK8开始,它可以有方法体的方法了。其实自己写的比较少,更多的是源码层面,它会扩充一些有方法体的方法。可以直接使用。
- 接口与类:
class A extends SuperA implements B,C{}
继承是单继承,现在可以实现多个接口。JDK8中,接口里面可以有方法体的方法了,可以直接拿给A用,一定程度上缓解了单继承的局限性。
(2)举例
1. 案例1–JDK8 静态方法
先来看JDK8中静态方法。
【CompareA.java】
package yuyi05;
/**
* ClassName: CompareA
* Package: yuyi05
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 9:59
*/
public interface CompareA {
//属性:声明为 public static final
//方法:JDK8之前,只能声明抽象方法 public abstract
//方法:JDK8--静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
}
可以看到,method1()
就是静态方法,有方法体。和平时在类里面写静态方法一致,没什么区别。
那怎么用呢?
先写一个SubClass
类,实现CompareA:
package yuyi05;
/**
* ClassName: SubClass
* Package: yuyi05
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 10:03
*/
public class SubClass implements CompareA{
}
由于接口里面没有抽象方法,所以此时也没有报错,如下:
再写一个测试类SubClassTest
:
public class SubClassTest {
public static void main(String[] args) {
}
}
现在接口CompareA里面有一个静态方法method1(),静态方法拿声明的主体去调应该就可以,比如:CompareA.method1();
运行发现可以:
如果用接口的实现类SubClass
来调用这个方法,可以吗?
其实不可以,如下:
所以,实现类不可以调用!
知识点1:接口中声明的静态方法只能被接口调用,不能使用其实现类进行调用。
2. 案例2–JDK8 默认方法
2.1 默认方法
接下来看JDK8中默认方法。
这里要使用一个关键字default
。
【CompareA.java】
public interface CompareA {
//...
//方法:JDK8--默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
}
就是在普通方法里面加了一个default
。若是不加default,它就默认你省略了abstract
,会认为此方法是一个抽象方法。如下:
既然SubClass实现了接口CompareA,那么这个默认方法method2()应该也可以拿到。
下面来做测试。
public class SubClassTest {
public static void main(String[] args) {
//...
SubClass s1=new SubClass(); //因为是默认方法,不是静态,所以要造对象
s1.method2();
}
}
运行结果:
在SubClass类里面,也可以对接口CompareA里的方法做重写。比如:
public class SubClass implements CompareA{
@Override
public void method2() {
System.out.println("SubClass:上海");
}
}
再次测试,就会调用重写之后的方法了(与类类似),如下:
知识点2:接口中声明的默认方法可以被实现类继承,实现类在没有重写此方法的情况下,默认调用接口中声明的默认方法;如果实现类重写了此方法,则调用的是自己重写的方法。
2.2 接口冲突
在接口CompareA中再写一个默认方法method3()
,如下:
public interface CompareA {
//属性:声明为 public static final
//方法:JDK8之前,只能声明抽象方法 public abstract
//方法:JDK8--静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//方法:JDK8--默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
public default void method3(){
System.out.println("CompareA:广州");
}
}
然后再写一个接口CompareB
,方法method3()
的声明与接口CompareA
中一致,如下:
public interface CompareB {
//默认方法
public default void method3(){
System.out.println("CompareB:广州");
}
}
现在SubClass实现了接口CompareA,那么SubClass也拿到了CompareA中的方法method3()。
此时让SubClass也实现接口CompareB,可以发现出错了:
前面说过“抽象方法”,若接口CompareA里面有一个抽象方法,接口CompareB里面也有一个抽象方法,CompareA和CompareB里面的抽象方法声明一致,那么此时SubClass会有上面的问题吗?
其实不会,
因为SubClass类要想造对象的话,需要把这个抽象方法重写一下,这个重写既可以看作是对CompareA里面抽象方法的重写,也可以看作是对CompareB里面抽象方法的重写。
若SubClass类没有重写抽象方法,而是一个抽象类,那也没有问题,因为它们的方法长得一样,没有方法体。
但是现在的情况是,接口CompareA和CompareB它们的默认方法有方法体,而且声明一样,那么在实现类SubClass没有重写的情况下就会冲突。
此时若在测试类里面调用method3(),编译器就不知道调用谁,因为接口的地位对于类来说完全一致,s1不知道该调用谁,就会报错。
我们将这种问题叫做“接口冲突
”。
此时实现类必须要重写接口中定义的同名同参数的方法。
public class SubClass implements CompareA,CompareB{
//...
public void method3(){ //类没有default之说,不用写它
System.out.println("SubClass:广州");
}
}
这个重写既可以看作是对CompareA里面method3()方法的重写,也可以看作是对CompareB里面method3()方法的重写。
然后在测试类里面调用method3(),就会是自己的方法了,如下:
知识点3:类实现了两个接口,而两个接口中定义了同名同参数的默认方法。则实现类在没有重写此两个接口默认方法的情况下会报错–>接口冲突
要求:此时实现类必须要重写接口中定义的同名同参数的方法。
2.3 类优先原则
现在在接口CompareA里面再加一个方法method4()
:
public interface CompareA {
//...
public default void method4(){
System.out.println("CompareA:深圳");
}
}
然后再写一个类SuperClass,并将CompareA中的method4()
粘过来(名字、参数都一样),如下:
public class SuperClass {
public void method4(){
System.out.println("SuperClass:深圳");
}
}
然后让SubClass继承于SuperClass:
可以发现,此时没有报错。
SuperClass里面有method4(),CompareA里面也有method4(),它们名字一样、参数一样,此时为啥不会报错呢?
这是因为类与接口的低位不平等。
若此时拿s1去调用method4(),结果如下:
可以发现,调用的是SuperClass类中的method4()方法。
将这种情况叫做“类优先原则”。
知识点4:子类(实现类)继承了父类并实现了接口,父类和接口中声明了同名同参数的方法。(对于接口来说,这是默认方法)
默认情况下,子类(实现类)在没有重写此方法的情况下,调用的是父类中的方法。—> 类优先原则
若子类重写了,那么调用的时候肯定就是重写之后的方法了,
2.4 在实现类中调用接口中被重写的方法
在SubClass类里面重写一下method4()方法:
public class SubClass extends SuperClass implements CompareA,CompareB{
//...
public void method4(){
System.out.println("SubClass:深圳");
}
}
在测试类里面调用method4()就是调用自己重写的了:
此时在SubClass中,再写一个普通的方法,想调用自己方法中的method4(),直接method4()
即可。
如果想调用父类中的method4(),加一个super.
即可:super.method4()
。
public class SubClass extends SuperClass implements CompareA,CompareB{
//...
public void method(){
method4(); //调用自己类中的方法
super.method4(); //调用父类中的方法
}
}
现在我想调用CompareA中的method3(),接口CompareA和接口CompareB中都有method3(),那怎么调用呢?
用CompareA调用吗?这样:CompareA..method3()
。此时method3()不是静态的啊,所以不能这样调用。
那加一个super:CompareA.super.method3();
就可以啦。
同理。调用CompareB里面的method3():CompareB.super.method3();
这里的super也不是体现“父类的”了,就是继承了父类或者实现了接口,都算是super的场景(基于实例来说的)。
public class SubClass extends SuperClass implements CompareA,CompareB{
//...
public void method(){
method4(); //调用自己类中的方法
super.method4(); //调用父类中的方法
CompareA.super.method3(); //调用接口CompareA中的默认方法
CompareB.super.method3(); //调用接口CompareB中的默认方法
}
}
知识点5:如何在子类(或实现类)中调用父类或接口中被重写的方法
比如,调用CompareB里面的method3():
CompareB.super.method3();
3. 案例3–JDK9 定义私有方法
“文件”–>“项目结构”:
这个地方用的17,如果是8就不行哦:
这种情况下就可以演示JDK9新特性了。比如:
public interface CompareA {
//...
//方法:JDK9新特性--定义私有方法
private void method5(){
System.out.println("我是接口CompareA中定义的私有方法");
}
}
私有方法实现类也不能继承,这个方法干啥用呢?就是自己用。
刚才写的默认方法都有方法体,而且method5()也不是静态方法,不是给静态方法用的,就是给默认方法用的啦。
若定义了好多默认方法,彼此之间有一些共同的代码,可以将这些共同代码专门抽取出来,也不对外暴露了,就让它私有化。
就是这样使用。
(3)整体代码
这里将刚才举例的所有代码展现出来,供大家学习使用。
🌱代码
【CompareA.java】
package yuyi05;
/**
* ClassName: CompareA
* Package: yuyi05
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 9:59
*/
public interface CompareA {
//属性:声明为 public static final
//方法:JDK8之前,只能声明抽象方法 public abstract
//方法:JDK8--静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//方法:JDK8--默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
public default void method3(){
System.out.println("CompareA:广州");
}
public default void method4(){
System.out.println("CompareA:深圳");
}
//方法:JDK9新特性--定义私有方法
private void method5(){
System.out.println("我是接口CompareA中定义的私有方法");
}
}
【CompareB.java】
package yuyi05;
/**
* ClassName: CompareB
* Package: yuyi05
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 10:35
*/
public interface CompareB {
//默认方法
public default void method3(){
System.out.println("CompareB:广州");
}
}
【SubClass.java】
package yuyi05;
/**
* ClassName: SubClass
* Package: yuyi05
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 10:03
*/
public class SubClass extends SuperClass implements CompareA,CompareB{
@Override
public void method2() {
System.out.println("SubClass:上海");
}
public void method3(){ //类没有default之说,不用写它
System.out.println("SubClass:广州");
}
public void method4(){
System.out.println("SubClass:深圳");
}
public void method(){
method4(); //调用自己类中的方法
super.method4(); //调用父类中的方法
CompareA.super.method3(); //调用接口CompareA中的默认方法
CompareB.super.method3(); //调用接口CompareB中的默认方法
}
}
【SuperClass.java】
package yuyi05;
/**
* ClassName: SuperClass
* Package: yuyi05
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 11:01
*/
public class SuperClass {
public void method4(){
System.out.println("SuperClass:深圳");
}
}
【SubClassTest.java】
package yuyi05;
/**
* ClassName: SubClassTest
* Package: yuyi05
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 10:05
*/
public class SubClassTest {
public static void main(String[] args) {
//知识点1:接口中声明的静态方法只能被接口调用,不能使用其实现类进行调用。
CompareA.method1();
//SubClass.method1();
//知识点2:接口中声明的默认方法可以被实现类继承,实现类在没有重写此方法的情况下,默认调用接口中声明的默认方法;
// 如果实现类重写了此方法,则调用的是自己重写的方法。
SubClass s1=new SubClass(); //因为是默认方法,不是静态,所以要造对象
s1.method2();
//知识点3:类实现了两个接口,而两个接口中定义了同名同参数的默认方法。则实现类在没有重写此两个接口默认方法的情况下会报错-->接口冲突
//要求:此时实现类必须要重写接口中定义的同名同参数的方法。
s1.method3();
//知识点4:子类(实现类)继承了父类并实现了接口,父类和接口中声明了同名同参数的方法。(对于接口来说,这是默认方法)
//默认情况下,子类(实现类)在没有重写此方法的情况下,调用的是父类中的方法。--->类优先原则
s1.method4();
s1.method();
}
}
🍺输出结果
四、JDK8中相关冲突问题
(1) 默认方法冲突问题
1. 类优先原则
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。代码如下:
定义接口:
package com.atguigu.interfacetype;
public interface Friend {
default void date(){//约会
System.out.println("吃喝玩乐");
}
}
定义父类:
package com.atguigu.interfacetype;
public class Father {
public void date(){//约会
System.out.println("爸爸约吃饭");
}
}
定义子类:
package com.atguigu.interfacetype;
public class Son extends Father implements Friend {
@Override
public void date() {
//(1)不重写默认保留父类的
//(2)调用父类被重写的
// super.date();
//(3)保留父接口的
// Friend.super.date();
//(4)完全重写
System.out.println("跟康师傅学Java");
}
}
定义测试类:
package com.atguigu.interfacetype;
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.date();
}
}
2. 接口冲突(左右为难)
- 当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?
无论你多难抉择,最终都是要做出选择的。
声明接口:
package com.atguigu.interfacetype;
public interface BoyFriend {
default void date(){//约会
System.out.println("神秘约会");
}
}
选择保留其中一个,通过“接口名.super.方法名
"的方法选择保留哪个接口的默认方法。
package com.atguigu.interfacetype;
public class Girl implements Friend,BoyFriend{
@Override
public void date() {
//(1)保留其中一个父接口的
// Friend.super.date();
// BoyFriend.super.date();
//(2)完全重写
System.out.println("跟康师傅学Java");
}
}
测试类
package com.atguigu.interfacetype;
public class TestGirl {
public static void main(String[] args) {
Girl girl = new Girl();
girl.date();
}
}
- 当一个子接口同时继承了多个接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?
另一个父接口:
package com.atguigu.interfacetype;
public interface USB2 {
//静态常量
long MAX_SPEED = 60*1024*1024;//60MB/s
//抽象方法
void in();
void out();
//默认方法
public default void start(){
System.out.println("开始");
}
public default void stop(){
System.out.println("结束");
}
//静态方法
public static void show(){
System.out.println("USB 2.0可以高速地进行读写操作");
}
}
子接口:
package com.atguigu.interfacetype;
public interface USB extends USB2,USB3 {
@Override
default void start() {
System.out.println("Usb.start");
}
@Override
default void stop() {
System.out.println("Usb.stop");
}
}
小贴士:
子接口重写默认方法时,default关键字可以保留。
子类重写默认方法时,default关键字不可以保留。
(2) 常量冲突问题
- 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见。
- 当子类同时实现多个接口,而多个接口存在相同同名常量。
此时在子类中想要引用父类或父接口的同名的常量或成员变量时,就会有冲突问题。
父类和父接口:
package com.atguigu.interfacetype;
public class SuperClass {
int x = 1;
}
package com.atguigu.interfacetype;
public interface SuperInterface {
int x = 2;
int y = 2;
}
package com.atguigu.interfacetype;
public interface MotherInterface {
int x = 3;
}
子类:
package com.atguigu.interfacetype;
public class SubClass extends SuperClass implements SuperInterface,MotherInterface {
public void method(){
// System.out.println("x = " + x);//模糊不清
System.out.println("super.x = " + super.x);
System.out.println("SuperInterface.x = " + SuperInterface.x);
System.out.println("MotherInterface.x = " + MotherInterface.x);
System.out.println("y = " + y);//没有重名问题,可以直接访问
}
}
五、接口的总结与面试题
(1)总结
- 接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
- 声明接口用interface,接口的成员声明有限制:
- (1)公共的静态常量
- (2)公共的抽象方法
- (3)公共的默认方法(JDK8.0 及以上)
- (4)公共的静态方法(JDK8.0 及以上)
- (5)私有方法(JDK9.0 及以上)
- 类可以实现接口,关键字是implements,而且支持多实现。如果实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么继承(extends)在前,实现(implements)在后。
- 接口可以继承接口,关键字是extends,而且支持多继承。
- 接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
- 接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过“接口名.静态方法名”进行调用。
(2)面试题
1、为什么接口中只能声明公共的静态的常量?
因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。
例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA
例如:某校学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。
2、为什么JDK8.0 之后允许接口定义静态方法和默认方法呢?因为它违反了接口作为一个抽象标准定义的概念。
静态方法
:
因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。
默认方法
:
(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。
(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。
3、为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的。
私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。
六、接口与抽象类之间的对比
【区分抽象类和接口】
-
共性
- 都可以声明抽象方法。(抽象类可能没有抽象方法)
- 都不能实例化。(都不能创建对象)
-
不同
- 抽象类一定有构造器。接口没有构造器。
- 类与类之间继承关系,类与接口之间是实现关系,接口与接口之间是多继承关系。
- 抽象类中的属性可以随意定义,但是接口中只能是常量。
☕总结如下图:
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
七、 练习
(1)练习1
🌋题目描述
1、声明接口Eatable
,包含抽象方法public abstract void eat();
。
2、声明实现类中国人Chinese
,重写抽象方法,打印用筷子吃饭。
3、声明实现类美国人American
,重写抽象方法,打印用刀叉吃饭。
4、声明实现类印度人Indian
,重写抽象方法,打印用手抓饭。
5、声明测试类EatableTest
,创建Eatable数组,存储各国人对象,并遍历数组,调用eat()方法。
🌱代码
【Eatable.java】
package yuyi02;
/**
* ClassName: Eatable
* Package: yuyi02
* Description:
* 1、声明接口Eatable,包含抽象方法public abstract void eat();
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 9:37
*/
public abstract interface Eatable { //接口
public abstract void eat();
}
【Chinese.java】
package yuyi02;
/**
* ClassName: Chinese
* Package: yuyi02
* Description:
* 2、声明实现类中国人Chinese,重写抽象方法,打印用筷子吃饭
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 9:39
*/
public class Chinese implements Eatable{
@Override
public void eat() {
System.out.println("中国人使用筷子吃饭");
}
}
【American.java】
package yuyi02;
/**
* ClassName: American
* Package: yuyi02
* Description:
* 3、声明实现类美国人American,重写抽象方法,打印用刀叉吃饭
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 9:46
*/
public class American implements Eatable{
@Override
public void eat() {
System.out.println("美国人使用刀叉吃饭");
}
}
【Indian.java】
package yuyi02;
/**
* ClassName: Indian
* Package: yuyi02
* Description:
* 4、声明实现类印度人Indian,重写抽象方法,打印用手抓饭
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 9:49
*/
public class Indian implements Eatable{
@Override
public void eat() {
System.out.println("印度人使用手抓吃饭");
}
}
【EatableTest.java】
package yuyi02;
/**
* ClassName: EatableTest
* Package: yuyi02
* Description:
* 5、声明测试类EatableTest,创建Eatable数组,存储各国人对象,并遍历数组,调用eat()方法
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 9:50
*/
public class EatableTest {
public static void main(String[] args) {
//new的是一个数组,数组这里指明的是元素的类型,类型是Eatable,new的并不是构造器,只是指明了元素类型
Eatable[] eatables=new Eatable[3]; //接口类型3个元素
//创建实现类对象,并赋值给数组元素
eatables[0]=new Chinese(); //针对数组中每个元素进行赋值,这后边new的是构造器
eatables[1]=new American();
eatables[2]=new Indian();
//遍历数组
for (int i = 0; i < eatables.length; i++) {
eatables[i].eat();
}
}
}
🍺输出结果
🍹注意
//new的是一个数组,数组这里指明的是元素的类型,类型是Eatable,new的并不是构造器,只是指明了元素类型
Eatable[] eatables=new Eatable[3]; //接口类型3个元素
//创建实现类对象,并赋值给数组元素
eatables[0]=new Chinese(); //针对数组中每个元素进行赋值,这后边new的是构造器
eatables[1]=new American();
eatables[2]=new Indian();
只要声明接口了,给它实例化的时候,一定是实现类的对象,必然要体现多态性。
只要有接口的地方就有多态性!
(2)练习2
🌋题目描述
1、定义一个接口用来实现两个对象的比较。
interface CompareObject{
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
public int compareTo(Object o);
}
2、定义一个Circle
类,声明radius属性,提供getter和setter方法。
3、定义一个ComparableCircle
类,继承Circle类并且实现CompareObject接口。
在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
4、定义一个测试类InterfaceTest
,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
拓展:参照上述做法定义矩形类Rectangle和ComparableRectangle类,在ComparableRectangle类中给出compareTo方法的实现,比较两个矩形的面积大小。
🌱代码
【Circle.java】
package yuyi03;
/**
* ClassName: Circle
* Package: yuyi03
* Description:
* 定义一个Circle类,声明radius属性,提供getter和setter方法
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 14:52
*/
public class Circle { //两个Circle对象不能够比较大小
public double radius; //半径
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public Circle() {
}
public Circle(double radius) {
this.radius = radius;
}
//toString方法
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
【CompareObject.java】
package yuyi03;
/**
* ClassName: CompareObject
* Package: yuyi03
* Description:
* 定义一个接口用来实现两个对象的比较。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 10:09
*/
public interface CompareObject { //自定义一个接口来比较对象大小
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
public int compareTo(Object o); //这个是抽象方法(省略了abstract),虽然它没有方法体,但是这个方法是做什么的,形参是什么意思,返回值类型是什么都完全确定了,只是细节没有确定
}
【ComparableCircle.java】
package yuyi03;
/**
* ClassName: ComparableCircle
* Package: yuyi03
* Description:
* 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
* 在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 14:56
*/
public class ComparableCircle extends Circle implements CompareObject{
//根据对象半径的大小,比较对象的大小(和之前说的equals很像)
@Override
public int compareTo(Object o) {
if(this==o){ //判断当前对象与o是不是指向同一个
return 0; //若地址一样,则半径肯定一致,直接返回0
}
if(o instanceof ComparableCircle){ //判断是否是当前类的对象
ComparableCircle c=(ComparableCircle) o; //若是当前类对象,先强转一下 (从父类对象强转成子类才能调用子类特有的结构)
//错误的(逻辑上错误)
//return (int) (this.getRadius()-c.getRadius()); //当两者整数部分都一致的时候,不靠谱
//正确的写法1
/*if(this.getRadius()>c.getRadius()){
return 1;
} else if (this.getRadius()<c.getRadius()) {
return -1;
}else{
return 0;
}*/
//正确写法2
return Double.compare(this.getRadius(),c.getRadius()); //API里面有一个类就叫Double,里面有一个方法叫compare(),里面传入两个double类型的值,就会自动比较它们的大小,返回的就是一个int类型的值,直接return即可
}else{ //当这个对象不是当前实例
throw new RuntimeException("输入类型不匹配");
}
}
public ComparableCircle() {
}
public ComparableCircle(double radius) {
super(radius);
}
}
【InterfaceTest.java】
package yuyi03;
/**
* ClassName: InterfaceTest
* Package: yuyi03
* Description:
* 定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 15:43
*/
public class InterfaceTest {
public static void main(String[] args) {
ComparableCircle c1=new ComparableCircle(2.3);
ComparableCircle c2=new ComparableCircle(5.3);
int compareValue=c1.compareTo(c2);
if(compareValue>0){
System.out.println("c1对象大");
} else if (compareValue<0) {
System.out.println("c2对象大");
}else {
System.out.println("c1与c2一样大");
}
}
}
🍺输出结果
🍹注意
//根据对象半径的大小,比较对象的大小(和之前说的equals很像)
@Override
public int compareTo(Object o) {
if(this==o){ //判断当前对象与o是不是指向同一个
return 0; //若地址一样,则半径肯定一致,直接返回0
}
if(o instanceof ComparableCircle){ //判断是否是当前类的对象
ComparableCircle c=(ComparableCircle) o; //若是当前类对象,先强转一下
return (int) (this.getRadius()-c.getRadius());
}
//...
}
注意return (int) (this.getRadius()-c.getRadius());
这里强转是不好使的(因为半径radius是double类型的)。
若this.getRadius()
是2.5,c.getRadius()
是2.1,那么this.getRadius()-c.getRadius()
则是2.5-2.1=0.4,经过强转就是0,0表示两个对象相等,不靠谱。
API里面有一个类就叫Double
,里面有一个方法叫compare()
,里面传入两个double类型的值,就会自动比较它们的大小,返回的就是一个int类型的值,直接return即可。
//正确写法2
return Double.compare(this.getRadius(),c.getRadius());
当这个对象不是当前实例的时候,相当于这两个对象就没有办法比较,此时还必须要一个返回值,但是 return 任何一个值都不太合适。
若是返回大于0的数,则当前对象大;若为0,则一样大;若是小于0,则当前小。
以后会说“异常”,这个时候就可以拋一个异常,在异常里面写“输入的类型不匹配”即可。(这里需要的类型是ComparableCircle,结果传过来的不是这个类型,没法比较)
else{ //当这个对象不是当前实例
throw new RuntimeException("输入类型不匹配");
}
只要你实现CompareObject接口(这个接口用于定义比较大小的事儿),就需要重写compareTo这个方法,只要重写了这个方法,就知道谁大谁小。
(3)练习3
🌋题目描述
阿里的一个工程师Developer,结构见图。
其中,有一个乘坐交通工具的方法takingVehicle(),在此方法中调用交通工具的run()。
为了出行方便,他买了一辆捷安特自行车、一辆雅迪电动车和一辆奔驰轿车。这里涉及到的相关类及接口关系如图。
其中,电动车增加动力的方式是充电,轿车增加动力的方式是加油。在具体交通工具的run()中调用其所在类的相关属性信息。
请编写相关代码,并测试。
提示:创建Vehicle[]数组,保存阿里工程师的三辆交通工具,并分别在工程师的takingVehicle()中调用。
🌱代码
【Developer.java】
package yuyi04;
/**
* ClassName: Developer
* Package: yuyi04
* Description:
* 阿里的一个工程师Developer,结构见图。
* 其中,有一个乘坐交通工具的方法takingVehicle(),在此方法中调用交通工具的run()。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 17:15
*/
public class Developer {
private String name;
private int age;
public Developer() {
}
public Developer(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void takingVehicle(Vehicle vehicle){
vehicle.run();
}
}
【IPower.java】
package yuyi04;
/**
* ClassName: IPower
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 17:40
*/
public interface IPower {
void power();
}
【Vehicle.java】
package yuyi04;
/**
* ClassName: Vehicle
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 17:21
*/
public abstract class Vehicle { //abstract抽象类,不能实例化(创建对象)
private String brand; //品牌
private String color; //颜色
public Vehicle() {
}
public Vehicle(String brand, String color) {
this.brand = brand;
this.color = color;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public abstract void run(); //接口里面可以省略public abstract,抽象类里面不可以
}
【Bicycle.java】
package yuyi04;
/**
* ClassName: Bicycle
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 17:30
*/
public class Bicycle extends Vehicle{
@Override
public void run() {
System.out.println("自行车通过人力行驶");
}
public Bicycle() {
}
public Bicycle(String brand, String color) {
super(brand, color);
}
}
【ElectricVehicle.java】
package yuyi04;
/**
* ClassName: ElectricVehicle
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 17:34
*/
public class ElectricVehicle extends Vehicle implements IPower{
@Override
public void run() {
System.out.println("电动车通过电机驱动行驶");
}
public ElectricVehicle() {
}
public ElectricVehicle(String brand, String color) {
super(brand, color);
}
@Override
public void power() {
System.out.println("电动车使用电力提供动力");
}
}
【Car.java】
package yuyi04;
/**
* ClassName: Car
* Package: yuyi04
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 17:36
*/
public class Car extends Vehicle implements IPower{
private String carNumber;
@Override
public void run() {
System.out.println("汽车通过内燃机驱动行驶");
}
public Car() {
}
public Car(String brand, String color, String carNumber) {
super(brand, color);
this.carNumber = carNumber;
}
public String getCarNumber() {
return carNumber;
}
public void setCarNumber(String carNumber) {
this.carNumber = carNumber;
}
@Override
public void power() {
System.out.println("汽车通过汽油提供动力");
}
}
【VehicleTest.java】
package yuyi04;
/**
* ClassName: VehicleTest
* Package: yuyi04
* Description:
* 为了出行方便,他买了一辆捷安特自行车、一辆雅迪电动车和一辆奔驰轿车。这里涉及到的相关类及接口关系如图。
* 其中,电动车增加动力的方式是充电,轿车增加动力的方式是加油。在具体交通工具的run()中调用其所在类的相关属性信息。
*
* 提示:创建Vehicle[]数组,保存阿里工程师的三辆交通工具,并分别在工程师的takingVehicle()中调用。
* @Author 雨翼轻尘
* @Create 2023/11/28 0028 17:45
*/
public class VehicleTest {
public static void main(String[] args) {
//创建一个工程师
Developer developer=new Developer();
//创建三个交通工具,保存在数组中(数组的类型是三个交通工具的共同类型)
Vehicle[] vehicles=new Vehicle[3]; //声明了3个元素的数组结构,每个元素是Vehicle,并不是new了一个构造器
vehicles[0]=new Bicycle("捷安特","红色");
vehicles[1]=new ElectricVehicle("爱玛","蓝色");
vehicles[2]=new Car("奔驰","黑色","皖P666");
for (int i = 0; i < vehicles.length; i++) {
developer.takingVehicle(vehicles[i]);
if(vehicles[i] instanceof IPower){ //接口这儿仍然可以这样来写
((IPower) vehicles[i]).power();
}
}
}
}
🍺输出结果
🍹注意
Ctrl+I
可以调出接口要重写的方法。
比如:
则可以自动生成:
@Override
public void power() {
}
编译看左边,运行看右边。这是多态的体现,虽然编译的时候,点进去是在vehicle类中,但真正执行run方法的是实现父类抽象方法的子类。
这个是自己加的新功能,题目中并没有要求。
接口也可以这样来写:
if(vehicles[i] instanceof IPower){ //接口这儿仍然可以这样来写
}
当然,此时用vehicles[i]
调用power()
方法是不可以的,如下:
vehicles[i]
本身没有power()
方法,所以此时需要强转,如下:
if(vehicles[i] instanceof IPower){ //接口这儿仍然可以这样来写
((IPower) vehicles[i]).power();
}
Vechicles类型是父类,没有重写接口的power方法,我们需要将其类型转化为Ipower
类型的。