接口的特点:
接口一般是通过interface
关键字来定义的,一个简单的接口定义就如下面的代码一样:
public interface advice { }
上面的接口是一个最简单的接口.往往我们的接口里面会包含很多的函数,就比方下面这个例子:
并且在这个接口里面我们不仅能够看到有多个函数,同时该接口里面还有自定义的变量.
这里还要提醒大家一点的就是:因为这里面不管是方法还是变量都是只通过public来进行修饰的,所以大家可能本能的就认为这些方法以及变量就是公开的,其实并不是这样的,接口里的所有方法其实都是抽象方法,接口里的所有变量也其实都是通过static final关键字来修饰的.这里我们可以通过查看类的结构就可以看到,就如下图所示:
大家其实仔细看变量名以及方法名的上面都是有字母的,其实大家现在想想应该能知道那些字母分表代表什么意思了.
SF就代表static final
A就代表abstract
所以并不是我们所想象的那样,知道了方法都是抽象方法了之后,大家这时候结合我们上面所讲的抽象方法的概念,大家应该也能基本了解接口中方法的特点了.方法我们理解完了,那现在我们再来讲讲接口中的变量吧.大家知道的,既然接口中的变量已经通过static final修饰了那么就以为着这个变量是无法重新进行赋值的,相当于是一个值已经恒定的变量了.
就如同我下面图里面所演示的一样:
这里可以给大家看一下错误的提示信息:
The final field advice.length cannot be assigned
意思就是我们上面讲的意思.所以一般在开发中是不在接口中定义变量的,如果需要在接口中定义变量并且来使用的话,那么很明显这个变量在项目中就是担当一个常量的作用.
其次我们在来看看我们在类中都是如何使用接口的:
我们都是通过implements关键字来实现我们的接口的.从上面的图中我们也可以看到如果一个类要使用接口的,那么就和一个类要继承抽象类一样,那么都是必须要实现该接口或者是抽象类中抽象方法的,否则也是会报错的.并且我们允许一个类可以实现多个接口,就如下图所示:
最后我们总结一下接口的特点有哪些:
接口必须通过interface关键字来修饰
接口中的方法都是抽象的,并且即使是变量也是无法更改的.
使用接口的类必须实现接口中的所有方法
一个类可以实现多个接口,并且需要实现这些接口中的所有方法.
到这里我们基本上就已经了解了抽象类以及接口的基本特点了.那么接下来就是我们的重点了:他们两者之间到底什么样的关系呢?让我们接着往下看.
抽象类与接口的区别
如果上面的内容你都已经认真看过之后,不知道大家是否会有这样的疑问:抽象类和接口真的好像啊,都有抽象方法,一般的类继承抽象类或者是实现接口的时候,同样都需要实现他们两者的抽象方法.
当初我看网上的文章基本都没有说到他们的内核问题上面,所以当时自己一直都有这样的疑惑.所以不知道大家有没有这样的疑惑.如果有的话,就请继续看我下面的内容,相信一定可以给你一个很好的解答.
首先我们的确需要承认的就是其实接口的确就是从抽象类中分离出来的,所以接口很有很多抽象类的特征.所以大家才会觉得接口与抽象类非常的相似.但是他们两者适用的的范围其实是不一样的.接口从抽象类中分离出来,肯定不是为了实现和抽象类同样的功能的.接下来我们详细的来讲解.
上面我们说过了抽象类是所有具体的个体类所共有的特征,所以我们是不是可以用下面的图来描述个体类与抽象类的关系呢,看下图:
从上面的图我们基本能得出这样的结论:抽象类与个体类是从属的关系.而且我们之前说过个体类实现抽象类的时候说的是继承,通过继承这个关键词大家也能够理解为什么是从属关系了.看完抽象类与个体类的关系之后我们再来看看接口与我们类的关系.
上面我们讲解接口的时候曾经说过,接口时用来扩展我们的系统或者是程序的.所以接口和我们的类的关系是不是可以用下面的图来描述呢,如下图:
从上图中我们就既可以看出来接口和我们的个体类的关系是需不需要的关系.如果一个个体类本身就应该有这个功能,那么很显然我们就需要为这个类实现相应的接口并且上面我们也说过一个类是可以实现多个接口的,正就刚好表现了需不需要的概念.只要我想,我就可以全都需要.
这就是抽象类与接口的第一个区别.为了方便大家更好的理解这个概念.我们还是举上面我们关于狗的例子.
现在我们我们现在有两个抽象类分别为Dog:
public abstract class Dog { public abstract void eat(); public abstract void wang(); public abstract void sleep(); }
Cat:
public abstract class Cat { public abstract void eat(); public abstract void miao(); public abstract void sleep(); }
假设我们需要创建一个Husky(哈士奇)的类,那么首先我们就需要先选择我们该将这个类继承上面我们定义的那个抽象类呢?很明显Husky是属于Dog的而并不是属于Cat的范畴.所以这一步不仅解释了抽象类与个体类是从属的关系,并且还解释了类只能继承一个抽象类.
接下来我们继承完相应的抽象类之后,我们首先就需要先实现抽象类中的抽象方法,代码如下:
public class Husky extends Dog{ @Override public void eat() { // TODO Auto-generated method stub System.out.println("邋里邋遢哈士奇,到处拆家惹人厌"); System.out.println("吃饭?没空吃饭,拆家呢!"); } @Override public void sleep() { // TODO Auto-generated method stub System.out.println("睡觉?家还没拆完,睡什么觉!"); } @Override public void wang() { // TODO Auto-generated method stub System.out.println("呜呜呜....呜呜呜呜呜呜....."); } }
这些就是我们哈士奇的基本特征吗,很显然哈士奇和其他的狗有一个基本的区别就是哈士奇会:
所以很显然这是哈士奇的一个最明显的特征同时也是哈士奇一定会的一项技能,所以我们需要为哈士奇这个类扩展这个功能,所以这时候我们就创建一个叫做DestroyHome
的接口:
public interface DestroyHome { void destroyhome(); }
这时候我们重新让Husky实现这个接口:
public class Husky extends Dog implements DestroyHome{ @Override public void eat() { // TODO Auto-generated method stub System.out.println("邋里邋遢哈士奇,到处拆家惹人厌"); System.out.println("吃饭?没空吃饭,拆家呢!"); } @Override public void sleep() { // TODO Auto-generated method stub System.out.println("睡觉?家还没拆完,睡什么觉!"); } @Override public void wang() { // TODO Auto-generated method stub System.out.println("呜呜呜....呜呜呜呜呜呜....."); } @Override public void destroyhome() { // TODO Auto-generated method stub System.out.println("你还想有家?我给你全拆光"); } }
到这里我们关于Husky 就基本上编写完毕了.在这个过程中大家可以更加的理解抽象类是一个属不属于的问题,接口是一个需不需要的问题.
其次就是在代码的编写上面,虽然两者本质上都是抽象的,但是抽象类属于是显式抽象,接口属于是隐式抽象.这个很好理解.抽象类的一个硬性条件就是必须要有abstract关键字的修饰,而接口则不需要我们将abstract这个关键字显示的编写在我们的类以及方法名的前面.
接着就是在设计模式的层面上,还是像我们上面说的那样,抽象类是所有具体个例都抽象出来的所有具体个例所拥有的统一的类.意思就是具体个例的一般性行为,抽象类中都会有.还是拿我们的狗来举例:
世界上有很多种类的狗,有金毛,边牧,拉布拉多,哈士奇等,他们的行为一定会因为品种的问题存在着很大的差异,就比如说哈士奇,他的拆家属性肯定是点满了的.但是呢这些不同种类的狗都会有狗的一些共性,就比如说吃,喝,睡觉.这些行为都是狗的一些共性,所以我们就可以直接在Dog这个抽象类里面直接定义这些方法,然后这些个例只需要继承抽象类实现这些方法即可.不用在单独在每个类里面再创建这些行为,再实现.就好比下图所示:
所以说抽象类就像是一个模板,他是所有具体个例的母版.所有的具体个例都会模仿抽象类的行为.并且假设所有的个例的某个行为都发生了变化,那么我们就只需要通过修改模版即抽象类即可解决问题.
但是接口和抽象类却有所不同.就像我们上面说的一样,接口是用来扩展我们的系统或者程序的,说以很显然接口属于是一种个性化定制服务,所接口一旦发生了改变的话,那么很显然只要是实现了该接口的类就全部需要进行修改.就比方下图演示的过程:
假设现在我们把接口中的play方法删掉了,改成了amuse方法,那么很显然所有只要实现了这个该接口的类都需要删掉play方法,并且实现amuse方法,入下图所示:
所以很显然接口属于是一种辐射状的设计,属于是一点向多点的扩散.这和我们的抽象类恰恰相反.但是不知道大家有没有想过是什么原因造成了这样的状况呢?
其实这个问题是很简单的.大家还记不记得我们上面讲过的.抽象类中即可以包含抽象方法,同时也可以包含一般方法.相反的接口中的方法默认都是抽象方法.正因为抽象类中可以存在一般方法,所以抽象类可以直接修改本身的方法而不需要在修改其他继承该抽象类中的方法.接口就不行了,因为全是抽象方法,所以一旦需要修改就只能修改每一个类中的方法.
最后我们总结一下抽象类与接口的区别:
抽象类是显式抽象,接口是隐式抽象
抽象类中既可以有抽象方法,也可以有一般方法,但是接口中的方法默认都是抽象的
抽象类解决的是"是不是"的问题,接口解决的是"有没有"的问题
一个类只能继承一个抽象类,但是能实现多个接口
最后的最后我们还是通过一个例子来帮助我们收官.网上经常讲的例子就是防盗门,但是我们本片文章一直再举狗的例子,所以最后一个例子我们还是来举狗的例子
好了话不多少,正式开始我们最后一个例子:
假设我们需要定义一个叫做Husky(哈士奇)的类,这个类现在需要实现这么几个功能:吃饭,睡觉,喝水,拆家,发疯.如何通过抽象类Dog或者接口Behavior来实现呢?
这里假设我们将所有的方法都定义在了抽象类Dog中.代码如下:
public abstract class Dog { public abstract void eat(); public abstract void sleep(); public abstract void drink(); public abstract void destoryhome(); public abstract void crazy(); }
很明显我们值需要通过让我们的Husky
继承该抽象类并且实现里面的抽象方法就行了.代码如下:
public class Husky extends Dog{ @Override public void eat() { // TODO Auto-generated method stub } @Override public void sleep() { // TODO Auto-generated method stub } @Override public void drink() { // TODO Auto-generated method stub } @Override public void destoryhome() { // TODO Auto-generated method stub } @Override public void crazy() { // TODO Auto-generated method stub } }
这样很显然也能够直接完成我们的目标,但是这里会有一个问题那就是当我们创建其他的狗的品种类时,就比如金毛,边牧的时候,很明显我们也是需要去继承Dog这个抽象类的,但是这时候我们会发现,这个抽象类中的两个方法是多余的,因为金毛和边牧基本上不会拆家,发疯,所以很明显这两个函数就是多余的.只要继承了这个抽象类,那么就必须要是实现这两个方法.所以虽然这种方案可以完成我们的任务,但是很明显不利于我们之后项目的扩展.
这时候有小伙伴可能会说,那我们直接在接口中定义这些方法不就行了.大家想一想就会发现,其实会出想我们上面遇到的同样的问题.
不知道大家这时候是不是已经有思路了呢.这时候结合我们上面所说的.抽象类是所有个例的共性,接口属于是个性化定制的范畴.看到这两句话,相信大家已经有了答案. 最好的方案应该是这样设计:
Dog抽象类:
public abstract class Dog { public abstract void eat(); public abstract void sleep(); public abstract void drink(); }
Behavior
接口:
public interface Behavior{ void destoryhome(); void crazy(); }
最后我们的Husky
只需要这样继承Dog
抽象类以及实现Behavior
接口即可,代码如下:
public class Husky extends Dog implements Behavior{ @Override public void eat() { // TODO Auto-generated method stub } @Override public void sleep() { // TODO Auto-generated method stub } @Override public void drink() { // TODO Auto-generated method stub } @Override public void destoryhome() { // TODO Auto-generated method stub } @Override public void crazy() { // TODO Auto-generated method stub } }
这样当我们创建其他类继承Dog的时候吧就不用再去是此案那两个多余的方法了.并且接口也是定制化,适用于所有包含拆家以及发疯的动物.这样就能醉倒更好的扩展我们的程序了.
到这里我们关于抽象类以及接口的讲解就已经全部讲解完毕了,如果觉得文章不错或者觉得UP写的还可以的话,可以关注我的公众号:萌萌哒的瓤瓤.