零、前言
《HeadFirstJava》是最通俗易懂也是最适合小白入门的JAVA书。
很久很久以前,我在学习《HeadFirstJAVA》时,尝试着把自己理解的内容写成文章,于是就有了《一个小白眼中的“面向对象”》,现在,书已经彻底读完了,所以要再写一些自己的体会。
在软件开发中,有这样一个规律:
- 绝对不重复造轮子,只要一段代码出现了两次,就一定要把它们共有的代码抽象出来。
我们可以从这个角度,来看一看对象、类、接口、继承之间的关系。
类和对象
我们已经知道,类是对象的模板,对象是类的克隆。
当我们需要创建一系列相同的对象时,就把它们共有的部分抽象出来,因此就有了类。
类和继承
(一)
开始之前,我们先想一想,所有的动物都有吃饭这个方法eat(),如果每种动物都单独写一个eat()方法,那就是许许多多相同的代码。
既然又出现了相同的代码,我们可不可以把它们抽象出来呢?可不可以写一个通用的类,包含了这些共同的方法,而所有具体的类都“克隆”这个通用的方法呢?
当然可以,于是就有了下图:
这就是继承,被继承的类称为父类,执行继承的类叫做子类,继承的作用在于,子类拥有了父类的所有属性和方法。
对于初学者来说听到这个词,可能会想到现实中的财产继承,但计算机的继承和这个并不一样,如果用一个更贴切的词语来形容计算机的“继承”,那应该是生物学中的“遗传”。
为什么说计算机的继承有点像“遗传”呢,因为并不是子类继承之后,父类就没有了,而是把父类完整的复制一份,无论发生多少次继承,父类都还是那个父类,一点也没有减少。
- 所以,子类可以获得父类的方法和属性。
(二)
虽然所有的动物都会吃,但并不是所有动物都会飞,如果想给鸟类添加fly()的方法,可以吗?
当然可以。
- 所以,子类可以定义自己独有的方法
(三)
如果所有陆生动物都会走路,但并不是所有的动物走路姿势都一样,那么可不可以只给几种特殊的动物定义单独的wolk(),而其他的动物使用父类的work()呢?
当然可以。
- 所以,子类可以覆盖父类的方法
因此又引出一个结论:
当对象被调用了一个方法,程序会先从对象所属的类中寻找此方法,如果没有,再去此类的父类中寻找,如果还没有,就一直向上寻找,直到找到最顶层的父类,如果还没有,就会报错。
(四)
我们继续第三个问题,所有的动物都有鸣叫的方法call(),但是所有的动物叫声都不一样,那么如果new一个Animal对象,它会怎么叫呢?
答案是:根本就不存在Animal这种对象,因此,有些类是不能new的。这种对象一旦new出来,不仅没有意义,而且很危险。
所以,必须要对这种特殊的类,做一个标注,使它不能new,但可以被继承。
这样,就有了抽象类,抽象类中至少有一个抽象方法。
抽象的,就是不具体的,这意味着只需要关注抽象方法的功能,而不关心它的具体实现方式。抽象方法只有方法名、返回值类型,而没有语句,所以它不能被执行。
抽象类只有被具体类继承,并且通过覆盖父类的方法,把抽象方法实现之后,才能被new出来。
因为上图中的Bird,覆盖了Animal的Call(),它不包括任何抽象方法,所以Bird是一个具体的类,可以被new出来。
类和接口
(五)
人类是哺乳类动物也是灵长类动物,那么可以同时继承两个类吗?
在回答问题之前,先来看一个例子:
播放器按照功能可以分为CD、VCD、DVD,他们都有播放方法Play()。
如果有一种新型的VCD和DVD的综合播放器,那应该怎么继承呢?
同时继承两个父类,看似这样没问题,但实际调用Play()的时候,到底调用哪个父类的Play()方法呢?
所以这是不行的,事实上,Java也是不允许这样做的。
那么如果有这种“继承”两个类的需求,怎么办呢?
有一种方法,就是把Play()变成抽象方法,让复合播放器实现这个抽象方法,即可。
但这样做,只是把多重继承变成了单一继承,虽然可用,但没有从根本上解决问题。
于是,接口出现了,接口的出现就可以解决多重继承的问题。
(六)
如果有一种宠物猫,它既有猫的属性,也有作为宠物的功能,按理说,它应该同时继承于Animal和Pet这两个类,可现实是不允许这么做。
如何用接口实现呢?——把Pet类的所有方法都变成抽象方法!而Animal不变。
这样,从理论上可以避免方法的冲突。为什么呢?因为实现抽象方法必须要覆盖,这样,即使父类和这个抽象类有重名方法,也会被子类覆盖掉。
但Pet毕竟还是个类啊,Java不允许继承两个类。
所以,什么是接口呢?接口就是100%抽象的类。接口只有抽象方法。
现在的Cat类依然可以继承Animal,但对于Pet来说,就不是继承了,而是实现了Pet接口。
由于接口实100%抽象的,所以永远也不会出现方法重名了。
用一句话概况就是,一个类只能继承另一个类,接口只能继承另一个接口,但类可以实现多个接口。
其实接口(interface)也属于类(class),那么他们有什么区别呢?
类是用来New对象的,接口是用来被实现的,所以当某个类实现了某个接口之后,它就具备了这个接口的所有功能。因此才说:接口实际上是一种协议、一种规范。
总结
让我们回到这个规律:
- 绝对不重复造轮子,只要一段代码出现了两次,就一定要把它们共有的代码抽象出来。
因为同样的对象出现了两次,所以把对象的共同点抽象出来,就有了类。
因为两个类有相同的功能,所以把相同的功能抽象出来,就有了继承。
因为要解决继承两个类的问题,所以把一个类抽象出来,就有了接口。
扩展
本文的内容大多是基于《Head First Java》精简出来的,因此比较基础,实际项目中,类和接口有它们特定的作用。
接口是图纸,是上层建筑,是从宏观上规划一个项目的构成。就像钱塘江大桥的修筑,当然不能从头开始一点一点的来搭建,而是先有图纸,再有零件。把一个项目分成几个大零件,最后再拼装起来。
类是方便量产零件的模具。当开发商拿到图纸之后,只看图纸造零件并不是很方便,生产效率也低。于是开发商在图纸的基础上进行加工,使其从扁平的图纸变成一个个活生生的模板,根据这个模板可以直接量产零件。这样就提高了效率。
回到计算机,实际的软件项目就是,先定接口,接口规范了某个方法的功能,但不关心具体实现方式。
然后就可以放心的分工了,由于接口是确定的,只要每个人的代码都遵循接口的规范,就可以保证合作编写的代码可以成功对接。
在这些接口上写出来的类,就可以满足项目的功能了。
所以类是量产的模具,接口除了解决多重继承之外,也是项目的图纸。