Java面向对象(高级)-- 接口(interface)

一、概念

(1)引入

从三条主线的角度来说的话,这儿属于第三条主线,叫做interface关键字的使用。

从另外一个角度来说的话,它是跟类并列的一个结构,这样讲的话,它的地位一下子就提升了。

可以看一下API

比如说就看一下这个long包下的base。这些结构里边,我们再来看一下这个long包:

image.png

可以看到Interface和Class是并列的结构:

image.png

平常新建java文件的时候,也可以看到类与接口:

image.png

那这个接口到底是一个什么样的结构?跟类并列的是吧?前面我们讲的面向对象特征说类里边都能声明什么,其实都是类的,这个体系跟接口没关系

(2)类比

生活中大家每天都在用USB接口,那么USB接口与我们今天要学习的接口有什么相同点呢?

USB,(Universal Serial Bus,通用串行总线)是Intel公司开发的总线架构,使得在计算机上添加串行设备(鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等)非常容易。

其实,不管是电脑上的USB插口,还是其他设备上的USB插口都只是遵循了USB规范的一种具体设备而已。

image.png

只要设备遵循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接口规范。
image.png

比如说图中这个电脑,一个相机、打印机,或者这个U盘,现在想跟电脑进行数据库的传输。

那么,你现在是有没有啊?你要是想进行数据传输了,你能不能有这样的一个规范,这样一个标准呢去遵循呢?如果你遵循了这样的标准了,那我就让你跟电脑进行数据传输;你要没有遵循这个标准,我就不让你进行数据的传输。

所以这个标准的话,就体现为JAVA层面的一个接口,就是可以定义一个接口,假设就叫USB。然后要求、几个设备都得遵循这个规范,这个遵循怎么体现呢?

注意不是继承了,不能说这个打印机的父类是一个USB是吧?那这时候就涉及到类和这个USB接口之间的关系,我们称为实现关系一旦实现了这个USB接口,它就具备了这个规范里边定义的一些功能

2. 举例2

再比如说这里,篮球运动员、足球运动员,大学生、小学生。篮球足球他们继承于叫运动员的类,学生这块儿继承学生类,这个是现有的继承关系
image.png

现在有一个叫学英语这样的一个接口,这个接口它封装了一定的功能,或者说这个接口定义了学英语的一些规范。

比如说让足球运动员,大学生,不管是谁,如果你们想学英语,想遵循这样的一个接口的规范,你就实现这个接口就可以了。这是我们用的这个关系叫实现

实现以后,足球运动员和大学生,他们就能够去学英语了。父类该是谁还是谁,不受影响。

3. 举例3

再比如,看下面的图:
image.png

这个飞机也好,子弹也好,风筝也好,热气球也好,这块儿都是不同场景的具体的类。

这呢,我们只考虑实现的关系。

现在呢,有一个接口,它表示的就是可以飞这样的功能,或者叫规范,我们定义到这个接口里了。

然后你发现飞机也好,子弹也好,风筝也好,热气球,它们都可以飞,那我就可以让它们去实现这个接口。或者换句话说,当你实现这个接口以后,体现为就是你们可以飞了

然后这还有一个接口叫攻击性,其实可以看到这里都不是名词了,以前讲父类的话,其实都是一个具体的载体,是一个名词,现在都是一种功能的封装。

这个子弹具备攻击性,那我就让子弹也实现这个接口,实现这个接口以后,它就具备了这样的一个功能。

所以从这个角度来讲呢,实现接口跟上一个例子继承的父类,它确实是不同场景。

4. 举例4

刚才提到了规范的问题,只要你实现了这个规范,你就具备这个规范里边定义的一些相关的功能

那么关于规范这个事儿,再唠叨两句,怎么体现叫规范呢?

刚才我们也说了,USB呢是定义了跟电脑通信的一种规范。谁实现了这个规范,谁就能够跟电脑进行数据传输。
这块呢又举了个例子。

这个例子的话,是在讲完这个java基础以后,大家可以学的一门技术,叫做jdbc,什么叫jdbc呢?它实际上是一波api。说白了就是一些类库,这个类库里边,主要的就是接口。

那这个接口是干什么用的呢?它就是规范了我们用JAVA程序操作不同数据库的一组规范

以前的话,比如没有JDBC,直接用JAVA程序去操作具体的数据库就可以了,但是这样写程序的话,会不具备通用性。像MySQL跟Oracle是不同的数据库,可能这个细节差别很大,你用JAVA程序连的MySQL,再拿这个代码去连Oracle肯定不好使。所以需要重新再去写一套代码去连Oracle,包括这个DB2也同样的道理。

显然就感觉一致性很差,怎么办呢?现在Sun公司他出面定一套标准,这套标准呢,就是有好多的接口,那么这套标准合在一起就叫做JDBC,所以对于JAVA程序员来讲,你不要去面向具体的数据库去编程了,你只面向我这套接口编程就行。需要具备什么样功能,我这都定义好了,你直接去实现我这个接口,实现完以后的话,再去提供下边这些实现类。

