Java—面向对象设计—类和对象

理解面向对象程序设计

面向对象程序(Object-oriented programming,OOP)设计是继面向过程又一具有里程碑意义的编程思想,是现实世界模型的自然延伸。下面从结构化程序设计说起,逐步展示面向对象程序设计。

结构化程序设计简介

早期的程序设计,大量使用共享变量(全局变量)和GOTO语句,这使得代码结构比较混乱,不容易改错和复用,后来有人证明所有的有意义的程序流程都可以使用顺序、选择和循环来实现,并由此提出结构化程序设计。其概念最早由E.W.Dijikstra在1965年提出的,是软件发展的一个重要的里程碑。它的主要观点是采用自顶向下、逐步求精及模块化的程序设计方法,使用三种基本控制结构构造程序,任何程序都可由顺序、选择、循环这三种基本控制结构来构造。

结构化程序设计主要强调的是程序的易读性。在该程序设计思想的指导下,编程基本是通过写不同目的的函数/过程来实现,故又称为“面向过程编程(ProcedureOriented Programming,POP))。面向过程开发方式是对计算机底层结构的一层抽象,它把程序的内容分为数据和操纵数据的操纵两个部分。这种编程方式的核心问题是数据结构和算法的开发和优化。

Java—面向对象设计—类和对象
从上面的分析可知,前面章节的所有的例子虽然用了Java中class来“包装”,但本质上都是“面向过程”的思路来解决问题,因为前面的范例中没有任何“对象”存在,只存在解决问题的“方法(method)”,脱离对象的方法其实就是面向过程程序设计中的“函数(function)”。

2. 两种编程范式之间的联系

面向对象是在面向过程的基础上发展而来的,只是添加了它独有的一些特性。面向对象程序中的对象就是由数据和方法构成,所以完整的面向对象概念应该是,

对象 = 数据 + 方法

更进一步的可以描述为,

程序 =对象 + 消息传递 = (数据 + 方法) +消息传递

面向对象的基本概念

将具有相同属性及相同行为的一组对象称为类(class)。广义地讲,具有共同性质的事物的集合就称为类。在面向对象程序设计中,类是一个独立的单位,它有一个类名,其内部包括成员变量,用于描述对象的属性;还包括类的成员方法,用于描述对象的行为。在Java程序设计中,类被认为是一种抽象的数据类型,这种数据类型不但包括数据,还包括方法,这大大地扩充了数据类型的概念。

类是一个抽象的概念,要利用类的方式来解决问题,必须用类创建一个实例化的对象,然后通过对象去访问类的成员变量,去调用类的成员方法来实现程序的功能。就如同“汽车”本身是一个抽象的概念,只有使用了一辆具体的汽车,才能感受到汽车的功能。

一个类可创建多个类对象,它们具有相同的属性模式,但可以具有不同的属性值。Java程序为每一个对象都开辟了内存空间,以便保存各自的属性值。

对象

对象(object)是类的实例化后的产物。对象的特征分为静态特征和动态特征两种。静态特征指对象的外观、性质、属性等。动态特征指对象具有的功能、行为等。客观事物是错综复杂的,但人们总是从某一目的出发,运用抽象分析的能力,从众多的特征中抽取最具代表性、最能反映对象本质的若干特征加以详细研究。

人们将对象的静态特征抽象为属性,用数据来描述,在Java语言中称之为变量,将对象的动态特征抽象为行为,用一组代码来表示,完成对数据的操作,在Java语言中称之为方法(method)。一个对象由一组属性和一系列对属性进行操作的方法构成。

在现实世界中,所有事物都可视为对象,对象是客观世界里的实体。而在Java里,“一切皆为对象”,它是一门面向对象的编程语言,面向对象(Object-Oriented)的核心就是对象。要学好Java,大家需要学会使用面向对象的思想来思考问题和解决问题。

类和对象的关系

面向对象的编程思想力图使在计算机语言中对事物的描述与现实世界中该事物的本来面目尽可能地一致,类和对象就是面向对象方法的核心概念。类是对某一类事物的描述,是抽象的、概念上的定义;对象是实际存在的该类事物的个体,因而也称作实例(instance)。下图所示是一个说明类与对象关系的示意图。

Java—面向对象设计—类和对象
再举一个例子来说明类与对象的关系。17世纪德国著名的哲学家、数学家莱布尼茨(Leibniz,1646年—1716年)曾有个著名的哲学论断:“世界上没有两片完全相同的树叶。” 这里,我们用“类”与“对象”的关系来解释:类相同——它们都叫树叶,而对象各异——树叶的各个属性值(品种、大小、颜色等)是有区别的,如上图所示。从这个案例也可以得知,类(树叶)是一个抽象的概念,它是从所有对象(各片不同的树叶)提取出来的共有特征描述。而对象(各片具体的不同树叶)则是类(树叶这个概念)的实例化。

