基础篇5.0类和对象
Java语言中,经常被提到的两个词汇是类与对象,实质上可以将类看做是对象的载体,它定义了对象所具有的功能。学习Java语言必须要掌握类与对象,这样可以从深层去理解Java这种面向对象语言的开发概念,使程序员更好、更快地掌握Java编程思想与编程方式,因此掌握类与对象是学习Java语言的基础。
面向对象概述
在程序开发初期人们使用结构化开发语言,但是随着时间的流逝,软件的规模越来越大,结构化语言的弊端也逐渐暴露出来,开发周期被无休止地拖延,产品的质量也不尽如人意,人们终于发现结构化语言已经不再适合当前的软件开发。这时人们开始将另一种开发思想引入程序中,即面向对象的开发思想。面向对象思想是人类最自然的一种思考方式,它将所有预处理的问题抽象为对象,同时了解这些对象具有哪些相应的属性以及这些对象的行为,以解决这些对象面临的一些实际问题,这样就在程序开发中引入面向对象设计的概念,面向对象设计实质就是对现实世界的对象进行建模操作。
什么是对象
现实世界中,随处可见的一种事务就是对象,对象是对事物存在的实体,如人类、书桌、计算机、高楼大厦等。人类解决问题的方式总是将复杂的事物简单化,于是就会思考这些对象都是由何种部分组成的。通常都会将对象划分为两部分,即动态部分与静态部分。静态部分,顾名思义,就是不能动的部分,这部分被称为“属性”任何对象都会具备其自身属性,例如一个人,它包括高矮、胖瘦、性别、年龄等属性。然而具有这些属性的人会执行哪些动作也是一个值得探讨的部分,这个人可以哭泣、微笑、说话、行走,这些事这个人具备的行为(动态部分),人类通过探讨对象的属性和观察对象的行为了解对象。
在计算机世界中,面向对象程序设计的思想要以对象来思考问题,首先要将显示世界的实体抽象为对象,然后考虑这个对象具备的属性和行为。例如,现在面临一直大雁要从北方飞往南方这样一个实际问题,试着以面向对象的思想来解决这一实际问题,步骤如下:
(1)首先可以从这个问题中抽象出对象,这里抽象出的对象为大雁。
(2)识别这个对象的属性。对象具备的属性都是静态属性,例如大雁有一对翅膀、黑色的羽毛等。

(3)识别这个对象的动态行为,即这只大雁可以进行的动作,如飞行、觅食等,这些行为都是因为这个对象基于其属性而具有的动作。

(4)识别出这些对象的属性和行为后,这个对象就被定义完成,然后可以根据这只大雁具有的特性制定这只大雁要从北方飞向南方的具体方案以解决问题。实质上究其本质,所有的鸟类都具有以上的属性和行为,可以将这些属性和行为封装起来以描述鸟类。由此可见,类实质上就是封装对象属性和行为的载体,而对象则是类抽象出来的一个实例,两者之间的关系为:

什么是类
不能将所谓的一个事物描述成一类事物,如一只鸟不能称为鸟,如果需要对同一类事物统称,就不得不说明类这个概念。
类就是同一类事物的统称,如果将现实世界中的一个事物抽象成对象,类就是这类对象的统称,如鸟类、家禽类、人类等。类是构造对象时所依赖的规范,例如,一只鸟具有一对翅膀,而它可以通过这对翅膀飞行,而基本上所有的鸟都具有翅膀这个特性和飞行的技能,这样的具有相同特性和行为的一类事物就称为类,类的思想就是这样产生的。在上图中已经描述过类与对象之间的关系,对象就是符合某个类定义所产生出来的实例。更为恰当的描述是:类是事件事物的抽象称呼,而对象则是这个事物想对应的实体。如果面临实际问题,通常需要实例化类对象来解决。例如解决大雁南飞的问题,这里只能拿这只大雁来处理这个问题,不能拿大雁类或鸟类类解决。
类是封装对象的属性和行为的载体,反过来说具有相同属性和行为的一类实体被称为类。例如一个鸟类,鸟类被封装了所有鸟的共同属性和应具有的行为,其结构为:

