JAVA基础——接口

接口概念
    官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。

    我的解释:接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。(JDK1.8之前可以这样理解)

接口的特点
    就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。

接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。
一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者implement move()方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。
如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)
一个JAVA库中接口的例子是:Comparator 接口,这个接口代表了“能够进行比较”这种能力,任何类只要实现了这个Comparator接口的话,这个类也具备了“比较”这种能力,那么就可以用来进行排序操作了。
为什么要用接口
    接口被用来描述一种抽象。
因为Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限。
接口也被用来实现解耦。
接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非final的变量,但是在接口中存在的变量一定是final,public,static的。
接口的语法实现
    为了声明一个接口,我们使用interface这个关键字,在接口中的所有方法都必须只声明方法标识,而不要去声明具体的方法体,因为具体的方法体的实现是由继承该接口的类来去实现的,因此,接口并不用管具体的实现。接口中的属性默认为Public Static Final.一个类实现这个接口必须实现这个接口中定义的所有的抽象方法。

    一个简单的接口就像这样:拥有全局变量和抽象方法。

    为了实现这个接口,我们使用implements关键词去实现接口:

其中testClass类实现了我们上面刚才定义的 in1 这个接口,既然你要实现接口,也就是实现接口代表的一种能力,那么你就必须去实现接口给你规定的方法,只有把接口给你规定的抽象方法都给实现了,才承认你这个类实现了这个接口,实现了这个接口代表的某种功能。上图实现了接口中规定的display()方法。

    写一个测试类,用来测试一下我们刚才实现的这个接口,因为testclass类的对象t实现了接口规定的display方法,那么自然而然就可以调用display()方法咯。

    有兴趣的同学可以去这个在线IDE亲自试一试:点击打开链接

接口的进一步理解
    我们知道,如果某个设备需要向电脑中读取或者写入某些东西,这些设备一般都是采用USB方式与电脑连接的,我们发现,只要带有USB功能的设备就可以插入电脑中使用了,那么我们可以认为USB就是一种功能,这种功能能够做出很多的事情(实现很多的方法),其实USB就可以看做是一种标准,一种接口,只要实现了USB标准的设备我就认为你已经拥有了USB这种功能。(因为你实现了我USB标准中规定的方法),下面是具体的例子:

先声明USB接口:其中规定了要实现USB接口就必须实现接口规定实现的read( )和write( )这两个方法。

interface USB {
    void read();

    void write();
}
然后在写一个U盘类和一个键盘类,这两个类都去实现USB接口。(实现其中的方法)

class YouPan implements USB {
    @Override
    public void read() {
        System.out.println("U盘正在通过USB功能读取数据");
    }
    @Override
    public void write() {
        System.out.println("U盘正在通过USB功能写入数据");
    }
}
这是U盘的具体实现。
class JianPan implements USB {
    @Override
    public void read() {
        System.out.println("键盘正在通过USB功能读取数据");
    }
    @Override
    public void write() {
        System.out.println("键盘正在通过USB功能写入数据");
    }
}
这是键盘的具体实现。
    那么,现在U盘和键盘都实现了USB功能,也就是说U盘和键盘都能够调用USB接口中规定的方法,并且他们实现的方式都不一样。

我们在写一个测试,来看看具体的实现:

public class Main {
    public static void main(String[] args) {
        //生成一个实现可USB接口(标准)的U盘对象
        YouPan youPan = new YouPan();
        //调用U盘的read( )方法读取数据
        youPan.read();
        //调用U盘的write( )方法写入数据
        youPan.write();
        //生成一个实现可USB接口(标准)的键盘对象
        JianPan jianPan = new JianPan();
        //调用键盘的read( )方法读取数据
        jianPan.read();
        //调用键盘的write( )方法写入数据
        jianPan.write();
    }
}
结果如下:


    感兴趣的同学可以去在线IDE平台自己验证一下:点击打开链接

关于接口的几个重点
我们不能直接去实例化一个接口,因为接口中的方法都是抽象的,是没有方法体的,这样怎么可能产生具体的实例呢?但是,我们可以使用接口类型的引用指向一个实现了该接口的对象,并且可以调用这个接口中的方法。因此,上图中最后的方法调用我们还可以这样写:(实际上就是使用了Java中多态的特性)