类的声明与定义

在使用类之前,必须先声明它,然后才可以声明变量,并创建对象。类声明的语法如下。

Java—面向对象设计—类和对象
程序首先用class声明了一个名为Person的类,在这里Person是类的名称。

第3、4行先声明了两个属性(即描述数据的变量)name和age,name为String(字符串类型)型, age为int(整型)型。

第5~8行声明了一个talk()方法——操作数据(如name和age)的方法,此方法用于向屏幕打印信息。为了更好地说明类的关系,请参看下图。

Java—面向对象设计—类和对象
对一个类定义而言,构造方法(constructor,又称构造器或构造函数)、属性和方法是三种最常见的成员,它们都可以定义零个或多个。如果三种成员都只定义零个,那实际上是定义了一个空类,那就失去了定义类的意义了。

类中各个成员之间,定义的先后顺序没有任何影响。各成员可相互调用,但值得注意的是,static修饰的成员不能访问没有static修饰的成员。

属性用于定义该类或该类的实例所包含的各种数据。方法则用于定义类中的行为特征或功能实现(即对数据的各种操作)。构造方法是一种特殊的方法,专用于构造该类的实例(如实例的初始化、分配实例内存空间等),Java语言通过new关键字来调用构造方法,从而返回该类的实例。

定义一个类后,就可以创建类的实例了,创建类实例通过new关键字完成。下面通过一个实例讲解如何定义并使用类。

类的定义使用(ColorDefine.java)。

Java—面向对象设计—类和对象
在ColorDefine这个类中,在第03行定义了一个String类型的属性color,并赋初值“黑色”。在第05行~第08行,定义了一个普通的方法getMes(),其完成的功能是向屏幕输出字符串“定义类”。第10行~15行,定义了一个公有访问的静态方法——main方法。在main方法中,代码第12行中,定义了ColorDefine的对象b,第13行输出了对象b的数据成员color,第14行调用了对象的方法getMes()。

还可以看出,在类ColorDefine中,没有构造方法(即与类同名的方法)。但事实上,如果用户没有显式定义构造方法,Java编译器会提供一个默认的无参构造方法。

类的属性

类的基本组成部分包括属性和方法。

通过前面的学习,其实大家对方法这个概念并不陌生。例如,在前面内容中,基本上每个范例都使用了System.out.println()语句,那么它代表什么含义呢实上,System是系统类(class),out是标准输出对象(object),而println()是对象out中的一个方法(method)。这句话的完整含义就是调用系统类System中的标准输出对象out中的方法println()。

一言蔽之,方法就是解决一类问题的步骤的有序组合。由于它涉及的概念很多,我会在后期文章详细探讨这个概念。这里仅做简单的提及,让大家有个初步的认知。

下面我们先来讲解类的属性,类的属性也称为字段或成员变量,不过习惯上将它称为属性。

属性的定义

类的属性是变量。定义属性的语法如下。

Java—面向对象设计—类和对象
Java—面向对象设计—类和对象
代码第03-04行,定义了两个String类型的属性a和b,由于它们是静态的,所以它们是属于类的,也就是属于所有这个类定义的对象共有的,所有对象看到的静态属性值是相同的。

代码第06-07行,定义了两个String类型的属性c和d,由于它们是非静态的,所以它们是属于这个类所定义的对象私有的,每个对象都有这个属性,且它们各自的属性值可不同。

代码第09-14行,定义了静态方法块,它没有名称。使用static关键字加以修饰并用大括号“{ }”括起来的代码块称为静态代码块,用来初始化静态成员变量。如静态变量b被初始化为”string-b”。

代码第23-28行,定义了一个构造方法usingAttribute (),在这个方法中,使用了类中的各个属性。构造方法与类同名,且无返回值(包括void),它的主要目的是创建对象。这里仅是为了演示,才使用了若干输出语句。实际使用过程中,这些输出语句不是必需的。

代码30-37行,定义了公有方法print(),用于打印所有属性值,包括静态成员值。

代码39-45行,定义了常见的主方法main(),在这个方法中,第44行使用关键字new和构造方法usingAttribute ()来创建一个匿名对象。

由输出结果可以看出,Java类属性和对象属性的初始化顺序如下。

⑴ 类属性 (静态变量) 定义时的初始化,如范例中的 static String a = “string-a”。

⑵ static 块中的初始化代码,如范例中的 static {} 中的 b = “string-b”。

⑶ 对象属性 (非静态变量) 定义时的初始化,如范例中的 String c = “stirng-c”。

⑷ 构造方法 (函数) 中的初始化代码,如范例构造方法中的 d = “string-d”。