定义完成鸟类之后,可以根据这个类抽象出一个实体对象,最后通过实体对象来解决相关一些实际问题。
在Java语言中,类中对象的行为是以方法的形式定义的,对象的属性是以成员变量的形式定义的,而类包括对象的属性和方法,有关类的具体实现会在后续章节中进行介绍。
面向对象的特点
面向对象程序设计具有以下几个特点。
1)封装
封装是面向对象编程的核心思想,将对象的属性和行为封装起来,而将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。例如,用户使用计算机,只需要使用手指敲击键盘就可以实现一些功能,用户无需知道计算机内部是如何工作的,及时用户可能碰巧知道计算的工作原理,但在使用计算机时并不完全依赖于计算机工作原理这些细节。
采用封装的思想保证了类内部数据结构的完整性,应用该类的用户不能轻易直接操纵此类数据结构,而只能执行类允许公开的数据。这样避免了外部对内部数据的影响,提高了程序的可维护性。
使用类实现封装特性:

2)继承
类与类之间同样具有关系,如一个百货公司类与销售员类相联系,类之间这种关系被称为关联。关联是描述两个类之间的一般二元关系,例如一个百货公司类与销售员类就是一个关联,再如学生类以及教师类也是一个关联。连个类之间的关系有很多种,继承是关联中的一种。
当处理一个问题时,可以将一些有用的类保留下来,当遇到同样问题时拿来复用。假如这时需要解决信鸽送信的问题,我们很自然会想到图示鸟类,并且保留鸟类具有的属性和行为。不过,并不是所有的鸟类都有送信的习惯,因此还需要再添加一些信鸽具有的独特属性以及行为。鸽子类保留了鸟类的属性和行为,这样就节省了定义鸟和鸽子共同具有的属性和行为的时间,这就是继承的基本思想。可见软件的代码使用继承思想可以缩短软件开发的时间,复用那些已经定义好的类可以提高系统性能,减少系统在使用过程中出现错误的几率。
继承性主要利用特定对象之间的共有属性。例如,平行四边形是四边形(正方形、矩形也都是四边形),平行四边形与四边形具有共同属性,就是拥有4个边,可以将平行四边形类看作四边形的延伸,平行四边形复用了四边形的属性和行为。同时添加了平行四边形独有的属性和行为,如平行四边形的对边平行且相等。这里可以将平行四边形类看作是从四边形类中继承的。在Java语言中将类似于平行四边形的类统称为子类,将类似于四边形的类称为父类或超类。注意,可以说子类的实例都是父类的实例,但不能说父类的实例是子类的实例。

