java基础-接口-接口的理解与使用

(先声明一下:以下学习和总结的接口内容是在jdk1.8版本之前的内容,因为jdk1.8之后对接口有改动并新增了许多特性,这些改动和新增特性会在下一篇文章中专门去学习和记录)

接口

一:接口的定义:一个完全抽象的类,即类中所定义的所有方法全是抽象方法,只有方法的声明,没有方法体。

二:接口的特点:

1.创建方式:创建一个接口,需要使用interface关键字来取代在创建类时所用的关键字class,这样创建出来的类便是接口,例如:public class A{...},这是一个类,public interface A{...},这就是一个接口。接口的本质还是一个类,只不过它是一个完全抽象的类。

2.接口的访问权限:接口有2种访问权限,即public和默认权限(包访问权限)。public接口对所有类公开,默认权限对同一包中的类公开。

3.接口的修饰权限:与抽象类一样,接口不能用final关键字修饰,final修饰的类不能被继承,定义接口的初衷就是为了让它被实现(继承),所以final不能修饰接口。

4.接口的实现(继承):当要继承一个类时,需要使用extends关键字,但是继承接口时,需要用implements关键字取代extends关键字,而且术语也变化了,即实现了一个接口而不是继承了一个接口。

5.接口中的域:接口中可以定义域,比如可以在接口中定义成员变量,但成员变量隐式的指定为static和final的,而且一般都是定义为public的。所以接口中的成员变量一般都是常量。

6.接口实例化:不能创建接口的实例对象,而且也不能在接口中定义构造器。并且定义一个没有任何抽象方法的接口也是允许的。

7.接口中的方法:接口中定义的全部方法必须是抽象方法,可以不用显示的写出abstract关键字,但它们是隐式的指定为abstract的,并且不能用static,final,private关键字修饰的,并且接口中所有抽象方法的访问权限都应该是public。

8.接口里的多重继承:接口是多实现的,即一个类可以同时实现多个接口,这与继承不同,一个类只能继承一个类,但是可以实现多个接口,并且这两种行为可以同时发生在一个类上面。即一个类可以继承另外一个类,同时实现多个接口。并且

9.接口的使用场景及使用接口的意义:接口用来建立类与类之间的协议。它最吸引人的原因就是允许一个接口拥有多个不同的具体实现。

接下来分别来测试和验证一下上面的几个特点:

第一点和第二点的测试:

在包外创建一个类来实现这2个接口试试

可以看到public接口InterfaceTest01可以在包外被实现,而默认权限的接口Test01则不能运用于包外。

测试第三点:接口的修饰权限,接口与抽象类一样,不能用final来修饰,final修饰的类表明这个类不能被继承,修饰接口表明这个接口不能被实现。而设计出抽象类与接口的初衷就是封装公用的动态绑定方法,运用动态绑定方法的多态性使其能有多个不同的具体表现,当调用者尝试使用这些方法时,只需要建立与接口或抽象类之间的耦合,而根本不需要关系方法的具体实现对象,这样不仅使类与类之间的耦合度大大减少,也使类和代码的组织关系更加清晰,便于进行维护以及拓展。final修饰抽象类或接口与这个初衷相违背,所以编译器是不允许这种情况发生的。

编译器报的错误很明显验证了这个结论。

第4点与第7点的验证:一个类可以继承一个抽象类,但是可以实现多个接口

先定义一个抽象类,再定义一个普通测试类,来测试一下这种情况:

可以看到,类TestClass01继承了抽象类AbstractTestClass01的同时又实现了Test01,InterfaceTest01这两个接口,理论上来说,还可以继续实现更多的接口,但是要说的是如果一个类同时继承了抽象类和实现了多个接口,那么它就得全部覆盖(重写)这些抽象类和接口中定义的全部抽象方法,否则,它也是一个抽象类,如下图所示:

不覆盖接口中的g()方法,那么只能定义这个类为抽象类。看下一个问题:抽象类可以实现接口吗?来验证一下:

可以看到。抽象类是可以实现接口的,并且抽象类中还可以不用覆盖接口中的抽象方法,甚至都不需要定义出这2个方法。那么在这个关系上,如果一个类再继承这个抽象类的话,它就必须覆盖抽象类中的抽象方法以及覆盖抽象类实现的接口中的所有抽象方法。如下面看到的一样:

接口可以继承抽象类吗?再来测试一下,重新定义一个抽象类AbstractClassTest02,让接口Test01,InterfaceTest01来继承一下它,看看编译器有报错吗?

可以发现,编译器说这里期望是一个接口,而不是一个抽象类,所以可以得出结论:接口不能继承抽象类。

再来看一个问题:接口能继承接口吗,或者说接口能实现接口吗?还是来测试一下:

可以看到,接口是可以继承接口的,将关键字改成implements试一下:

可以看到,接口与接口之间是继承关系的,不能用implements关键字,也就是说不能说一个接口实现了另外一个接口,只能说一个接口继承了另外一个接口,需要使用extends关键字来体现这种继承关系。

那一个接口可以继承多个接口吗?看下面的例子:

可以看到,接口之间的继承关系是多继承的,也就是说一个接口可以继承多个接口。

第四点和第七点的测试总结出来的话,就是指:

1.一个类可以(至多)继承另外一个类,同时实现多个接口,当有这种关系时,这个类必须覆盖掉它继承的类和它实现的接口中的所有抽象方法,否则,它也就是一个抽象类,必须用abstract关键字修饰。