当然这里只是为了演示Java类的属性和对象属性的初始化顺序。在实际的应用中,并不建议在类中定义属性时实施初始化,如例子中的字符串变量“a”和“c”。

注意,被static修饰的变量称为类变量(class’s variables),它们被类的实例所共享。也就是说,某一个类的实例改变了这个静态值,其他这个类的实例也会受到影响。而成员变量(member variable)则是没有被static修饰的变量,为实例所私有,也就是说,每个类的实例都有一份自己专属的成员变量,只有当前实例才可更改它们的值。

static是一个特殊的关键字,其在英文中直译就是静态的意思。它不仅用于修饰属性(变量),成员,还可用于修饰类中的方法。被static修饰的方法,同样表明它是属于这个类共有的,而不是属于该类的单个实例,通常把static修饰的方法也称为类方法。

对象的声明与使用

在上述范例中,已创建好了一个Person的类,相信类的基本形式读者应该已经很清楚了。但是在实际中单单有类是不够的,类提供的只是一个模板,必须依照它创建出对象之后才可以使用。

对象的声明

下面定义了由类产生对象的基本形式。

Java—面向对象设计—类和对象
当然也可以用下面的这种形式来声明变量。
Java—面向对象设计—类和对象
从图中可以看出,当语句执行到Person p1的时候,只是在“栈内存”中声明了一个Person对象p1的引用,但是这个时候p1并没有在“堆内存”中开辟空间。对象的“引用”本质上就是一个对象在堆内存的地址,所不同的是,在Java中,用户无法向C/C++那样直接操作这个地址。

本质上,“new Person()”就是使用new关键字,来调用构造方法Person(),创建一个真实的对象,并把这个对象在“堆内存”中的占据的内存首地址赋予p1,这时p1才能称为一个实例化的对象。

这里做个对比来说明“栈内存”和“堆内存”的区别。在医院里,为了迎接一个新生命的诞生,护士会先在自己的登记本上留下一行位置,来记录婴儿床的编号,一旦婴儿诞生后,就会将其安置在育婴房内的某个婴儿床上。然后护士就在登记本上记录下婴儿床编号,这个编号不那么好记,就给这个编号取个好记的名称,例如p1,那么这个p1(本质上就为婴儿床编号)就是这个婴儿“对象”的引用,找到这个引用,就能很方便找到育婴房里的婴儿。这里,护士的登记表就好比是“栈内存”,它由护士管理,无需婴儿父母费心。而育婴房就好比是“堆内存”,它由婴儿爸妈显式申请(使用new操作)才能有床位,但一旦使用完毕,会由一个专门的护工(编译器)来清理回收这个床位——在Java中,有专门的内存垃圾回收(Garbage Collection,GC)机制来负责回收不再使用的内存。

对象的使用

如果要访问对象里的某个成员变量或方法,可以通过下面的语法来实现。