从图中可以看出,继承关系可以使用树形关系来表示,父类与子类存在一种层次关系。一个类处于继承体系中,它即可以是其他类的父类,为其他类提供属性和行为,也可以是其他类的子类,继承父类的属性和方法,如三角既是图形类的子类,同时事等边三角形的父类。
3)多态
其实将父类对象应用于子类的特征就是多态。依然以图形类来说明多态,每个图形都拥有绘制自己的能力,这个能力可以看做是该类具有的行为,如果将子类的对象统一看做是超类的实例对象,这样当绘制任何图形时,可以简单地调用父类也就是图形类绘制图形的方法即可绘制任何图形,这就是多态最基本的思想。
多态性允许以统一的风格编写程序,以处理种类繁多的已存在的类以及相关类。该统一风格可以由父类来实现,根据父类统一风格的处理,就可以实例化子类的对象。由于整个事件的处理都只依赖于父类的方法,所以日后只要维护和调整父类的方法即可,这样既降低了维护的难度,又节省了时间。
在提到多态的同时,不得不提到抽象类和接口,因为多态的实现并不依赖具体类,而是依赖于抽象类和接口。
再回到绘制图形的实例上来。作为所有图形的父类图形类,它具有绘制图形的能力,这个方法可以成为“绘制图形”,但如果要执行这个“绘制图形”的命令,没人知道应该画什么样的图形,并且如果要在图形类中抽象出一个图形对象,没有人能说清这个图形究竟是什么图形,所以使用“抽象”这个词汇来描述图形类比较恰当。当Java语言中称这样的类为抽象类,抽象类不能实例化对象。在多态的机制中,父类通常会被定义为抽象类,在抽象类中给出一个方法的标准,而不给出实现的具体流程。实质上这个方法也是抽象的,例如图形类中的“绘制图形”方法只提供一个可以绘制图形的标准,并没有提供具体绘制图形的流程,因为没有人知道究竟需要绘制什么形状的图形。
在多态的机制中,比抽象类更为方便的方式是将抽象类定义为接口。(概念)由抽象方法组成的集合就是接口。就扣的概念在显示中也极为常见,如从不同的五金店买了的螺丝和螺丝钉,螺丝很轻松地就可以拧在螺丝钉上,可能螺丝和螺丝钉的厂家不同,但这两个物品可以很轻易地组合在一起,这是因为生产螺丝和螺丝钉的厂家都遵循着一个标准,这个标准在Java语言中就是接口。依然拿“绘制图形”来说明,可以将“绘制图形”作为一各接口的抽象方法,然后使用图形类实现这个接口,同时实现“绘制图形”这个抽象方法,当三角形类需要绘制时,就可以继续图形类,重写其中“绘制图形”方法,改写这个方法为“绘制三角形”,这样就可以通过这个标准绘制不同的图形。
类
类是封装对象的属性和行为的载体,而在Java语言中对象的属性以成员变量的形式存在,而对象的方法以成员方法的形式存在。在Java语言中类是如何定义的。
类的构造方法
在类中畜类成员方法之外,还存在一种特殊类型的方法,那就是构造方法。构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的,每当类实例化一个对象时,类都会自动调用构造方法。构造方法的特点如下:
构造方法没有返回值。构造方法的名称要与本类的名称相同。
注意:在定义构造方法时,构造方法没有返回值,但这与普通没有返回值的方法不同,普通没有返回值的方法使用public void methodEx()这种形式进行定义,但构造方法并不需要void关键字进行修饰。
构造方法的定义语法格式:
public:构造方法修饰符。book:构造方法的名称。
在构造方法中可以为成员变量赋值,这样当实例化一个本类的对象时,响应的成员变量也将被初始化。
注意:如果在类中定义的构造方法都不是无参的构造方法,(即都是有参的构造方法)则编译器不会为类设置一个默认的无参构造方法,当时图调用无参构造方法实例化一个对象时,编译器会报错。所以只有在类中没有定义任何构造方法时,编译器才会自动在该类中创建一个不带参数的构造方法。
了解了this关键字可以调用类的成员变量和成员方法,事实上this还可以调用类中的构造方法。在无参构造方法中可以使用this关键字调用有参的构造方法。但使用这种方式值得注意的是,只可以在无参构造方法中的第一句使用this关键字调用有参构造方法。
类的主方法
主方法是类的入口,它定义了程序从何处开始;主方法提供对程序流向的控制,Java编译器通过主方法来执行程序。主方法的语法格式:
public static void main(Stringp[] args){
//方法体
}
在主方法的定义中可以看到主方法具有以下特性
主方法是静态的,要直接在主方法中调用其他方法,则该方法必须也是静态的。
主方法没有返回值。
主方法的形参为数组。
用args[0]~args[n]分别代表程序的第1到第n个参数,可以使用args.length获取参数的个数。
注意:在Eclipse中设置程序参数的步骤如下:
(1)在eclipse中选择菜单栏中的“运行”/“运行”命令,弹出“运行”对话框。
(2)选择“自变量”选项卡,在“程序自变量”文本框中输入相应的参数,每个参数件按Enter键隔开。