public class Main {
    public static void main(String[] args) {
        //生成一个实现可USB接口(标准)的U盘对象
        //但是使用一个接口引用指向对象
        //USB接口类引用可以指向一个实现了USB接口的对象
        USB youPan = new YouPan();
        //调用U盘的read( )方法读取数据
        youPan.read();
        //调用U盘的write( )方法写入数据
        youPan.write();
        //生成一个实现可USB接口(标准)的键盘对象
        //但是使用一个接口引用指向对象
        //USB接口类引用可以指向一个实现了USB接口的对象
        USB jianPan = new JianPan();
        //调用键盘的read( )方法读取数据
        jianPan.read();
        //调用键盘的write( )方法写入数据
        jianPan.write();
    }
}                                                     

                                                                           
 2.一个类可以实现不止一个接口。

3.一个接口可以继承于另一个接口,或者另一些接口,接口也可以继承,并且可以多继承。

4.一个类如果要实现某个接口的话,那么它必须要实现这个接口中的所有方法。

5.接口中所有的方法都是抽象的和public的,所有的属性都是public,static,final的。

6.接口用来弥补类无法实现多继承的局限。

7.接口也可以用来实现解耦。

接口的通俗理解
    前面我们讲多态的时候用“空调”——“遥控器”的方式去理解多态,实际上在上面的的几个重点中的第一条讲的也是多态的实现,比如,我们可以把“节能”作为一种标准,或者说节能就是一个“接口”,这个接口中有一个方法,叫做变频方法,任何空调,如果要称得上叫做节能空调的话,那么必须实现“节能”这个接口,实现“节能”这个接口,也就必须实现“节能”接口中规定实现的“变频”方法,这样才算是真正的实现了“节能”这个接口,实现了“节能”这个功能。

    当某个空调实现了“节能”接口后,这个空调就具备了节能的功能,那么我们也可以不用空调类的引用指向空调对象,我们可以直接使用一个“节能”接口类型引用的“遥控器”去指向“空调”,虽然这个“遥控器”上面只有一个按键,只有一个“变频”的方法,但是“遥控器”所指向的空调是实现了“节能”这个接口的,是有“变频”方法的实现的,我们用这个只有一个“变频”方法的遥控器去命令空调调用“变频”方法,也是行得通的,实在不清楚的同学可以去看我的另一篇文章:JAVA之对象的多态性。

接口的标识用法
    虽然接口内部定义了一些抽象方法,但是并不是所有的接口内部都必须要有方法,比如Seriallizable接口,Seriallizable接口的作用是使对象能够“序列化”,但是Seriallizable接口中却没有任何内容,也就是说,如果有一个类需要实现“序列化”的功能,则这个类必须去实现Seriallizable接口,但是却并不用实现方法(因为接口中没有方法),此时,这个Serilizable接口就仅仅是一个“标识”接口,是用来标志一个类的,标志这个类具有这个“序列化”功能。具体的实现请参考我的另一篇文章——JAVA之IO流。

接口在生活中的思想体现
其实,在我们的生活当中,有很多地方都体现了“接口”的思想,想必,正在阅读这篇博文的你,是不是也喜欢摄影呢?

玩摄影的童鞋都知道,单反由相机和镜头组成,相机分不同的型号,有半画幅的,也有全画幅的。镜头也是一样的,分长焦,短焦;还有定焦和变焦。每种镜头都有各自特定的发挥场景。正是因为镜头的多元化,使得我们的摄影能够“术业有专攻”。大家想一想,如果我们的单反相机部分和镜头部分是固定在一起的,不能够更换镜头,那么将会多么的糟糕啊!

因此,每个相机品牌为了能够兼容不同的镜头,各自发布了一套镜头卡口的标准,这套标准就好比我们前面提到的“接口”,都是某种“约束”。举个栗子,我们佳能的相机,不管你是哪一家镜头生产厂商,腾龙也好,适马也好,只要你按照我佳能卡口的标准来生产镜头,你生产的镜头都能够很好的在我佳能相机上面驱动。

