与所有初学者一样,我(当然也是初学者)也认为“面向对象”一词是抽象的,何为对象,如何面向对象,又为何要面向对象。在这里,我想与大家探讨交流一些Java中面向对象的问题。
生活中有很多事物,例如:CPU(中央处理器)中高速移动的电子、CPU中正处理的信息、CPU、电脑、屋子、楼、镇、区县、市······这些能实际被我们(也就是用户)研究的客观存在的事物,就是对象(object)。这种对象可以具有形体、也可以是无形而抽象的,比如上述例子中CPU正处理的信息。可能许多人现在就在与这些对象打交道,而打交道的过程,就是与对象通信,也就是面向对象。
一个对象不是凭空创造的。假设我们手中有现成的零件,现在要求我们使用这些零件去组装成一款手机,在没有图纸的情况下显然是不现实的。而制作出这款手机的实体(也就是对象),需要一张供我们参考的图纸,这种图纸我们就称之为“类(class)”。将这个类转变为实实在在存在的东西,就叫做创建类的实例(instance),也称创建类的对象。而制作这款手机的图纸,我们就称为这款手机的类,比如“iPhone100类”的设计图纸,我们就称之为iPhone100类。
面向对象具有三要素:封装、继承、多态。又是三个抽象的名词,我们接下来分别介绍它们
首先是封装(encapsulation)。创建了一款手机的实例后,我们可以观察到,手机中有很多的元器件,有一些是外界能看到的,比如屏幕、摄像头等。也有一些我们看不到的,比如内部的处理器、集成电路等。这些不管能不能看到的,我们都称之为类中的成员变量。显然,屏幕外有玻璃盖板、摄像头外有保护玻璃,还有内部的集成电路由外壳保护。这些成员变量不希望我们通过直接手段去访问、修改它们,而对自己加装了保护措施,来限制我们去访问和修改它们。因为我们对它们直接的访问或修改,很有可能会损坏这个手机,使手机的运行出现问题。而这种对成员变量限制访问权限的行为,就是封装。
在程序中设计类时,应对各成员变量进行访问权限的限制,也就是封装,以达到保护成员变量数据、使程序更安全的目的。
那么大家可能会有疑问,既然这些成员变量被封装了,那我们如何去访问、修改、调用这些类变量呢?这里我们先扣一个字眼,限制访问权限中的“限制”,是在限制类的外部,也就是外部调用者,而不是限制类的内部。大家都知道,我们不能直接通过取出摄像头的方式调用摄像头,但我们可以通过手机内的相机程序去调用摄像头。而这种可以调用成员变量、完成用户与类的实例交互的途径,我们就称之为成员方法(member method)。
我们知道,“iPhone100类”只不过是各“iPhone类”中的一个种类,但是它仍具备了大多数“iPhone类”所具有的元器件(成员变量)和功能(成员方法)。类似地,“iPhone类”也具备“智能手机类”类的成员(我们将类的成员变量和成员方法统称为类的成员)。不过值得注意的是,即使“iPhone100类”衍生于“iPhone类”,“iPhone100类”也会有不同于“iPhone类”的独特成员(也就是我们经常看到的新款手机的新功能)。种类一词中“种”,是子代对于父代而言的,子代会继承父代的部分属性和行为习惯(成员),同时子代也会衍生出许多父代不具有的特别的成员。这就是我们接下来要介绍的继承(inheritance)。
继承就是子类继承父类中成员的过程。龙生龙、凤生凤、老鼠的儿子会打洞。完成继承后,父类中的成员(属性和方法)会被延续到子类中,让子类也具有父类的成员变量和成员方法,可以实现同父类的功能。我们接下来深入地理解继承:
①继承是具有传递性的,父类的子类也可以有子类,也就是我们生活中常见的爷爷辈、父亲辈、儿子辈、孙子辈;我们上述的“手机类”、“iPhone类”、“iPhone100类”······它们通过继承关系获取上一辈的属性和方法。
②子类可以拥有父类不具有的成员变量和成员方法。以小见大、通过孩子可以看出家长······都反映了子类对于父类的继承作用。但是,子类通过后天的特殊化,可以获得独特的、父类不具有的功能(方法)和属性(成员变量)。例如很多年轻人都有蓝牙耳机,而许多老一辈人没有,这就是独特的属性。而年轻人同时会使用和操作蓝牙耳机,而许多老一辈人不会,这就是独特的方法。
③一个父类可以有多个子类,而一个子类可能会有多个父类。这个是我们要着重注意的。我们可以将继承类比为“血缘关系”,那么一个子类只会有一个父类,但是一个父类可以有多个子类,这样多个子类都可以都可以共用父类的成员。在Java中,一个子类只能继承一个父类。但是在C++中,一个子类可以继承多个父类,这是与上文常识相违背的地方,不同的语言有各自的语言特色。
④子类实例和父类实例相同名称的成员变量和成员方法分别储存,互不干扰。
⑤在编写程序中,最顶端的父类通常为抽象类。抽象类可以简单理解为子类确定好了大致功能,而不去具体描述这些功能怎么实现的类。抽象类为子类确定了必须具有的方法,为子类的功能做了限定。比如“灯类”确定了灯具有照明的方法,但不具体描述照明方法如何实现,这种只声明不实现的方法我们称之为抽象方法。我们常见的有许多灯的子类的实例,比如吊灯、台灯、路灯······它们在“吊灯类”、“台灯类”、“路灯类”中实现了照明的功能,为这个照明的方法提供了具体的实现过程,这就是重写父类的方法。同时各子类在实现照明的过程中也具有了独特的属性和功能。
⑥有些父类的方法,子类可以重写(override)。重写的意思是从父类获取的同名方法可以在子类中实现或重新实现。实现的意思是将父类中声明的抽象方法赋予实际的实现过程,例如“灯类”声明了这个灯可以亮的方法,那么子类实现这个方法就是写出怎么亮的过程。而重新实现就是指在父类中已经实现了这个方法,但是子类就是想要与父类的实现途径有所不同,而重写同名方法的过程。我们由此可以知道,多个子类继承同一个父类时,可以同时重写父类的一个方法。假设,我们同时有一个名为“灯001”的灯,它是“台灯”的实例;也有一个名为“灯002”的灯,它是“吊灯”的实例。“灯001”和“灯002”都是“灯类”子类的实例。我们通过“灯类”来声明“灯001”和“灯002”,意思是我们只知道它们是灯,并不知道具体的种类时,我们要知道大类“灯类”的“灯001”和“灯002”的照明过程时如何实现的,会分别调用各类的“怎么亮的”的方法,这个过程就是多态(polymorphism)。
多态可以通俗的理解为给不同的实例发送同一条消息(通常表现为调用同名的方法),得到的结果不一样,具体不一样在哪取决于这些实例所属于的类,而这些类都与同一个父类有继承关系。或者说,这些类都派生自同一个父类。多态的存在让所有子类具有父类指定的方法的同时,为实现这种方法提供了各自独特化的实现过程。
上面我们解决了第一段中的前两个问题,即“何为对象”、“如何面向对象”,我们不禁要问,为什么要面向对象?
大家可以来结合上文我们说到的许多例子来思考这个问题。我们会发现,我们生活中存在无数的类,也就是无数的事物,这些事物可以根据条件的不同而普遍联系、产生效应。这些事物就是每一个类的实例化,比如我们是人类,也有非人类,我们都是动物类的子类,动物类、植物类和病毒类都是生物界类的范畴。我们人类可以通过一些手段(程序中的各种函数或运算符)与看似与我们毫无关系的类交互、产生作用,比如与计算机类、与纸类。决定能让我们与别的类产生作用的正是各个类的成员变量(属性)与功能(方法)。
不难看出,分类的思想是我们最常用的。类实例化后就是面向对象,各个对象具有自己类中的特征;每一个对象都有自己类中特有的方法,可以对外界的信号做出响应;对象之间可以通过改变条件取得联系,并在对象之间传递信号(一种典例是对象中含有引用别的对象的变量),并与别的对象产生作用。这些是单个对象的作用。还有继承对象的作用。我们可以将多个具有相同特征和方法的类归为一类,方便对各子类进行统一管理。
我们在写代码的过程中其实已经解除过一些对象。比如数组就是一个对象,而数组名是数组对象的引用,通过数组名可以找到数组的地址,从而修改数组对象的数据。在java和c++中,类的思想无所不在,我们通过深入研究这些面向对象的语言后将会感受到类的思想的强大之处。
(这些是本人对面向对象的一些个人见解,希望对各位对面向对象的理解有所帮助,如有错误欢迎指出)
下一次我们将研究java中实现面向对象的基本过程:)