成员变量
在Java语言中对象的属性称为成员变量,也可以称为属性。为了了解成员变量,首先定义一个图书类,成员变量对应于类对象的属性,在book类中设置id、name和category3个成员变量,分别对应于图书编号、图书名称和图书类别这3个图书属性。
在Java语言中使用class关键字来定义类,book是类的名称。同事在book类中定义了3个成员变量,成员变量的类型可以设置为Java语言中合法的数据类型,其实成员变量就是普通的变量,可以为它设置初始值,也可以不为其设置,如果不设置初始值,则会有默认值。在3个成员变量前面读者应该主要到private关键字,它用来定义一个私有成员。
成员方法
在Java语言中使用成员方法对应于类对象的行为。以book类为例,它包含getname()和setname()两个方法,分别为获取图书名称和设置图书名称的方法。定义成员方法的语法格式如下:
权限修饰符 返回值类型 方法名(参数类型 参数名){
……//方法体
return 返回值;
}
一个成员方法可以有参数,这个参数可以是对象也可以是基本数据类型的变量,同时成员方法有返回值和不返回任何职的选择,如果方法需要返回值可以在方法体中使用return关键字,使用这个关键字之后,方法的执行将被终止。
注意:Java语言中的成员方法无返回值可以使用void关键字。
成员方法的返回值可以使计算结果也可以是其他想要的数值、对象,返回值类型要与方法返回的值类型一致。
在成员方法中可以调用其他成员方法和类成员变量,例如在getname()方法中调用了setname()方法将图书名称赋予一个值。同时在成员方法中可以定义一个变量,这个变量为局部变量
注意:如果一个方法中含有与成员变量同名的局部变量,则方法中对这个变量的访问以局部变量进行访问,例如变量id,在getname()方法中id的值为0,而不是成员变量中id的值。
局部变量
(概念)如果在成员方法内定义一个变量,那么这个变量被称为局部变量。
在book类中,getname()方法的id变量即为局部变量。实际上方法中的形参也可以作为一个局部变量。例如在定义setname(string name)方法时,string name这个形参就被看做是局部变量。
注意:局部变量时在方法被执行时创建,在方法执行结束时被销毁。局部变量在使用时必须进行赋值操作或被初始化,否则会出现编译错误。
局部变量的有效范围
可以将局部变量的有效范围称为变量的作用域,局部变量的有效范围从该变量的声明开始到该变量的结束为止。局部变量的作用范围如图:

在相互不嵌套的作用域中可以同时声明两个名称、类型相同的局部变量,如图所示:

但是在相互嵌套的区域中不可以这样声明,如果将局部变量id值在方法体的for循环中再次定义,编译器会报错,如图:

注意:在作用范围外使用局部变量是一个常见的错误,因为在作用范围外没有声明局部变量的代码。
静态变量、常量和方法
(概念)在介绍静态变量、常量和方法之前首先需要介绍static关键字,因为由static修饰的变量、常量和方法被称作静态变量、常量和方法。
有时在处理问题时,会需要两个类在同一个内存区域共享一个数据。例如,在球类中使用pI这个常量,可能除了本类需要这个常量之外,在另外一个圆类中也需要使用这个常量。这时没有必要在两个类中同时创建PI这个常量,因为这个系统会将这两个不在同一个类中定义的常量分配到不同的内存空间中。为了解决这个问题,可以将这个常量设置为静态的。PI常量在内存中被共享的布局如图。

