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

理解面向对象程序设计

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

结构化程序设计简介

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

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

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

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

对象 = 数据 + 方法

更进一步的可以描述为,

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

面向对象的基本概念

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

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

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

对象

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

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

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

类和对象的关系

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

类的声明与定义

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

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

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

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

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

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

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

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

类的属性

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

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

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

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

属性的定义

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

代码第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的类,相信类的基本形式读者应该已经很清楚了。但是在实际中单单有类是不够的,类提供的只是一个模板,必须依照它创建出对象之后才可以使用。

对象的声明

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

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

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

对象的使用

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

匿名对象

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

创建匿名对象

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

对象数组的使用

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

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

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

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

程序第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行就变成如下的语句。

但是用户的需求是一直在变的,软件的升级基本上是不可避免的。如果项目经理要求增加新的要求——新的升级软件需要支持“三角形”的旋转和播放歌曲。那么程序员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日

相关推荐