- Java程序设计与应用开发(第3版)
- 吴敏 於东军 李千目主编 成维莉 邵杰 姜小花副主编
- 3737字
- 2025-03-22 04:39:47
3.2 面向对象特性
基于面向对象特性,我们来剖析Java语言机制。
3.2.1 封装性
访问控制符是Java语言控制对方法和变量访问的修饰符。对象是对一组变量和相关方法的封装,其中变量表明了对象的状态,方法表明了对象具有的行为。通过对象的封装,实现了模块化和信息隐藏;通过对类的成员施以一定的访问权限,实现了类中成员的信息隐藏。
我们可以通过一个类的接口来使用该类。这里所指的接口不同于第4章中的接口(后者是Java语言的成分,是一种类型)。这里我们认为类的接口表示一个类的设计者和使用者之间的约定。一般来说,一个类同时具有两种接口:保护(protected)接口和公共(public)接口:
•一个类的保护接口是指该类中被声明为protected的所有变量和方法。通过保护接口扩展类,使子类可共享保护接口所提供的变量和方法,并且只能被子类所继承和使用。子类还可以改写接口中提供的方法,可以隐藏接口中提供的变量。子类也可以直接使用其超类的公共接口。
•一个类的公共接口是指该类中被声明为public的所有变量和方法。通过公共接口,该类可作为其他类的域成分(聚集),或者在其他类的方法中创建该类对象并使用(关联)。若要如此使用一个类,仅能通过其公共接口来进行。
一个类中除了保护接口和公共接口外,还有私有(private)成分。私有成分仅被该类所持有,并仅能被该类中的方法访问。类的私有成分被严格保护,不允许其他类直接访问,即使子类也不例外。
一个类把其私有成分封装起来,其保护接口和公共接口提供给其他类使用,这样的结构体现了类的“封装性”。封装性是面向对象程序设计的一个重要特征。我们在设计或使用一个类时,应谨慎考虑上述这些约定关系,依照类中各成分的语义做合适的安排。
1. private
类中用private修饰的成员,只能被这个类本身访问。
如果一个类的成员声明为private,则其他类(不管是定义在同一个包中的类,还是该类的子类或是别的类)都无法直接使用这个private修饰的成员。
例3.5 PrivateDemo.java
class Display{ private int data =8; private void displayData(){ System.out.println("data value=" + data); } } class EnhancedDisplay extends Display{ void enhancedDisplayData(){ System.out.println("********"); displayData(); System.out.println("********"); } void changeData(int x){ data = x; } } public class PrivateDemo{ public static void main(String args[]){ EnhancedDisplay ed = new EnhancedDisplay(); ed.enhancedDisplayData(); ed.changeData(100); ed.enhancedDisplayData(); } }
编译PrivateDemo.java,出现如下错误:
PrivateDemo.java:10: displayData() has private access in Display displayData(); ^ PrivateDemo.java:14: cannot resolve symbol symbol : variable i location: class EnhancedDisplay i = x; ^ 2 errors
2. default
类中不加任何访问权限限定的成员属于默认的(default)访问状态,这些成员可以被这个类本身和同一个包中的类所访问。例如,我们去掉例3.5中的两个private,然后再编译运行之,可以得到:
**************** data value=8 **************** **************** data value=100 ****************
3. protected
类中限定为protected的成员,可以被这个类本身、它的子类(包括同一个包中以及不同包中的子类)和同一个包中的所有其他类访问。
4. public
public成员可以被所有的类访问,包括包内和包外的类。
表3.3列出了上述这些访问控制符的作用范围。
表3.3 Java中类的访问控制符的作用范围比较