Java—面向对象设计—类和对象
因此若想将Person类的对象p中的属性name赋值为“张三”,年龄赋值为25,则可采用下面的写法。
Java—面向对象设计—类和对象
对于对象属性和方法点操作符“.”,这里建议大家直接读成 “的”,例如,p1.name = “张三”,可以读成“p1的name被赋值为张三”。再例如,“p1.talk()”可以读成“p1的talk()方法”。这样读是有原因的: 点操作符“.”对应的英文为“dot [d]”,通常“t”的发音弱化而读成“[d]”(大家可以尝试用英文读一下sina.com来体会一下),而“[d”的发音很接近汉语“的”的发音[de],如下图所示。此外,“的”在含义上也有“所属”关系。因此将点操作符“.”读成 “的”,音和意皆有内涵。
Java—面向对象设计—类和对象
Java—面向对象设计—类和对象

匿名对象

匿名对象是指就是没有名字的对象。实际上,根据前面的分析,对于一个对象实例化的操作来讲,对象真正有用的部分是在堆内存里面,而栈内存只是保存了一个对象的引用名称(严格来讲是对象在堆内存的地址),所以所谓的匿名对象就是指,只开辟了堆内存空间,而没有栈内存指向的对象。

创建匿名对象

Java—面向对象设计—类和对象
代码第11行,创建匿名对象,没有被其他对象所引用。如果第11行定义一个有名对象,如:
Java—面向对象设计—类和对象
但是由于“new NoNameObject()”创建的是匿名对象,所以就用“NoNameObject()”整体来作为新构造匿名对象的引用,它访问类中的方法,就如同普通对象一下,使用点操作符(.):
Java—面向对象设计—类和对象
Java—面向对象设计—类和对象
由程序的输出结果可以发现,str1不等于str2,有些人可能会问,str1与str2的内容完全一样,为什么会不等于呢家可以发现在程序的第5和第6行分别用new实例化了两个String类对象,此时这两个对象在“堆内存”中处于不同的内存位置,也就是它们的内存地址是不一样的。这个时候程序中是用的“= =”比较,比较的是内存地址值(即引用值),所以输出str1!=str2。程序第7行将str2的引用值直接赋给str3,这个时候就相当于str3也指向了str2的引用,此时这两个对象指向的是同一内存地址,所以比较值的结果是str2==str3。str1、str2和str3的内存布局模拟如下图所示。
Java—面向对象设计—类和对象

Java—面向对象设计—类和对象
达到的比较效果是同原来的代码是一样的。

在这里需要大家记住,“==”是比较对象内存地址值(即所谓的引用值)的,而“equals”是比较对象内容的。

对象数组的使用

我们可以把类理解为用户自定义的数据类型,它和基本数据类型(如int、float等)具有相同的地位。在前面章节中我们已介绍过如何以数组来保存基本数据类型的变量。类似地,对象也可以用数组来存放,可通过下面两个步骤来实现。

声明以类为数据类型的数组变量,并用new分配内存空间给数组。

用new产生新的对象,并分配内存空间给它。

例如,要创建3个Person类型的数组元素,语法如下。

Java—面向对象设计—类和对象
当然也可以写成如下形式。
Java—面向对象设计—类和对象
或者也可以采用静态方式来初始化对象数组,如下所示。
Java—面向对象设计—类和对象
Java—面向对象设计—类和对象
程序第20~24行用静态声明方式声明了Person类的对象数组p,它包含了3个对象。事实上,在21-23行,每一行都是返回一个对象的引用地址,而对象数组的三个元素就是这三个对象的引用地址。

程序第25~28行用for循环输出对象数组p中的所有对象,并分别调用它们talk()方法,打印出个人信息。

序第06-10行构造方法Person()的定义,在这个代码段里,有个关键词this,容易让初学者困惑。下面给予简要介绍,有关this的详细使用说明,在后期的文章中我会再详细介绍。

当创建一个对象后,Java虚拟机(JVM)就会给这个对象分配一个自身的引用——this。由于this是和对象本身相关联的,所以this只能在类中的非静态方法中使用。静态属性及静态方法属于类,它们与具体的对象无关,所以静态属性及静态方法是没有this的。同一个类定义下的不同对象,每个对象都有自己的this,虽然都叫this,但指向的对象不同。这好比一个班里的众多同学来做自我介绍:“我叫XXX”,虽然说的都是“我”,但每个“我”指向的对象是不同的。

为什么第08-09行中有赋值运算符(=)左侧的变量使用this引用呢是因为构造方法Person()的参数列表有形参name和age,它们是隶属于构造方法Person()的局部变量,而Person对象中有同名的属性变量name和age(分别在第03行和04行定义),如果将构造方法Person()中的形参给给同名的对象属性赋值,第08-09行就变成如下的语句。

Java—面向对象设计—类和对象
由上面的描述可知,看起来面向过程代码更加简洁。如果程序比较短,面向过程要比面向对象更加的清晰。

但是用户的需求是一直在变的,软件的升级基本上是不可避免的。如果项目经理要求增加新的要求——新的升级软件需要支持“三角形”的旋转和播放歌曲。那么程序员POP和OOP是如何完成自己的工作呢/p>

⑵ 第二回合的代码完成情况。

Java—面向对象设计—类和对象
表面上看来面向过程代码POP依然占据优势,比较简洁,但是POP代码在代码维护中,“牵一发而动全身”,过程Rotate()、PlaySong()是全局性的,在前期版本Rotate()、PlaySong()可以正确响应“矩形”和“圆形”的变化,但是在维护这两个“新版本”过程中有一点错误,都会让前期的“无辜”的“矩形”和“圆形”受到牵连——无法正确运行。

而面向对象代码OOP,虽然代码过程看起来复杂一点,但是如果前期版本的软件可以正确响应“矩形”和“圆形”的变化,那么在新版本维护过程中,增加了“三角形”,即使出现了错误(不管是逻辑上的还是语法上的),那这些错误仅仅局限性于“三角形”类——这样程序的错误就可控,很方便维护。

如果代码很短,面向对象编程的模式优势并不明显,但是如果读者把Rotate()、PlaySong()过程想象成上万行的代码,就会知道将代码错误局部化、可控化,对程序的后期维护有多重要!

落后的软件生产方式无法满足迅速增长的计算机软件需求,从而导致软件开发与维护过程中出现一系列严重问题的现象。这就是所谓的软件危机。在学习了类的三个特点——封装、继承和多态,大家会发现,相比于面向过程编程,面向对象编程还有更多的优点。

Java—重复调用的代码块—方法

JAVA—重谈方法【详细版】

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树类和接口类和面向对象92102 人正在系统学习中

来源:脑袋不灵光的小白羊

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年1月21日
下一篇 2022年1月21日

相关推荐