佳能EF卡口镜头是佳能公司为其单反相机和电影摄影机设计的一系列镜头。除了佳能原厂生产的EF镜头外,还有一些第三方镜头制造商生产的镜头也可以适配佳能EF卡口。以下是一些可以适配佳能EF卡口的镜头品牌:
 
1. 腾龙(Tamron):腾龙提供了多款兼容佳能EF卡口的镜头,如腾龙EF 18-400,这是一款覆盖超广角到超远摄的变焦镜头。
 
2. 适马(Sigma):适马也生产了一系列兼容佳能EF卡口的镜头,包括适马EF 20mm F1.8 EX DG和适马ART系列镜头,如35mm F1.4和85mm F1.4。
 
3. 永诺(Yongnuo):永诺提供了价格相对便宜的EF卡口镜头选项,例如永诺EF 50mm F1.4 EX和EF 100 F2.0。
 
4. 佳能(Canon):佳能原厂的EF镜头系列非常庞大,包括广角、标准、远摄、微距等多种类型的镜头。
 
5. 第三方适配器:通过使用适配器,其他品牌的镜头也可以安装在佳能EF卡口相机上。例如,佳能推出了EF-EOS R 0.71x卡口适配器,允许在EOS R系列相机上使用EF镜头。
 


因此,当我们准备给自己的新相机买镜头的时候,就不难发现,我们需要根据自己相机的品牌来挑选特定卡口的镜头,这样的镜头才能被我们的相机正常驱动。

回到Java上面来说,其实接口给我们带来的最大的好处就是“解耦”了,相机能够搭配不同的镜头,才能有各种各样的搭配玩法,变得更加的灵活。在软件系统中也是一样的,接口可以有很多不同“特色”的实现类,我们只需要声明同一个接口,却可以引用很多个该“接口”引申出来的“子类”,这不也大大增强了我们软件系统中组件的灵活性吗?

聪明的你,对于“接口”的理解是不是又更加的深入了呢?

接口在编码中的实践
本文发表也有些年头了,最开始仅仅只是想自己记录一些学习心得,没想到竟然受到大家如此热爱!陆陆续续看到评论区有些同学可能因为还没有实际的工作经验,所以对接口在实际项目中的应用,还是缺少些感性的认知,因此本着宠粉的精神,觉得可以通过模拟【发送短信】这个场景,来给到工作经验少的同学,对于接口有一个更加感性的认知。

假设,你刚参加实习工作,组长说:来来来,先给你个小需求练练手,我们系统现在需要接入短信平台,你仔细看看阿里云的文档,把阿里云SDK引入我们系统,然后大概有100处地方,需要发送短信,你把发送短信的代码分别加到这100处去。

好家伙,这不就是CTRL+C+V的事情吗?于是你看到了阿里云发送短信只需要一个class就能完成工作,这个class长下面这样:

package com.lyj.demo.sms;
 
/**
 * 阿里云短信实现类
 */
 
public class AliyunSMS {
 
 
    /**发送登录短信
     * @param phoneNumbers 电话号码
     * @param message 短信内容
     */
    void sendLoginSMS(String phoneNumbers,String message) {
        System.out.println("【阿里云】登录短信发送给"+phoneNumbers+":"+message);
    }
 
    /**
     * 忘记密码发送短信
     *
     * @param phoneNumbers 电话号码
     * @param message 短信内容
     */
    void sendForgetSMS(String phoneNumbers,String message) {
        System.out.println("【阿里云】忘记密码发送给"+phoneNumbers+":"+message);
 
    }
 
 
    /**发送营销短信
     * @param phoneNumbers 电话号码
     * @param message 短信内容
     */
    void sendMarketingSMS(String phoneNumbers, String message) {
        System.out.println("【阿里云】营销短信发送给"+phoneNumbers+":"+message);
 
    }
}


好家伙,100处代码分散在项目中各个文件里面(虽然下图100条发送语句都在一个文件中,但模拟100条发送语句分散在各种代码文件中),光打开这些文件,都耗费了你一个下午的时间,终于在快下班的时候,你感叹到:小小短信平台,拿下!你跑了跑项目,看到发送的短信内容,露出了欣慰的笑容。