注:“*”表示可以访问。
注意:public可用于类、方法和变量。标以public的类、方法和变量,可为任何地方的任何其他类和方法访问。覆盖不能改变public的访问权限。没有被指定public或private的类、方法和变量,只能在声明它们的包(package)中访问。
3.2.2 继承性
面向对象中最为强大的功能是类的继承,继承允许编程人员在一个已经存在的类之上编写新的程序。比如想建立一个FillRect类,该类可以使用Rectangle类中所有已定义的数据和成员方法,如width、height等数据和getArea等方法,就可以通过继承来实现。为了继承Rectangle类,就必须引用旧的Rectangle类(使用extends关键字),并且在新类的说明中引用它。例如:
class FillRect extends Rectangle{ ... }
Java中所有的类都是通过直接或间接地继承Java.lang.Object类得到的。继承而得到的类称为子类,被继承的类称为父类。子类不能继承父类中访问权限为private的变量和方法。子类可以重写父类的方法,即命名与父类中同名的方法。
Java不支持多重继承,即不具有一个类从多个超类派生的能力,这种单一继承使类之间具有树型的层次结构。所有类共享该层次结构的根节点Object,也就是说,每个类都隐式地继承于Object。继承是面向对象程序设计的主要特点和优势之一。利用类继承,可利用已有的类方便地建立新的类,最大限度地实现代码重用。
Java由继承引出了“多态”的概念:方法的多态和类型的多态。
(1)方法的多态。3.1.2小节详细介绍了在一个类中方法的重载(overload),这是一种方法多态的形式。3.2.3小节还将引入另一种方法多态的形式:扩展类继承其超类的方法,它们有相同的基调,但对方法的实现进行了改写。这种方法多态形式在有些书中也称为方法的覆盖(override)。
(2)类型的多态。假设由超类F扩展出类Z,即类Z继承了超类F。由类Z实例化创建的对象d不仅属于类Z,而且属于其超类F,也就是说,对象d的域包含了超类F的域,因此对象d也是超类F的对象。所以创建一个类对象,也隐含着创建了其超类的一个对象,因此,类构建器往往需要调用其超类构建器。另一个结论是,一个类的对象不仅可以被创建类的类型所引用,也可以被其超类的类型所引用。所以Object类型的引用可以引用任何对象。
例3.6 VehicleDemo.java
//类的继承 class Vehicle{//车辆类 int VehicleID; //性质:车辆的ID号 void setID(int ID){ VehicleID=ID; } void displayID( ) { //方法:显示ID号 System.out.println("车辆的号码是:"+ VehicleID); } } class Car extends Vehicle{ //轿车类 int mph; //时速 void setMph(int mph){ this.mph=mph; } void displayMph( ) { //显示轿车的时速 System.out.println("轿车的时速是:"+ mph); } } public class VehicleDemo{ public static void main(String[] args){ //产生一个车辆对象 Car benz = new Car(); benz.setID(9527); benz.setMph(10); benz.displayID(); benz.displayMph(); } }
程序运行结果:
车辆的号码是:9527 轿车的时速是:10
在例3.6中,定义了Car和Vehicle两个类。显然,Car是Vehicle的一种,Car除了Vehicle的所有特性都有之外,它还延伸出自己的一些特性。
继承关系是由原来的类延伸而来,所以子类所产生的对象也是父类的一种。就以例3.6为例,类Vehicle是类Car的父类,因此,由Car产生的对象实体,也是一种Vehicle。这样的关系在程序中可以表示成:即使类型是父类的一个引用,也可以拿来引用子类所产生的对象。例如:
class Example { Vehicle Benz; Benz= new Car(); //父类型引用子类对象 }
1. 成员变量的隐藏和方法的重写
子类通过隐藏父类的成员变量和重写父类的方法,可以把父类的状态和行为改变为自身的状态和行为,例如:
class A{ int x; void setX(){ x=0; } } class B extends A{ int x; //隐藏了父类的变量x void setX(){ //重写了父类的方法 setX() x=5; } }
注意:子类中重写的方法和父类中被重写的方法要具有相同的名字,相同的参数表和相同的返回类型,只是方法体不同。
2. 关键字super
Java中通过关键字super来实现对父类成员的访问,super用来引用当前对象的父类。在扩展类的所有非静态方法中均可使用super关键字。在访问字段和调用方法时,super将当前对象作为其超类的一个实例加以引用。
使用super时应特别注意,super引用类型明确决定了所使用方法的实现。具体来说,super.method总是调用其超类的method实现,而不是在类层次中较下层该方法的任何改写后的实现。使用super关键字调用特定方法与其他引用不同,非super关键字调用根据对象的实际类型选择方法,而不是根据引用的类型。当通过super调用一个方法时,得到的是基于超类类型的方法的实现。
注意:super的使用有3种情况:
•访问父类被隐藏的成员变量,如super.variable。
•调用父类中被重写的方法,如super.method([paramlist])。
•调用父类的构建器,如super([paramlist])。
下面例子说明了super的作用。
例3.7 SuperDemo.java
class Father{ int x; Father(){ x=3; System.out.println("Calling Father : x=" +x); } void doSomething(){ System.out.println("Calling Father.doSomething()"); } } class Son extends Father{ int x; Son(){ //调用父类的构造方法 //super()必须放在方法中的第一句 super(); x=5; System.out.println("Calling Son : x="+x); } void doSomething() { super.doSomething( ); //调用父类的方法 System.out.println("Calling Son.doSomething()"); System.out.println("Father.x="+super.x+" Son.x="+x); } } public class SuperDemo{ public static void main(String args[]) { Son son=new Son(); son.doSomething(); } }
程序运行结果:
Calling Father : x=3 Calling Son : x=5 Calling Father.doSomething() Calling Son.doSomething() Father.x=3 Son.x=5
注意:在Java中,this通常指当前对象,super则指父类。当编程人员想要引用当前对象的某种东西,比如当前对象的某个方法,或当前对象的某个成员时,便可以利用this来实现这个目的,当然,this的另一个用途是调用当前对象的另一个构建器。如果编程人员想引用父类的某种东西,则非super莫属。
3. 类Object
类java.lang.Object处于Java开发环境的类层次的根部,其他所有的类都是直接或间接地继承了此类。Object类定义了一些最基本的状态和行为。
一些常用的方法如下。
•equals():比较两个对象(引用)是否相同。
•getClass():返回对象运行时所对应的类的表示,从而可得到相应的信息。
•toString():用来返回对象的字符串描述。
•finalize():用于在垃圾收集前清除对象。
•notify()、notifyAll()、wait():用于多线程处理中的同步。
3.2.3 多态性
在Java语言中,多态性体现在两个方面:由方法重载实现的静态多态性(编译时多态)和方法覆盖实现的动态多态性(运行时多态)。
(1)编译时多态。在编译阶段,编译器会根据参数的不同来静态确定调用相应的方法,即具体调用哪个被重载的方法。
(2)运行时多态。由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。
注意:重载方法的调用原则是,Java运行时系统根据调用该方法的实例,来决定调用哪个方法。对于子类的一个实例,如果子类重写了父类的方法,则运行时系统调用子类的方法;如果子类继承了父类的方法(未重写),则运行时系统调用父类的方法。
例3.8 Dispatch.java
class C{ void abc() { System.out.println("Calling C's method abc"); } } class D extends C{ void abc() { System.out.println("Calling D's method abc"); } } public class Dispatch{ public static void main(String args[]) { C c=new D(); c.abc( ); } }
程序运行结果:
Calling D's method abc
注意:在例3.8中,父类对象C引用的是子类的实例,所以Java运行时调用子类D的abc方法。
下面我们再举一个例子来说明在对象的创建过程中,发生在构建器中的多态性方法调用的例子。
例3.9 SuberClass.java
class BaseClass{ public BaseClass(){ System.out.println("Now in BaseClass()"); init(); } public void init(){ System.out.println("Now in BaseClass.init()"); } } public class SuberClass extends BaseClass{ public SuberClass(){ System.out.println("Now in SuberClass()"); } public void init() { System.out.println("Now in SuberClass.init()"); } public static void main(String[] args) { System.out.println("创建BaseClass对象:"); new BaseClass(); System.out.println("创建SuberClass对象:"); new SuberClass(); } }
程序运行结果:
创建BaseClass对象: Now in BaseClass() Now in BaseClass.init() 创建SuberClass对象: Now in BaseClass() Now in SuberClass.init() Now in SuberClass()