因为是面向接口编程的,它封装了不同数据库厂商的细节。

比如现在操作的是Oracle,后期想改成MySQL,其实这块儿呢,非常简单,因为不同的数据库厂商都针对这套接口,就是这个标准进行了相关的一些改造了,都满足这套标准了。然后在连接MySQL的时候,这个代码其实变动很小,直接就可以来操作MySQL。

这就使得代码具备很好的移植性。

这就体现了接口它的作用,它就是一套规范
image.png

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:声明私有方法(自己用的少,一般源码会用)
  • 不可以声明:构造器、代码块等(接口跟抽象类一样也不能造对象,接口压根没有构造器)

4. 举例

image.png

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体现也可以证明,比如修改它的值,会报错,如下:

image.png

由于属性一般有 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{

}

目前会报错,如下:

image.png

报错的原因是因为接口的抽象方法,两方面处理,要么写成一个抽象类,要么就将抽象方法实现以下。

比如:

//方法一、抽象类
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("子弹可以击穿石头");
    }
}

输出结果:

image.png


🗳️总结

<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–虚方法调用

image.png

其实就是虚方法调用,跟讲类的多态性时候一样。

编译时定位到接口Flyable里面,但是运行的时候是实现类Bullet()里面的。

//接口的多态性
Flyable f1=new Bullet();
f1.fly();   //编译时定位到接口Flyable里面,但是运行的时候是实现类Bullet()里面的

调式:
问题5.gif

运行结果:

image.png


4.4.2 案例2–创建接口实现类的对象

用接口体现的是“规范”,实际new的时候都是“实现类的对象”。

比如:
image.png

🌱代码(场景一)

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();
}

🍺输出结果:

image.png

编译的时候看似调用的是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();
}

🍺输出结果

image.png


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);

此时是报错状态,如下:

image.png

此时报错很正常,因为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();
}

🍺输出结果

image.png

🗃️絮叨

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();
}

🍺输出结果

image.png

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 接口{
    // 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

image.png

注意:

  1. 如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法
  2. 默认方法可以选择保留,也可以重写。

重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了

  1. 接口中的静态方法不能被继承也不能被重写

举例:

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。。。{
    // 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
  	// 重写接口中默认方法【可选】
}

接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重写一次

举例:
image.png

image.png

image.png

定义多个接口:

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)说明

  1. 接口内部结构的说明:
  • 可以声明:

  • 属性:必须使用public static final修饰

  • 方法:jdk8之前:声明抽象方法,修饰为public abstract
    jdk8:声明静态方法、默认方法
    jdk9:声明私有方法

  • 不可以声明:构造器、代码块等

  1. 传统中,就按照这样来写:

image.png
但是从JDK8开始,它可以有方法体的方法了。其实自己写的比较少,更多的是源码层面,它会扩充一些有方法体的方法。可以直接使用。

  1. 接口与类: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{

}

由于接口里面没有抽象方法,所以此时也没有报错,如下:

image.png

再写一个测试类SubClassTest

public class SubClassTest {
    public static void main(String[] args) {

    }
}

现在接口CompareA里面有一个静态方法method1(),静态方法拿声明的主体去调应该就可以,比如:CompareA.method1();

运行发现可以:

image.png

如果用接口的实现类SubClass来调用这个方法,可以吗?

其实不可以,如下:

image.png

所以,实现类不可以调用!

知识点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,会认为此方法是一个抽象方法。如下:

image.png

既然SubClass实现了接口CompareA,那么这个默认方法method2()应该也可以拿到。

下面来做测试。

public class SubClassTest {
    public static void main(String[] args) {
        //...
        SubClass s1=new SubClass(); //因为是默认方法,不是静态,所以要造对象
        s1.method2();
    }
}

运行结果:

image.png

在SubClass类里面,也可以对接口CompareA里的方法做重写。比如:

public class SubClass implements CompareA{
    @Override
    public void method2() {
        System.out.println("SubClass:上海");
    }
}

再次测试,就会调用重写之后的方法了(与类似),如下:

image.png

知识点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,可以发现出错了:

image.png

前面说过“抽象方法”,若接口CompareA里面有一个抽象方法,接口CompareB里面也有一个抽象方法,CompareA和CompareB里面的抽象方法声明一致,那么此时SubClass会有上面的问题吗?

其实不会

因为SubClass类要想造对象的话,需要把这个抽象方法重写一下,这个重写既可以看作是对CompareA里面抽象方法的重写,也可以看作是对CompareB里面抽象方法的重写。

若SubClass类没有重写抽象方法,而是一个抽象类,那也没有问题,因为它们的方法长得一样,没有方法体。


但是现在的情况是,接口CompareA和CompareB它们的默认方法有方法体,而且声明一样,那么在实现类SubClass没有重写的情况下就会冲突