【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!
【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!
【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!
【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!
正当你准备下班和女朋友约饭的时候,小组长来了,说:“完成的不错,但是领导觉得阿里云太贵了,我们还是准备用腾讯云,把阿里云换成腾讯云吧!明天就要上线哦!”

你笑着对小组长说:“好的,我今晚没事,放心,我加个班一下子就能搞定的事情!”,心里却想着"vocal !,这B组长,一下班就搞事情是吧!看我10分钟就能搞定,今晚和女朋友的饭局稳稳地!"说着,你看着代码,却发现代码散落在各个文件中,你不禁陷入了沉思,这咋改啊?阿里云和腾讯云的短信参数顺序也不一样,名字也不一样,看来只能一个一个替换了,说罢,你便开始一个一个代码的删除,新增,忙的手忙脚乱。

下面是腾讯云发送短信的class:

package com.lyj.demo.sms;
 
/**
 * 腾讯云短信实现类
 */
public class TencentSMS {
    /**
     * /**发送登录短信
     *
     * @param message      短信内容
     * @param phoneNumbers 电话号码
     */
    void sendLoginSMS(String message, String phoneNumbers) {
        System.out.println("【腾讯云】登录短信发送给"+phoneNumbers+":"+message);
    }
 
    /**
     * 忘记密码发送短信
     *
     * @param message      短信内容
     * @param phoneNumbers 电话号码
     */
    void sendForgetSMS(String message, String phoneNumbers) {
        System.out.println("【腾讯云】忘记密码发送给"+phoneNumbers+":"+message);
 
    }
 
 
    /**
     * 发送营销短信
     *
     * @param message      短信内容
     * @param phoneNumbers 电话号码
     */
    void sendMarketingSMS(String message, String phoneNumbers) {
        System.out.println("【腾讯云】营销短信发送给"+phoneNumbers+":"+message);
 
    }
}


你直接通过ctrl+R把阿里云替换成腾讯云

一跑项目,发现全是Bug,原来腾讯云的参数和阿里云全部是反的,你直接人傻了,这可咋办啊,眼泪在眼眶里打转。

此时小组长看到你难堪的样子,说到:“你忘记你学的接口吗?试试看,能不能用它去解决问题”。

此时你心里想到:“接口,接口这破玩意儿有啥用?”,以下引用的是本文真实的评论,哈哈!

有个问题想不明白,接口由不同的类去实现,我干嘛不直接写几个类,为什么非要实现接口,接口里面屁也没有,就定义了几个方法名。就好比人是一个类,你非要定义一个接口,规定了吃饭、拉屎、睡觉等等方法,然后去实现这些方法,有人用刀叉吃饭,有人用手抓,有人用筷子。我干嘛不直接印度人定义一个类,中国人定义一个类,美国人定义一个类呢?想不通,在我看来接口屁用没用。

那么,我们看看,怎样用接口去实现呢?小组长说,既然腾讯云和阿里云有不同的地方,是不是也有相同的地方,即共性的地方,我们把共性的地方,即接收一个号码参数,一个短信内容参数,最后发送出去。这样一个抽取成公共的接口,散落各个文件的100处代码调用,都直接调用接口,而不是具体的实现类(阿里云或腾讯云),这样,后续我们无论变成什么短信平台,都不会需要去找到这100处代码去做修改,因为他们依赖的是抽象的接口,不变的抽象。

下面是基于阿里云和腾讯云抽取出来的公共的短信接口CommonSmsService:

package com.lyj.demo.sms;
 
public interface CommonSmsServiceInterface {
    /**发送登录短信
     * @param phoneNumbers 号码
     * @param message 短信
     */
    void sendLoginSMS(String phoneNumbers,String message);
    /**
     * 忘记密码发送短信
     *
     * @param phoneNumbers 电话号码
     * @param message 短信内容
     */
    void sendForgetSMS(String phoneNumbers,String message) ;
 
