目录
在java中经常被提到的两个词就是类和对象,实际上可以将类看做是对象的载体,类定义了对象所具有的功能.类和对象是面向对象的基础.
7.1面向对象概述
面向对象思想是人类最自然的一种思考方式,它将所有预处理的问题抽象为对象,同时了解这些对象具有哪些相应的属性以及展示这些对象的行为,面向对象设计实质上就是对现实世界的对象进行建模操作。
7.1.1对象
在现实世界中,随处可见的一种事物就是对象,对象是物体存在的实体,如书桌、计算机等。人类解决问题的方式总是将复杂的事物简单化,于是就会思考这些对象是由哪些部分组成的。通常会将对象划分为两个部分:静态部分和动态部分。静态部分顾名思义就是不能动的部分,这部分称为“属性”,任何对象都会具备其自身属性,如一个人的属性包括身高、体重、性别等;然而具有这些属性的人会执行哪些动作也是一个值得探讨的部分,这个人可以哭泣、微笑、说话等,这些是这个人所具备的行为(动态部分),人们通过观察对象的属性和行为来了解对象。
在计算机世界中,面向对象的程序设计思想就是要以对象来思考问题,首先将现实世界的实体抽象为对象,然后考虑这个对象所具备的属性和行为。例如,现在面临一只大雁要从南方飞往北方这样一个问题,试着以面向对象的思想来解决这一个问题:
(1)首先可以从这个问题中抽象出对象,即大雁;
(2)然后识别这个对象的属性,对象所具备的属性都是静态的,如图所示:
(3)接着识别大雁的动态行为,如飞行、觅食等,这些行为都是这个对象基于其属性而具有的动作。如图所示:
(4)识别出这个对象的行为和属性后,这个对象就被定义完成了,然后根据这只大雁具有的特性指定大雁从南方飞往北方的具体方案以解决问题。
究其本质,所有的大雁都具有以上的属性和行为,可以将这些属性和行为封装起来以描述大雁这种动物。由此可见,类实质上就是封装对象属性和行为的载体,而对象则是类抽象出来的一个实例,两者之间的关系如图:
7.1.2类
不能将所谓的一个事物描述成一类事物,如一只鸟不能叫做鸟类。类是同一类事物的统称,如果将现实世界的一个事物抽象成对象,类就是这类对象的统称,如鸟类、家禽类等。类是具有相同特性和行为的一类事物的统称 ,对象是符合某个类的定义所产生的出来的实例,类与对象的关系就是鸟类与一只鸟的关系。更确切的说,类是世间事物的抽象称呼,对象则是这种事物对应的实体。
类是封装对象的属性和行为的载体,反过来说具有相同属性和行为的一类实体被称为类。例如,鸟类封装了所有鸟的共同属性和行为,其结构如图:
定义完鸟类后,可以根据这个类抽象出一个实体对象,最后通过实体对象来解决相关的实际问题。在java中,类中对象的行为是以方法的形式定义的,对象的属性是以成员变量的形式定义的,所以类包括属性和方法两部分。
7.1.3封装
面向对象的程序设计具有以下特点:
(1)封装性;
(2)继承性;
(3)多态性;
封装是面向对象的核心思想。将对象的属性和方法封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。例如,用户在使用计算机时只需要敲击键盘和鼠标即可,而不需要知道计算机内部是如何工作的。
采用封装的思想保证了类内部数据结构的完整性,应用该类的用户不能轻易的操作此类的数据结构,只能执行类允许公开的数据,避免了外部操作对类的内部数据的影响,提高了程序的可维护性。
7.1.4继承
当处理一个问题时,可以将一些有用的类保留下来,下次在遇到同样的问题时拿来复用。假如这时需要解决信鸽送信问题,我们很自然想到鸟类。由于鸽子属于鸟类,具有与鸟类相同的属性和方法,我们便可以在创建信鸽类时将鸟类拿来复用,并保留鸟类的属性和方法。但是,并不是所有的鸟都有送行的行为(方法),因此还需要添加一些信鸽独特的属性和方法(行为)。信鸽类保留了鸟类的属性和方法,这样就节省了定义鸟类与信鸽类共有属性和方法的时间,这就是继承的基本思想。
继承主要是利用特定对象之间的共有属性和方法。例如,平行四边形是四边形,正方形和矩形也是四边形,平行四边形和四边形的共有属性就是都具有四条边,可以将平行四边形看做是四边形的延伸,平行四边形复用了四边形的属性和方法,同时又添加了平行四边形的独特属性和方法,如对边平行且相等。因此,这里可以说平行四边形是从四边形中继承的。在java中,将平行四边形的类称为子类,四边形的类称为父类。值得注意的是,可以说平行四边形是四边形,但是不能说四边形是平行四边形,也就是说子类的对象都是父类的对象,但不能说父类的对象是子类的对象。
继承关系可以用树形图来表示。一个类处于继承体系中,它既可以是其他类的父类,为其他类提供属性和方法;也可以是其他类的子类,继承父类的属性和方法。
7.1.5多态
将父类的对象应用于子类的特征就是多态。以图形类来说明多态,每个图形都有绘制自己的能力,这个能力可以看做是类的方法。如果将子类的对象统一看做是父类的实例对象,这样当使用绘制图形这个方法时,简单的调用父类也就是图形类的绘制图形方法即可绘制任何图形,但是不同的子类绘制图形的标准不同,这就需要在父类定义绘制图形方法时对于不同的子类采取不同的操作,这就是多态。
7.2类
类是封装对象属性和行为的载体,而在java中对象的属性是以成员变量的形式存在的,对象的行为则是以成员方法的形式存在。
7.2.1成员变量
在java中对象的属性叫做成员方法。
可以看到,上面的类并没有主方法main(),主方法是程序开始执行的位置,也就是说类中允许没有主方法。上面的类定义了3个成员变量,成员变量就是普通变量,可以赋初值也可以不赋。如果不赋初值,会有默认值。
7.2.2成员方法
在java中成员方法对应于类的对象的行为。定义成员方法的格式如下:
成员方法可以有参数,这个参数可以是对象也可以是基本数据类型的变量;同时成员方法可以有返回值,没有返回值可以用关键字void表示;如果有返回值,可以再方法体中使用关键字return。使用这个关键字后,方法的执行将被终止。
注意,成员方法的返回值类型要与方法返回的值的类型一致。同时也可以在成员方法中定义变量,成员方法中的变量称为局部变量。如果局部变量的变量名与成员变量的变量名一样,则在方法中对这个变量的访问以局部变量进行。
7.2.3权限修饰符
java中的权限修饰符主要包括private、public、protected.这些修饰符控制着对类和类的成员变量以及成员方法的访问。
public和protected修饰的类可以由子类访问,如果子类和父类不在一个包中,那么只有修饰符为public的类可以被子类访问。如果父类不允许通过继承产生的子类访问它的成员变量,那么必须使用private声明父类的这个成员变量。下表描述了三种修饰符的权限:
当声明的类不使用上面任意一个修饰符设置类的权限时,则这个类预设为包存取范围,也就是只有同一个包中的类可以调用这个类的成员变量和成员方法。
上面例子,由于类的修饰符为默认修饰符,即只有一个包内的其他类和子类可以对该类进行访问,而AnyClass中的方法doString()却又被设置为public访问权限。但是java规定,类的权限设定会约束类成员的权限设定,所以上述代码等同于:
7.2.4局部变量
成员方法中的定义的变量称为局部变量。局部变量在方法执行时被创建,在方法执行结束时被销毁。局部变量在使用时必须进行赋值操作或初始化,否则会报错。
7.2.5局部变量的有效范围
局部变量的有效范围称为变量的作用域,局部变量的有效范围从该变量的声明开始到该变量结束为止。
在互不嵌套的作用域中可以定义两个类型和名称都相同的局部变量。
但是在相互嵌套的区域不可以这样声明,如下例:局部变量id在方法体的for循环中再次被定义,编译器会报错。
在作用范围外使用局部变量时一个常见的错误,因为在作用范围外没有声明局部变量的代码。
7.2.6 this关键字
上例看到,成员变量与setName()方法中的形式参数名称相同,都为name,那么如何在类中区分使用的是哪个变量呢?java中规定使用关键字this代表本类对象的引用,也就是说this.name指的就是类的成员变量,this.name=name语句的第二个name则指的是形参name。
事实上this引用就是本类的一个对象。在局部变量或方法参数覆盖了成员变量时,就要添加this关键字明确的指出引用的是类的成员变量还是方法参数或局部变量。
其实,this除了可以调用成员变量和成员方法外,还可以作为方法的返回值,此时的返回值的类型是类的对象。
7.3类的构造方法
在类中除了成员方法以外,还存在一种特殊类型的方法,就是构造方法。构造方法是一个与类同名的方法,对象的创建就是通过构造方法来完成的。每当实例化一个对象时,类都会自动调用构造方法.new操作符后跟的就是类的构造方法。
构造方法的特点如下:
(1)构造方法没有返回值,但是可以有参数;
(2)构造方法名要与类名一致 ;
与普通无返回值的成员方法不同的是,构造方法不需要用关键字void进行修饰。
构造方法的定义格式如下;
在构造方法中可以为成员变量赋值,这样当实例化一个本类的对象时,相应的成员变量也被初始化;
如果类中没有明确定义构造方法,编译器会自动创建一个不带参数的默认构造方法;
注意,如果类中定义的都是有参数的构造方法,那么编译器一定不会为类创建一个默认的无参构造方法,当此时如果试图调用一个无参构造方法实例化对象时,编译器会报错。
关键字this还可以用来调用类中的构造方法,看下例:
上例在无参构造方法中用关键字this调用有参的构造方法,但需要注意的是只可以在无参构造方法的第一句使用this调用有参构造方法。
7.4静态变量、常量和方法
在处理问题时,有时候需要两个类在同一块内存区域共享一个数据。例如,在球类中使用PI这个常量,可能出了本类需要这个常量外,在另一个圆类也需要使用PI常量。这时没有必要在两个类中同时创建PI常量,因为这样系统会将这两个不在同一个类中定义的常量分配到不同的内存空间。为了解决这个问题,可以将这个常量设为静态的。PI常量在内存中被共享的布局如图:
由static修饰的变量、常量和方法称为静态变量、常量和方法,统称为静态成员。静态成员归类所有,区别于个别对象,可以在本类或其他类使用类名和"."运算符调用静态成员。语法如下:
虽然静态成员也可以用"对象.静态成员"的形式进行调用,但是不建议用这种形式,因为这样容易混淆静态成员和非静态成员。
静态成员的作用通常了为了共享数据或方法,如数学公式等,直接使用类名调用这些静态成员即可。尽管这种方式调用静态成员比较方便,但是静态成员同样受权限修饰符public、private和protected的限制。
上述代码输入后,编译器会发生错误,这是因为method3()方法是静态方法,而在其方法体中调用了非静态方法和this关键字。在java中对静态方法有以下规定:
(1)在静态方法中不可以使用this关键字;
(2)在静态方法中不能直接调用非静态方法;
注意,java中规定不能将方法体内的局部变量声明为static,下述代码是错误的:
如果在执行类时希望先执行类的初始化动作,可以用static修饰一个静态区域。例如:
7.5类的主方法
主方法是类的入口,它定义了程序从何处开始执行;主方法提供了程序的流向控制,java编译器通过主方法来执行程序。语法如下:
主方法具有以下特性:
(1)主方法是静态方法,如果在主方法中要直接调用其他方法,则该方法也必须是静态的,否则需要例化对象才能调用非静态方法;
(2)主方法没有返回值;
(3)主方法的形参是数组,可以使用args.length获取参数的个数;
(4)主方法的权限修饰必须是public;
7.6对象
java是一门面向对象的语言,对象是由类抽象出来的,所有的问题都通过对象来处理,对象可以操作类的属性和方法解决相应问题。
7.6.1对象的创建
前面介绍过每实例化一个对象就会自动调用一次构造方法,实质上这个过程就是创建对象的过程。准确的说,可以在java中使用new操作符调用构造方法来创建对象。语法如下:
test对象被创建出来,就是一个对象的引用(指针),这个引用在内存中为对象分配了内存空间。在前面介绍构造方法时说过,可以在构造方法中初始化成员变量,当创建对象时自动调用类中的构造方法,也就是说在java中对象的创建与初始化是捆绑在一起的。
每个对象都是相互独立的,在内存中占据独立的内存地址,且每个对象都有自己的生命周期。当生命周期结束时,对象就变成垃圾,由java虚拟机自带的垃圾回收机制处理。
在主方法中使用new创造对象的同时,自动调用构造方法并执行。
7.6.2访问对象的属性和方法
使用new操作符创建一个对象后,可以使用"对象.类成员"来获取对象的属性和方法。
可以看到,两个对象的产生是相互独立的,t2改变了i值,不会影响到t1的i值。在内存中这两个对象的布局如图:
如果希望成员变量被对象共享,即在一个对象中改变这个成员变量时会影响另一个对象中的该成员变量,可以使用static关键字,此时这个成员变量为静态成员变量,归类所有,可以直接使用"类名.静态成员"的方式引用。
上面运行结果可以看到,由于t2改变了静态成员变量i的值,所以t1的成员变量i也变为60.
7.6.3对象的引用
在java中尽管一切都看做是对象,但真正的操作标识符实质上只是对象的一个地址,即标识符是指向对象的指针。
7.6.4对象的比较
在java中对象的比较有两种比较方式,分别是"==" 运算符与equal()方法,这两种方式有本质的区别。
从结果可以看出,两种方式的比较内容是不同的。equal()方法是String类中的方法,用于比较两个对象指针所指的内容是否相等;而"=="比较的是两个对象指针所指向的地址是否相等。由于c1和c2是两个不同的指针,两者在内存的位置不同,而"String c3=c1"语句将c1赋值给c3,两者指向同一块内存地址。
7.6.5对象的销毁
每个对象都有生命周期,当生命周期结束时,分配给该对象的内存地址将会被收回。java有一套完整的垃圾回收机制,用户不必担心废弃的垃圾对象占用内存。
在谈到垃圾回收机制之前,首先需要了解何种对象会被java虚拟机视为垃圾。主要包括以下几种情况:
(1)对象引用超过其作用范围,这个对象将被视作垃圾;
(2)将对象赋值给null;
垃圾回收器只能回收那些new操作符创建的对象,如果对象不是通过new操作符在内存区中获取一块存储区域,这种对象可能不能被垃圾回收机制所识别,所以java提供了一个finalize()方法,如果用户在类中定义了该方法,在垃圾回收时首先会调用该方法,在下一次垃圾回收动作发生时,才能真正回收被对象占用的内存。
需要明确的一点是,垃圾回收或finalize()方法不一定保证会发生,如java虚拟机内存损耗殆尽时,它是不会执行垃圾回收的。
由于垃圾回收不受人为控制,具体执行时间也不确定,所以finalize()方法也就无法执行。为此,java提供了System.gc()方法强制启动垃圾回收器,告知垃圾回收器进行清理。