赞
踩
官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
我的解释:接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。(JDK1.8之前可以这样理解)
就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。
为了声明一个接口,我们使用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平台自己验证一下:点击打开链接
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. 依赖注入:这是实现依赖抽象的一种方式,通过将依赖关系在运行时注入到对象中,而不是在对象内部创建。
-
- 通过遵循“依赖抽象,而不是具体实现”的原则,开发者可以构建出更加模块化、灵活和可维护的系统。这种设计方式在现代软件开发中非常普遍,特别是在使用面向对象编程和设计模式的场合。
1.《Java开发实战经典》 李兴华著 清华大学出版社
2.https://www.geeksforgeeks.org/interfaces-in-java 作者:Mehak Kumar. and Nitsdheerendra. 翻译:刘扬俊
第一条 本博客文章仅代表作者本人的观点,不保证文章等内容的有效性。
第二条 本博客部分内容转载于合作站点或摘录于部分书籍,但都会注明作/译者和原出处。如有不妥之处,敬请指出。
第三条 在征得本博客作者同意的情况下,本博客的作品允许非盈利性引用,并请注明出处:“作者:____转载自____”字样,以尊重作者的劳动成果。版权归原作/译者所有。未经允许,严禁转载。
第四条 对非法转载者,“扬俊的小屋”和作/译者保留采用法律手段追究的权利。
第五条 本博客之声明以及其修改权、更新权及最终解释权均属“扬俊的小屋”。
第六条 以上声明的解释权归“扬俊的小屋”所有。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。