    /**发送营销短信
     * @param phoneNumbers 电话号码
     * @param message 短信内容
     */
    void sendMarketingSMS(String phoneNumbers, String message);
}
阿里云实现CommonSmsService这个接口的类AliyunSmsServiceImpl:

package com.lyj.demo.sms;
 
public class AliyunSmsServiceInterfaceImpl implements CommonSmsServiceInterface {
    private final AliyunSMS aliyunSMS;
 
    public AliyunSmsServiceInterfaceImpl(AliyunSMS aliyunSMS) {
        this.aliyunSMS = aliyunSMS;
    }
 
    @Override
    public void sendLoginSMS(String phoneNumbers, String message) {
        aliyunSMS.sendLoginSMS(phoneNumbers, message);
    }
 
 
    @Override
    public void sendForgetSMS(String phoneNumbers, String message) {
        aliyunSMS.sendForgetSMS(phoneNumbers, message);
    }
 
    @Override
    public void sendMarketingSMS(String phoneNumbers, String message) {
        aliyunSMS.sendMarketingSMS(phoneNumbers, message);
    }
}


腾讯云实现 CommonSmsService这个接口的实现类TencentSmsServiceImpl(此处要注意腾讯云的参数是反的哈!实现类里就要把他纠正过来):

package com.lyj.demo.sms;
 
public class TencentSmsServiceInterfaceImpl implements CommonSmsServiceInterface {
    private final TencentSMS tencentSMS;
 
    public TencentSmsServiceInterfaceImpl(TencentSMS tencentSMS) {
        this.tencentSMS = tencentSMS;
    }
 
    @Override
    public void sendLoginSMS(String phoneNumbers, String message) {
        tencentSMS.sendLoginSMS(message,phoneNumbers);
    }
 
    @Override
    public void sendForgetSMS(String phoneNumbers, String message) {
        tencentSMS.sendForgetSMS(message,phoneNumbers);
    }
 
    @Override
    public void sendMarketingSMS(String phoneNumbers, String message) {
        tencentSMS.sendMarketingSMS(message.phoneNumbers);
    }
}


此时,分散在各个文件中的100处代码,就可以写成下面这样:

如果,你的小组长在“恶心”你,你再也不需要改100代码了,只需要改下面两行代码这样:

 此时,你还会觉得接口没有用吗?原来要改100处的任务,使用接口后,只需要改2处,而且大大减少修改代码导致的bug问题,此时,聪明的你,还会觉得【接口】是鸡肋了吗?

“依赖抽象,而不是具体实现”是面向对象编程中的一条核心原则,它强调在设计和实现软件系统时,应该依赖于抽象的接口或抽象类,而不是依赖于具体的实现类。这个原则有助于提高代码的灵活性、可维护性和可测试性。下面是这个原则的一些关键点:
 
1. 接口隔离:定义清晰的接口,使得客户端代码只依赖于它们需要的特定功能,而不是依赖于一个庞大的、多功能的类。
 
2. 开闭原则:软件实体应当对扩展开放,对修改封闭。这意味着当需要增加新功能时,可以通过继承或实现新的抽象接口来扩展系统,而不需要修改现有的代码。
 
3. 单一职责:每个类应该只有一个引起它变化的原因,这通常意味着每个类应该只负责一个具体的功能。
 
4. 替换性:由于依赖的是抽象,所以具体的实现可以被替换,只要它们遵循相同的接口或抽象类。
 
5. 解耦:抽象层次上的依赖关系使得各个组件之间的耦合度降低,从而更容易地进行修改和替换。
 
6. 控制反转:依赖抽象而不是具体实现是控制反转(IoC)的基础,其中对象的创建和它们之间的依赖关系由外部容器管理,而不是由对象自身管理。
 
7. 依赖注入:这是实现依赖抽象的一种方式,通过将依赖关系在运行时注入到对象中,而不是在对象内部创建。
 
通过遵循“依赖抽象,而不是具体实现”的原则,开发者可以构建出更加模块化、灵活和可维护的系统。这种设计方式在现代软件开发中非常普遍,特别是在使用面向对象编程和设计模式的场合。
基础不牢?新手不友好?无人带路?关注《扬俊的小屋》公众
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_19782019/article/details/80259836

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值