2.一个抽象类可以实现多个接口,并且不必要在类中定义出或覆盖它所实现的接口中的抽象方法,当有另外的非抽象类继承这个抽象类时,需要覆盖掉这个抽象类以及抽象类实现的所有接口中的所有抽象方法。

3.一个接口不能继承一个抽象类,但可以继承多个其它接口,这个继承关系是用extends关键字来修饰的,接口与接口之间没有实现关系,不能使用implements关键字。

接下来验证第六点中的内容:接口不允许创建它的实例对象,而且也不允许定义它的构造器:

图中编译器报的错可以清晰的验证第六点中的内容。

接下来重点研究一下接口中的域。

三:接口中的域

接口中的域指的是接口中定义的成员变量,当在接口中定义成员变量时,成员变量自动是public static final修饰的。在定义时,编译器强制要求这么做,那么为什么需要这样去修饰接口中的成员变量呢?下面一一来解释一下:

定义接口的意义在于封装所有导出类中公有的属性,抽象出导出类中所公有的行为,给导出类去完善行为的细节。它就是这些导出类的一个规范。

public:接口中的成员变量是所有的导出类所公有的,所有的导出类对象都可以来使用它。当我们定义一个接口时,如果因为访问权限而导致某一子类不能使用它实现的接口中的公有成员变量,那么定义这样的接口将毫无意义。为了避免出现这样的情况,最好的解决办法就是将接口中的所有成员变量都定义为public的。java编译器也在我们定义时给出了管控,就算我们定义时不显示写出public关键字,编译器也会隐式的给我们加上去。

final:final修饰成员变量时,意味着这个变量是不可变的,对于基本类型变量来说,变量一旦初始化就不能改变它的值,对非基本类型和对象引用来说,一旦初始化就不能将该引用再次指向别的对象。为什么呢?想象一个场景:接口A中定义了一个成员变量a,B和C类都实现了这个接口,B某次使用了a,并改变了它的值,那么C下次使用了a时,发现a的值并不是它预期中的值,因为a已经被改变了,那么这样变量a出现在接口中就显得特别不合理了。所以,在接口中需要给公有成员变量加上final关键字,来让它一旦被初始化就不能被改变,并且这个初始化动作必须由接口自身来完成。

static:static修饰的成员变量意味着这个变量在内存中只有一份,它是和类绑定的,与类的实例对象无关,在该类的Class对象首次被加载时初始化一次,然后仅保存一份它的值到内存中。想象一个场景:如果类A同时实现了B接口和C接口,在B接口和C接口中都有定义一个变量a,那么A类在使用这个变量a时,是使用的接口B中的a的值还是C的呢?或许有人会说有三种方法可以得到:(1)在接口中定义一个getA(){...}方法来获取。但请注意,接口中的方法必须全部是抽象的,没有方法体,如果接口中允许这样做的话,那接口就是一个抽象类了。(2)创建接口B和C的实例对象,用它们的实例对象去获取。但接口是不允许创建实例对象的。如果可以的话,接口就是一个普通的类了。(3)将A的一个对象实例向上转型赋给它的基类引用,用这个基类引用去获取,获取完后再向下造型回来。这样一来,确实是可以获取到,但是如果我想同时获取B接口和C接口中的两个变量a的值呢?是不是需要创建两个A的实例对象去获取呢?如果A类是一个单例模式的类,只允许创建一个它的实例对象,这种情况下又该怎么办呢?所以以上三种方式都无法实质性的解决这个问题。这时,就只有一个办法了---将变量a定义为static的,如果是一个静态的变量的话,变量本身与接口的实例对象无关,只与类有关,在使用时用(类名.变量名)的方式去调用,这样既可以满足导出类的访问,又可以提供它想访问的正确的变量的值。完美的解决了上面的这个问题。同时,因为变量a是静态的,只有一份,所以它的值被存储在该接口的静态存储区域中,这样也节约了内存的使用。

通过上面的解析,也可以理解了接口中的域为什么要定义为public static final 修饰的了。再来看一个现象:

java要求接口中的成员变量必须在定义时就给他进行初始化。图中的b没有在定义时就初始化,因而报错了。所以接口中不允许出现“空白final”。

这时,我们可以发现,接口中的变量本身就是public static final修饰的,再加上要求在定义时便初始化,那这不就是一个常量了吗?

关于编译时常量和常量部分的内容,我前面的记录文章《java基础-复用类-final关键字的使用》中有学习过,想了解的可以点这里

确实,接口中的成员变量全是常量,且在定义常量名称的时候,常量名需要全部大写,如果有多个单词,单词之间需要用下划线隔开。

初始化接口中的域:

接口中的常量的初始化可以使用常量表达式来初始化,也可以使用非常量表达式来初始化。看下面的例子:

输出结果为:

可以看到,使用非常量表达式初始化时,它是在类第一次被加载的时候进行的或在类的任何静态常量首次被访问的时候进行的。

 

四:接口中的方法

接口中的方法默认是public abstract修饰的,并且不能用static,final,private修饰。

由于方法是public修饰,所以实现了接口的类中覆盖(重写)的方法的访问权限也必定是public修饰的。

关于方法的验证代码就不传了。在前面的抽象类中已经测试过了。

五:使用接口的意义

使用接口的核心意义:

1.允许一个接口拥有多个不同的具体实现,从而使导出类能向上转型为多个基类型。

2.防止创建该接口的对象。

3.使类与类之间的耦合度大大减少,也使类和代码的组织关系更加清晰,便于进行维护以及拓展。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值