当前位置:   article > 正文

JAVA基础——接口(全网最详细教程)_java接口怎么写

java接口怎么写

                                   Java基础——接口


接口概念


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

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

接口的特点


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

  • 接口指明了一个类必须要做什么和不能做什么,相当于类的蓝图。
  • 一个接口就是描述一种能力,比如“运动员”也可以作为一个接口,并且任何实现“运动员”接口的类都必须有能力实现奔跑这个动作(或者implement move()方法),所以接口的作用就是告诉类,你要实现我这种接口代表的功能,你就必须实现某些方法,我才能承认你确实拥有该接口代表的某种能力。
  • 如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类
  • 一个JAVA库中接口的例子是:Comparator 接口,这个接口代表了“能够进行比较”这种能力,任何类只要实现了这个Comparator接口的话,这个类也具备了“比较”这种能力,那么就可以用来进行排序操作了。

为什么要用接口


  1.     接口被用来描述一种抽象。
  2. 因为Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限
  3. 接口也被用来实现解耦。
  4. 接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非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平台自己验证一下:点击打开链接

关于接口的几个重点


  1. 我们不能直接去实例化一个接口,因为接口中的方法都是抽象的,是没有方法体的,这样怎么可能产生具体的实例呢?但是,我们可以使用接口类型的引用指向一个实现了该接口的对象,并且可以调用这个接口中的方法。因此,上图中最后的方法调用我们还可以这样写:(实际上就是使用了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流。

接口在生活中的思想体现


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

佳能(Canon)EOS 80D 单反相机 单反套机(EF-S 18-200mm f/3.5-5.6 IS 单反镜头)

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

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

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

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

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

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

接口在编码中的实践


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

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

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

  1. package com.lyj.demo.sms;
  2. /**
  3. * 阿里云短信实现类
  4. */
  5. public class AliyunSMS {
  6. /**发送登录短信
  7. * @param phoneNumbers 电话号码
  8. * @param message 短信内容
  9. */
  10. void sendLoginSMS(String phoneNumbers,String message) {
  11. System.out.println("【阿里云】登录短信发送给"+phoneNumbers+":"+message);
  12. }
  13. /**
  14. * 忘记密码发送短信
  15. *
  16. * @param phoneNumbers 电话号码
  17. * @param message 短信内容
  18. */
  19. void sendForgetSMS(String phoneNumbers,String message) {
  20. System.out.println("【阿里云】忘记密码发送给"+phoneNumbers+":"+message);
  21. }
  22. /**发送营销短信
  23. * @param phoneNumbers 电话号码
  24. * @param message 短信内容
  25. */
  26. void sendMarketingSMS(String phoneNumbers, String message) {
  27. System.out.println("【阿里云】营销短信发送给"+phoneNumbers+":"+message);
  28. }
  29. }

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

  1. 【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
  2. 【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
  3. 【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!
  4. 【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
  5. 【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
  6. 【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!
  7. 【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
  8. 【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
  9. 【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!
  10. 【阿里云】登录短信发送给15067471274:您的登录短信验证码为:231453
  11. 【阿里云】忘记密码发送给15412345678:您的忘记密码验证码为:341531
  12. 【阿里云】营销短信发送给13875232346:今晚20点优惠多多,限时抢购!

正当你准备下班和女朋友约饭的时候,小组长来了,说:“完成的不错,但是领导觉得阿里云太贵了,我们还是准备用腾讯云,把阿里云换成腾讯云吧!明天就要上线哦!”

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

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

  1. package com.lyj.demo.sms;
  2. /**
  3. * 腾讯云短信实现类
  4. */
  5. public class TencentSMS {
  6. /**
  7. * /**发送登录短信
  8. *
  9. * @param message 短信内容
  10. * @param phoneNumbers 电话号码
  11. */
  12. void sendLoginSMS(String message, String phoneNumbers) {
  13. System.out.println("【腾讯云】登录短信发送给"+phoneNumbers+":"+message);
  14. }
  15. /**
  16. * 忘记密码发送短信
  17. *
  18. * @param message 短信内容
  19. * @param phoneNumbers 电话号码
  20. */
  21. void sendForgetSMS(String message, String phoneNumbers) {
  22. System.out.println("【腾讯云】忘记密码发送给"+phoneNumbers+":"+message);
  23. }
  24. /**
  25. * 发送营销短信
  26. *
  27. * @param message 短信内容
  28. * @param phoneNumbers 电话号码
  29. */
  30. void sendMarketingSMS(String message, String phoneNumbers) {
  31. System.out.println("【腾讯云】营销短信发送给"+phoneNumbers+":"+message);
  32. }
  33. }

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

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

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

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

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

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

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

  1. package com.lyj.demo.sms;
  2. public interface CommonSmsServiceInterface {
  3. /**发送登录短信
  4. * @param phoneNumbers 号码
  5. * @param message 短信
  6. */
  7. void sendLoginSMS(String phoneNumbers,String message);
  8. /**
  9. * 忘记密码发送短信
  10. *
  11. * @param phoneNumbers 电话号码
  12. * @param message 短信内容
  13. */
  14. void sendForgetSMS(String phoneNumbers,String message) ;
  15. /**发送营销短信
  16. * @param phoneNumbers 电话号码
  17. * @param message 短信内容
  18. */
  19. void sendMarketingSMS(String phoneNumbers, String message);
  20. }

阿里云实现CommonSmsService这个接口的类AliyunSmsServiceImpl:

  1. package com.lyj.demo.sms;
  2. public class AliyunSmsServiceInterfaceImpl implements CommonSmsServiceInterface {
  3. private final AliyunSMS aliyunSMS;
  4. public AliyunSmsServiceInterfaceImpl(AliyunSMS aliyunSMS) {
  5. this.aliyunSMS = aliyunSMS;
  6. }
  7. @Override
  8. public void sendLoginSMS(String phoneNumbers, String message) {
  9. aliyunSMS.sendLoginSMS(phoneNumbers, message);
  10. }
  11. @Override
  12. public void sendForgetSMS(String phoneNumbers, String message) {
  13. aliyunSMS.sendForgetSMS(phoneNumbers, message);
  14. }
  15. @Override
  16. public void sendMarketingSMS(String phoneNumbers, String message) {
  17. aliyunSMS.sendMarketingSMS(phoneNumbers, message);
  18. }
  19. }

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

  1. package com.lyj.demo.sms;
  2. public class TencentSmsServiceInterfaceImpl implements CommonSmsServiceInterface {
  3. private final TencentSMS tencentSMS;
  4. public TencentSmsServiceInterfaceImpl(TencentSMS tencentSMS) {
  5. this.tencentSMS = tencentSMS;
  6. }
  7. @Override
  8. public void sendLoginSMS(String phoneNumbers, String message) {
  9. tencentSMS.sendLoginSMS(message,phoneNumbers);
  10. }
  11. @Override
  12. public void sendForgetSMS(String phoneNumbers, String message) {
  13. tencentSMS.sendForgetSMS(message,phoneNumbers);
  14. }
  15. @Override
  16. public void sendMarketingSMS(String phoneNumbers, String message) {
  17. tencentSMS.sendMarketingSMS(message.phoneNumbers);
  18. }
  19. }

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

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

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

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

基础不牢?新手不友好?无人带路?关注《扬俊的小屋》公众号吧!


参考资料


1.Java开发实战经典》 李兴华著  清华大学出版社

2.https://www.geeksforgeeks.org/interfaces-in-java  作者:Mehak Kumar. and Nitsdheerendra.   翻译:刘扬俊

博客文章版权说明


第一条 本博客文章仅代表作者本人的观点,不保证文章等内容的有效性。

第二条 本博客部分内容转载于合作站点或摘录于部分书籍,但都会注明作/译者和原出处。如有不妥之处,敬请指出。

第三条 征得本博客作者同意的情况下,本博客的作品允许非盈利性引用,并请注明出处:“作者:____转载自____”字样,以尊重作者的劳动成果。版权归原作/译者所有。未经允许,严禁转载

第四条 对非法转载者,“扬俊的小屋”和作/译者保留采用法律手段追究的权利

第五条 本博客之声明以及其修改权、更新权及最终解释权均属“扬俊的小屋”。

第六条 以上声明的解释权归扬俊的小屋所有。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/881960
推荐阅读
相关标签
  

闽ICP备14008679号