被声明为static的变量、常量和方法称为静态成员。静态成员是属于类所有的,区别于个别对象,可以在本类或其他类使用类名和“.”运算符调用静态成员。语法格式:类名.静态类成员
注意:虽然静态成员也可以使用“对象.静态成员”的形式进行调用,但这样的形式通常不被鼓励使用,因为这样容易混淆静态成员和非静态成员。
静态数据与静态方法的作用通常是为了提供共享数据或方法,如数据计算公式等,以static声明并实现,这样当需要使用时,直接使用类名调用这些静态成员即可。尽管这种方式调用静态成员比较方便,但静态成员同样遵循着public、private、protected修饰符的约束。
注意:在eclipse中输入上述代码后,编译器会发生错误,这是因为method3()方法为一个静态方法,而在其方法体中调用了非静态方法和this关键字。在Java语言中对惊天方法有以下两点规定:
在静态方法中不可以使用this关键字。在静态方法中不可以直接调用非静态方法。
注意:在Java语言中规定不能将方法体内的局部变量声明为static。如果在执行类时,希望先执行类的初始化动作,可以使用static定义一个静态区域。例如:public class example{ static{//some}}当这段代码被执行时,首先执行static块中的程序,并且只会执行一次。
权限修饰符
(作用)Java语言中的权限修饰符主要包括private、public和protected,这些修饰符控制着对类和类的成员变量以及成员方法的访问。如果一个类的成员变量或成员方法被修饰为private,则该成员变量只能在本类中使用,在子类中是不可见的,并且对其他包的类也不可见的。如果将类的成员变量和成员方法的访问权限设置为public,则除了可以在本类使用这些数据之外,还可以在子类和其他包中的类中使用。如果一个类的访问权限被设置为private,这个类将隐藏其内的所有数据,以免用户直接访问它。如果需要使类中的数据被子类或其他包中类使用,可以将这个类设置为public访问权限。如果一个类使用protect修饰符,那么只有本包内该类的子类或其他类可以访问此类内该类的子类及其他类可以访问此类中的成员变量和成员方法。
public和protected修饰的类可以由子类访问,如果子类和父类不在同一包中,那么只有修饰符为public的类可以被子类进行访问。如果父类不允许通过集成产生的子类访问它的成员变量,那么必须使用private声明父类的这个成员变量。

注意:当声明类时不适用public、protected、private修饰符设置类的权限,则这个类预设为包存取范围,即只有一个包中的类可以调用这个类的成员变量或成员方法,相当于protected?
注意:由于类的修饰符为默认修饰符,即只有一个包内的其他类和子类可以对该类,而anyclass类中的dotring()方法却又被设置为public访问权限,及时这样,dostring方法的访问权限依然与anyclass类的访问权限相同,因为Java语言规定,类的权限设定会约束类成员上的权限设定。
this关键字
在项目中创建一个类文件,该类中定义了setname()方法,并将方法的参数值赋予类中的成员变量。
在上述代码中可以看到,成员变量与在setname()方法中的形式参数的名称相同,都为name,那么如何在类中区分使用的是哪一个变量?在Java语言中规定使用this关键字来指定。this关键字被隐式地用于引用对象的成员变量和方法,如上述代码中,this.name指定的就是book类中name成员变量,而this.name=name语句中的第二个name则指定的是形参name。实质上setname()方法事项的功能就是将形式参数name的值赋予成员变量name .
this可以调用成员变量和成员方法,但这并不是Java语言最常规调用方式——使用“对象.成员变量”或“对象.成员方法”进行调用。既然this关键字可以调用成员变量和成员方法,究竟this关键字与对象之间具有怎样的关系?事实上this引用就是对一个对象的引用。
在setname()方法体中也可以使用name=name这种形式,可以在引用成员变量时省略this关键字,结果与使用this关键字是一样的。this除了可以调用成员变量或成员方法之外,还可以作为方法的返回值。
在getbook()方法中,方法的返回值为book类,所以方法体中使用return this这种形式将book类的对象进行返回。
总结:类的修饰符是相对于对象来说,对于对象是否可见。变量或者方法的修饰符也是对于对象来说,但是要看类权限的设定,因为Java语言规定,类的权限设定会约束类成员上的权限设定。
对象
(很nice的一句话)Java是一门面向对象的程序设计语言,对象是由类抽象出来的,所有的问题都是通过对象来处理,对象可以操作类的属性和方法解决响应的问题,所以了解对象的产生、操作和小王对于学习Java语言是十分必要的。
对象的创建
(概念)对象可以认为是一类事物中抽象出某一特例,通过这个特例来处理这类事物出现的问题,在Java语言中通过new操作符来创建对象。以前曾经在讲解构造方法中介绍过每实例化一个对象就会自动调用一次构造函数,实质身上这个过程就是创建对象的过程。准确滴说,可以在Java语言中使用new操作符调用改造方法创建对象。语法格式:
Test test = new Test(); Test test = new Test("a");

test对象被创建出来时,test对象就是一个对象的引用,这个引用在内存中为对象分配了存储空间,可以在构造方法中初始化成员变量,当创建对象时,自动调用构造方法,也就是说在Java语言中初始化与创建是被捆绑在一起的。
每个对象都是相互独立的,在内存中占据独立的内存地址,并且每个对象都具有自己的生命周期,当一个对象的生命周期结束时,对象就变成了垃圾,由Java虚拟机自带的垃圾回收机制处理,不能再被使用。
注意:在Java语言中对象和实例事实上可以通用。在创建对象的同时,自动调用构造方法中的代码。
访问对象的属性和行为
当用户使用new操作符创建一个对象后,可以使用“对象.类成员”来获取对象的属性和行为。对象的属性和行为在类中是通过类成员变量和成员方法的形式来表示的,所以当对象获取类成员,也就想想地获取了对象的属性和行为。
在代码的主方法中实例化一个对象,然后使用“.”操作符调用类的成员变量和成员方法。但是在运行结果中看到,虽然使用两个对象调用同一个成员变量,结果却不同,因为在打印这个成员变量的值之前将该值重新赋值为60,但在赋值时使用的是第二个对象t2调用成员变量,所以在第一个对象t1调用成员变量打印该值时仍然是成员变量的初始值,由此可见,两个对象的产生是相互独立的,改变了T2的i值,不会影响到T1的i值。在内存中这两个对象的布局如图:

如果希望成员变量不被其中任何一个对象改变,可以使用static关键字(声明为static的成员变量的值可以被本类或其他类的对象共享)。由于使用t2.i=60,语句改变了静态成员变量的值,使用对象t1调用成员变量的值也为60,这正是i值被定义为静态成员变量的功效,即使使用两个对象对同一静态成员变量进行操作,依然可以改变静态成员变量的值,因为在内存中两个对象同时指向同一块内存区域。“t1.i++”语句执行完毕后,i值变为3.当再次调用call方法时又被重新赋值为0,座循环打印操作。
对象的引用
在Java语言中尽管一切都可以看做对象,但真正操作标识符的实质上是一个引用,那么引用究竟如何在Java语言中是如何体现的?
语法格式: 类名 对象引用名称
(举例)一个book类的引用 Book book
通常一个引用不一定需要有一个对象相关联,引用与对象相关联的语法如下:
Book book = new Book();
Book : 类名。book : 对象 。 new:创建对象操作符。
注意:引用只是存放一个对象的内存地址,并非存放一个对象,严格说引用和对象是不同的,但是可以将这种区别忽略,可以简单地说book是Book类的一个对象,而事实上应该说book是包含Book对象的一个引用。
对象的比较
在Java语言中有两种对象比较的方式,分别为“==”运算符和equals()方法,这两种方法有着本质区别。
“==”运算符和equals()方法比较的内容是不相同的。equals()方法是string类中的方法,它用于比较两个对象引用所指的内容是否相等;而“==”运算符比较的是两个对象引用的地址是否相等。由于c1与c2是两个不同的对象引用,两者在内存中的位置不同,而“string c3=c1;”语句将c1的引用赋给c3,所以c1与c3这两个对象引用是相等的。也就是打印c1==c3这样的语句将返回true值。
对象c1/c2/c3在内存中的布局如图。

对象的销毁
每个对象都有声明周期,当对象的声明周期结束时,分配给该对象的内存地址将会被回收,在其他语言中需要手动回收废弃的对象,但是Java语言拥有一套完整的垃圾回收机制,用户不必担心废弃的对象占用内存,垃圾回收器将回收无用的占用内存的资源。
在谈垃圾回收机制之前,首先需要了解何种对象会被Java虚拟机视为垃圾。主要包括以下两种情况:
对象引用超过其作用范围,则这个对象将被视为垃圾。将对象赋值为NULL。

虽然垃圾回收机制已经很完善,但垃圾回收器智能回收那些由new操作符创建的对象,如果某些对象不是通过new操作符在内存中获取一块内存区域,这种对象可能不被垃圾回收机制所识别,所以Java语言提供了一个finalize()方法,这个方法是object类的方法,它被声明为protected,用户可以在自己的类中定义这个方法,如果用户在类中定义了finalize()方法,在垃圾回收时首先调用该方法,并且在下一次垃圾回收动作发生时,才能真正回收对象占用的内存。
说明:垃圾回收或是finalize()方法不保证一定会发生,如Java虚拟机面临内存损耗待机的情形,它是不会执行垃圾回收的。由于垃圾回收不受人为控制,具体执行时间也不确定,所以finalize()方法也就无法执行,为此,Java提供了system.gc()方法强制启动垃圾回收器,这与给120打电话通知医院来救护的道理一样,告知垃圾回收器来清理。
小结:应该掌握面向对象的编程思想,同时在此基础上读者可以编写类,定义类成员、构造方法、主方法以解决一些实际问题,类以及类成员可以使用权限修饰符进行修饰,应了解掌握这些修饰符的具体范围。由于在Java中通过对象来处理问题,所以对象的创建、比较、销毁的应用就显得非常重要。应反复揣摩这些基本概念和面向对象的编程思想。
浙公网安备 33010602011771号