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()