第六讲 类、对象和接口(二)    
 
  1 类的继承
  继承是类的另一个特性。继承的意义在于:我们重复使用或更改现成的类的方法,也可以加入新的数据成员以及新的方法,以满足新环境的需要。这种技术是所有面向对象的编程语言的一个基本特征。
  让我们来看一个例子:前面我们定义了一个Employee类,这只是普通员工,现在我们要定义一个经理类。经理也是员工的一种,所以Employee类中的数据和方法他也应该有;但经理又不同于普通员工,经理有秘书,而且涨工资的时候还要加上分红。怎么办?我们要不要从头开始写一个经理类?
  有了继承的技术,我们可以在Employee类的基础上,编写我们的Manager类。程序如下:
  package teach4;
  import java.util.Date;
  
  class Manager extends Employee
  {
   private String secretaryName;
  
  
   public Manager(String n, double s, int d)
   { super(n, s, d);
   secretaryName = "";
   }
  
   public void raiseSalary(double byPercent)
   { // add 1/2% bonus for every year of service
   Date today = new Date(2001,1,1);
   double bonus = 0.5 * (today.getYear() - getHireYear());
   super.raiseSalary(byPercent + bonus);
   }
  
   public void setSecretaryName(String n)
   { secretaryName = n;
   }
  
   public String getSecretaryName()
   { return secretaryName;
   }
  }
  我们以这个例子为例,学习继承的用法。
  首先,请注意这个类的头部有些不同:class Manager extends Employee;其中,关键字extends是扩展的意思,表明Manager类是从Employee类继承而来。我们把Employee叫做父类或者超类,把Manager叫做子类或者衍生类。一般来说,子类比父类有更多的功能。
  Manager的构造方法中有个语句:super(n, s, d),super是一个关键字,意思是调用父类的方法,在这里是父类,也就是Employee的构造方法;类似地,super.raiseSalary(byPercent + bonus)表示调用父类Employee的raiseSalary方法。所以,如果要在子类中调用父类的方法,使用super。
  Manage的构造方法调用了Employee的构造方法。事实上,一般要求子类用super语句调用父类的构造方法并附上恰当的参数。如果没有用super调用,将默认地调用父类的默认构造方法,这时,如果父类没有没有默认的构造方法,编译器将报错。在子类的构造方法中,如果有super语句,要求super语句在第一行。
  子类自动拥有父类的标志为public的成员变量和方法,比如:虽然我们在Manager类中没有定义print( )方法,但是boss.print()是合法的,因为print( )是Employee类的一个方法。如果希望改变父类中的方法,使之适合子类,我们也可以覆盖它。比如,因为经理的提薪方式是:除了上涨百分比,还要加上工龄*0.5的奖金,与普通员工的提薪方式就有不同。所以,我们要覆盖掉这个方法。实际上,只要重写这个方法就可以了。boss.raiseSalary(1.0),将自动调用Manager类里定义的raiseSalary()方法,而不是Employeee里的方法。
  但是,如果是私有的成员变量或者方法,也就是用private关键字修饰的那些,那么子类是不能访问的。如果希望子类能访问而别的类不能访问,我们可以用protected关键字,比如:protected String name;这样,name对于Manager来说是可以可见和可访问的,而对于不是Employee的子类的其他类,则是不能访问的。
  总结一下访问权限关键字:
  public:对全世界来说都是可见的;
  private:仅对类来说是可见的;
  protected:对所有子类和同一个包(package)来说是可见的;
  不声明:如果这三个关键字都没有,那么默认为对整个包可见。
  Manager类里定义的setSecretaryName()和getSecretaryName()方法,都只能为Manager类的对象调用,Employee类的对象是不能调用这两个方法的。
  继承可以是几层的。比如,我们可以以Manager为父类,再衍生出Executive类来。我们也可以从Employee类衍生出Programmer类来,它与Manager类没有什么关系。如果希望防止别人从自己编写的一个类中衍生出另一个类,可以加上关键字final,比如,不希望从Manager类中再衍生出别的类,我们可以把Manager类的声明改为:final class Manager extends Employee。这样可以提高程序的安全性,但可扩展性会受到影响。
  我们可以画出类的继承结构如下:
              

  2 造型
  也叫强制类型转换。回忆一下,我们在第二讲的Java的基本语法中,说到基本数据类型的强制类型转换,可以把一个浮点数强制转换为整型,比如:double x=3.14; int nx = (int)x;这样,x=3,把小数部分丢掉了。我们同样可以把类强制转换为另一个类。但不是什么时候都可以转换的,只有在继承结构图内部,才能进行强制类型转换。
  比如:Manager boss; Employee emp; 下面这个语句:emp=boss,相当于emp=(Employee)boss;因为emp是Employee类型的,而boss是Manager类型的,但是,这时候,emp将不能调用Manager类的方法,比如getSecretaryName(),如果要调用,必须把emp转换回来。比如:Manager manager; manager=(Manager)emp;这样,manager就可以调用getSecretaryName()了。
  但是,如果一个对象确实是Employee类型,现在要把它转换为Manager类型而且试图访问Manager的方法,编译将会通过,但是运行时将会出现异常,程序将中止运行。

  3 接口
  前面我们介绍Java的特点的时候说过,Java不允许多重继承,即一个类不允许有多个父类,至多只能有一个父类。在Java中,取代多重继承的技术是接口。Java是用接口技术的原因,是因为多重继承要么使编译器非常复杂,要么效率不高。
  那么,接口是什么呢?我们可以把接口理解为一个描述框架,里面定义了一些方法,但并不实现这些方法,而是由继承这个接口的类来实现。这样,如果某一个类继承了一个接口,意思是说:这个类实现了接口所定义的所有方法。
  比如,Java的标准库里定义了一个名为Comparable的接口。只要某个类是可对比的,就可以继承并实现这个接口。数字型和字符串型都是可比的,所以String, Float, Integer, Double等类都继承并实现了这个接口。如果我们定义我们的例子中的Employee类业是可比的,以进公司的年份来比较,那么我们也可以继承这个类,并实现它。(演示)
  注意,如果继承了一个接口,就必须实现这个接口所声明的所有方法。

  4 this对象引用
  有时会有这种情况,想全面地访问当前的对象,而不是某一个特殊的实例变量。This关键字引用方法所运作的那个对象。
  比如,我们可能经常会遇到这样的构造方法:this( )。这个方法会调用这个类中的另一个构造方法。我们来看看程序示例。

  5 类的包装和引入
  package和import语句
  包(Package) 由一组类(class)和接口(interface)组成。它是管理大型名字空间,避免名字冲突的工具。每一个类和接口的名字都包含在某个包中。按照一般的习惯,它的名字是由“.”号分隔的单词构成,第一个单词通常是开发这个包的组织的名称。
  定义一个包由package语句定义。如果使用package语句,编译单元的第一行必须无空格,也无注释。其格式如下:package packageName;若编译单元无package语句,则该单元被置于一个缺省的无名的包中。即:如果源文件中定义的类在放在同一个包中,但该包没有名字。
  import语句用来引入所需要的类。
  为了引用已有的类,使用import语句,import语句必须放在package语句(如果有的华)和类的定义之间。import语句的格式:
  import 包名.类名
  比如:
  import java.applet.Applet;
  import java.awt.Label;
  如果要引用一个包里所有的类,用*表示,比如:
  import java.awt.*;
  package语句用来指明该源文件定义的类所在的包,可以把包粗略理解为存放类的文件夹。比如,java.lang.*,就是java提供的一个程序包,里面放置了诸如数学方法等很多有用的程序,供大家调用。我们把package语句叫做包装语句。为了引用一个类,我们需要用引入语句:import。比如,我们要使用数学方法(比如求绝对值、求开方等),就要用import java.lang.Math,然后就可以调用Math里的方法了,非常方便。
  包装的作用是方便管理,避免重名。比如,我们的例子,通过 package teach5包装在teach4里的:以后如果谁要用我们的程序中的类,只要用import teach4就可以引用了。注意,同在一个package里的类,可以直接相互引用,不用import语句。比如我们例子中,EmployeeTest类使用了Employee类,但没有用 import Employee之类的语句,就是因为它们都被包装在teach5里。
  
  小结
  这一讲,我们主要学习了类的继承和接口。至此,我们学习了完Java语言的主要的必备的基础知识。同学们现在应该可以编写自己的面向对象的应用程序了。希望同学们多加练习,熟练掌握。下一讲开始,我们开始面向应用,学习编写小应用程序,也就是Java Applet。
  习题
  1.子类和父类的构造方法调用顺序是怎么样的,举例说明。
  2.编程实现“人”类、“学生”类和“大学生”类。“人”类是“学生”类的父类,“学生”类是“大学生”类的父类。“人”类的成员变量有:年龄,性别,籍贯;“学生”类的成员变量有:就读学校,成绩考评;“大学生”类的变量有:导师,每月补助。
  3.要求实现Comparable接口,按年龄比较两个人的大小;