此时若在测试类里面调用method3(),编译器就不知道调用谁,因为接口的地位对于类来说完全一致,s1不知道该调用谁,就会报错。

image.png

我们将这种问题叫做“接口冲突”。

此时实现类必须要重写接口中定义的同名同参数的方法

public class SubClass implements CompareA,CompareB{
    //...
    public  void method3(){ //类没有default之说,不用写它
        System.out.println("SubClass:广州");
    }
}

这个重写既可以看作是对CompareA里面method3()方法的重写,也可以看作是对CompareB里面method3()方法的重写。

然后在测试类里面调用method3(),就会是自己的方法了,如下:

image.png

知识点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:

image.png

可以发现,此时没有报错

SuperClass里面有method4(),CompareA里面也有method4(),它们名字一样、参数一样,此时为啥不会报错呢?

这是因为类与接口的低位不平等

若此时拿s1去调用method4(),结果如下:

image.png

可以发现,调用的是SuperClass类中的method4()方法。

将这种情况叫做“类优先原则”。

知识点4:子类(实现类)继承了父类并实现了接口,父类和接口中声明了同名同参数的方法。(对于接口来说,这是默认方法)

默认情况下,子类(实现类)在没有重写此方法的情况下,调用的是父类中的方法。—> 类优先原则

若子类重写了,那么调用的时候肯定就是重写之后的方法了,

2.4 在实现类中调用接口中被重写的方法

在SubClass类里面重写一下method4()方法:

public class SubClass extends SuperClass implements CompareA,CompareB{
    //...  
    public void method4(){
        System.out.println("SubClass:深圳");
    }
}

在测试类里面调用method4()就是调用自己重写的了:

image.png

此时在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(),那怎么调用呢?

image.png

用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 定义私有方法

“文件”–>“项目结构”:

image.png

这个地方用的17,如果是8就不行哦:

image.png

这种情况下就可以演示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();
    }
}

🍺输出结果

image.png

四、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. 接口冲突(左右为难)

  • 当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?

image.png

无论你多难抉择,最终都是要做出选择的。

声明接口:

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要允许接口定义私有方法呢?因为我们说接口是规范,规范是需要公开让大家遵守的。

私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所以就增加了私有方法。

六、接口与抽象类之间的对比

【区分抽象类和接口】

  • 共性

    • 都可以声明抽象方法。(抽象类可能没有抽象方法)
    • 都不能实例化。(都不能创建对象)
  • 不同

    • 抽象类一定有构造器。接口没有构造器。
    • 类与类之间继承关系,类与接口之间是实现关系,接口与接口之间是多继承关系。
    • 抽象类中的属性可以随意定义,但是接口中只能是常量。

☕总结如下图:

image.png

在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。

七、 练习

(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();
        }
    }
}

🍺输出结果

image.png

🍹注意

//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一样大");
        }
    }
}

🍺输出结果

image.png

🍹注意

//根据对象半径的大小,比较对象的大小(和之前说的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 任何一个值都不太合适。

image.png

若是返回大于0的数,则当前对象大;若为0,则一样大;若是小于0,则当前小。

以后会说“异常”,这个时候就可以拋一个异常,在异常里面写“输入的类型不匹配”即可。(这里需要的类型是ComparableCircle,结果传过来的不是这个类型,没法比较)

else{  //当这个对象不是当前实例
    throw new RuntimeException("输入类型不匹配");
}

只要你实现CompareObject接口(这个接口用于定义比较大小的事儿),就需要重写compareTo这个方法,只要重写了这个方法,就知道谁大谁小。

(3)练习3

🌋题目描述

阿里的一个工程师Developer,结构见图。

image.png

其中,有一个乘坐交通工具的方法takingVehicle(),在此方法中调用交通工具的run()。

为了出行方便,他买了一辆捷安特自行车、一辆雅迪电动车和一辆奔驰轿车。这里涉及到的相关类及接口关系如图。
image.png

其中,电动车增加动力的方式是充电,轿车增加动力的方式是加油。在具体交通工具的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();
            }
        }

    }
}

🍺输出结果

image.png

🍹注意

Ctrl+I可以调出接口要重写的方法。

比如:

image.png

则可以自动生成:

@Override
public void power() {

}

image.png

编译看左边,运行看右边。这是多态的体现,虽然编译的时候,点进去是在vehicle类中,但真正执行run方法的是实现父类抽象方法的子类。

这个是自己加的新功能,题目中并没有要求。

接口也可以这样来写:

if(vehicles[i] instanceof IPower){  //接口这儿仍然可以这样来写

}

当然,此时用vehicles[i]调用power()方法是不可以的,如下:
image.png

vehicles[i]本身没有power()方法,所以此时需要强转,如下:

if(vehicles[i] instanceof IPower){  //接口这儿仍然可以这样来写
    ((IPower) vehicles[i]).power();
}

Vechicles类型是父类,没有重写接口的power方法,我们需要将其类型转化为Ipower类型的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨翼轻尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值