从零开始学Java Web开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.2 类的继承性

继承性是面向对象语言的又一个基本特性,它是一种由已有的类创建新类的机制,正是因为有这种机制,才使得面向对象语言编写的程序代码具有较高的复用性。

3.2.1 类的继承

继承是指相关的类之间的层次关系。利用继承,可以先创建一个共有属性的一般类,根据该一般类再创建具有特殊属性的新类,新类继承一般类的状态和行为,并根据需要增加它自己的新的状态和行为。

由继承而得到的类称为子类,被继承的类称为父类。类继承的具体定义格式如下。

    class subclassname extends superclassname{
    …
    }

在上述类的声明中,通过使用关键字extends来创建一个类的子类,其中,subclassname是声明的子类的类名,superclassname是继承的父类的类名。

在3.1.1小节中介绍过Java类的声明,在当时定义的类并没有使用extends关键字,那么是不是表示当时定义的类没有继承其他的类呢?答案是否定的。在Java语言中,Object类是所有类的祖先类,也就是说所有的类都是直接或者间接继承Object类的。所以当没有使用extends关键字时,就表示这个类只是Object类的子类。

说明

与C++语言中的继承不同,Java语言不支持多重继承,子类只能有一个父类。

【实例3-7】实现类的继承。

首先定义父类Student。

    01  class Student {
    02      int stu_id;              //父类的成员变量
    03      void set_id(int id) {   //父类的成员方法
    04          stu_id=id;
    05      }
    06      void show_id() {         //父类的成员方法
    07          System.out.println(“The student_ID is:”+stu_id);
    08      }
    09  }

【代码说明】该类中第2行定义一个int型的成员变量,第3行和第6行分别定义名称为set_id和show_id()的两个成员方法。

然后定义继承Student类的子类Granduate类。

    01  class Granduate extends Student {
    02      int dep_number;              //子类的成员变量
    03      void set_dep(int dep_num) {   //子类的成员方法
    04             dep_number=dep_num;
    05      }
    06      void show_dep() {             //子类的成员方法
    07             System.out.println(“The department number is:”+dep_number);
    08      }
    09  }

【代码说明】该类是Student类的子类,因此,它可以直接使用Student类中定义的成员变量和成员方法,而无须重复定义。除此之外,在第2行还定义了自己的成员变量dep_number,在第3行和第6行分别定义了成员方法set_dep()和show_dep()。

最后定义调用Granduate类的应用程序类。

    01  public class Student_Show {
    02        public static void main(String args[]) {
    03             Granduate sun=new Granduate();          //创建子类对象
    04             sun.set_id(102);                        //调用父类中定义的方法
    05             sun.set_dep(6);                         //调用子类中定义的方法
    06             sun.show_id();                          //调用父类中定义的方法
    07             sun.show_dep();                         //调用子类中定义的方法
    08         }
    09  }

【代码说明】在应用程序类中的第3行创建了Granduate类,第4行和第6行分别调用其父类中定义的成员方法,第5行和第7行则分别调用子类中定义的成员方法。

【运行结果】该程序的输出结果如图3.3所示。

图3.3 程序运行结果

说明

根据类的继承关系,创建的子类对象一定也是父类的对象。例如上例中创建的Granduate类的对象sun也是其父类Student类的对象,但是反过来,父类的对象则不一定是子类的对象。

3.2.2 方法的重载和覆盖

当类之间出现继承关系之后,在子类中就将可能出现成员方法的重载和覆盖。这是面向对象编程中多态性的体现。

1.方法的覆盖

如果在子类中定义了与父类中的成员方法同名的成员方法,那么当子类的对象在程序中调用该成员方法时,调用的将是子类中新定义的成员方法,而子类中继承下来的父类中的成员方法就将被覆盖掉了,如果要访问被覆盖的成员方法,则只有通过父类的对象来调用它了。

例如,在实例3-7中子类Granduate中定义的成员方法与其继承的父类中的成员方法没有同名,所以不存在方法的覆盖。下面修改Granduate类的定义,具体代码如下:

    01  class Granduate extends Student {
    02      int dep_number;
    03      void set_dep(int dep_num) {
    04             dep_number=dep_num;
    05      }
    06      //定义与父类中成员方法同名的方法,实现方法的覆盖
    07      void show_id() {
    08             System.out.println(“The department number is:”+dep_number);
    09      }
    10  }

【代码说明】 子类Granduate中的第7行,定义了一个与父类Student中成员方法同名的方法,这就是方法的覆盖。

然后将调用Granduate类的应用程序类的代码修改如下:

    01  public class Student_Show {
    02        public static void main(String args[]) {
    03             Granduate sun=new Granduate();
    04             sun.set_id(102);
    05             sun.set_dep(6);
    06             //由于方法覆盖,这里调用的将是子类中定义的show_id()方法
    07             sun.show_id();
    08             }
    09  }

图3.4 调用子类中覆盖后的方法

【代码说明】在应用程序的第7 行通过子类实例调用了show_id()方法,由于在子类和父类中都定义了该方法,那么通过子类调用时将调用子类中定义的show_id()方法。

【运行结果】执行应用程序时,将调用子类中定义的show_id()方法,最终程序输出结果如图3.4所示。

2.方法的重载

如果在一个类中,定义了两个或者两个以上的具有不同参数列表的同名方法,这种情况就被称为方法的重载,方法的重载体现了Java作为面向对象语言的多态性。

那么什么是不同的参数列表呢?仅仅是形参名称不同的两个参数列表实际上仍然是相同的,但如果是参数的个数或者参数的数据类型不同,则这样的参数列表才是不同的。因此,同一个对象在调用具有相同名称的方法时,会根据传递进来的参数的个数或数据类型的不同,而自动选择对应的方法。

在类的继承关系中,如果当子类中定义了与父类中同名的方法时,但是方法的参数列表不同,这时在子类中将对该方法进行重载,即子类中既继承下来父类的方法,又定义了自己的新的成员方法,不会对父类的方法进行覆盖。

下面修改实例3-7中的Granduate类的定义,具体代码如下:

    01  class Granduate extends Student {
    02      int dep_number;
    03      void set_dep(int dep_num) {
    04             dep_number=dep_num;
    05      }
    06      //定义与父类中成员方法同名的方法,但参数列表不同
    07      void show_id(String name) {
    08             System.out.println(“The department number of”+name+” is:”+dep_number);
    09      }
    10  }

【代码说明】在该类中的第7行定义了与父类中成员方法同名的方法,但是与父类中的show_id()方法的参数列表不同,因此实际上在Granduate类中将存在两个名称为show_id的成员方法,但是这两个方法一个是参数为空,一个是具有一个字符串类型参数。

然后将调用Granduate类的应用程序类的代码修改如下:

    01  public class Student_Show {
    02        public static void main(String args[]) {
    03             Granduate sun=new Granduate();
    04             sun.set_id(102);
    05             sun.set_dep(6);
    06             //由于方法重载,这里调用子类中的两个不同的show_id()方法
    07             sun.show_id();
    08             sun.show_id(“sun”);
    09             }
    10  }

【代码说明】在应用程序的第7 行和第8 行分别两次调用了show_id()方法,但是这两次调用的方法的参数不同,第7行将调用父类Student中定义的show_id()方法,第8行将调用子类Granduate中定义的show_id()方法。

【运行结果】执行应用程序时,将调用子类中重载的两个不同的show_id()方法,最终程序输出结果如图3.5所示。

图3.5 调用重载的方法

3.2.3 抽象类和最终类

在一般情况下,Java中的所有的类都可以被其他类继承,也可以直接被实例化使用。但是有两种特殊的类,一种是专门用来给其他类做父类的,自己不能够被实例化,另一种是不能够再被其他类所继承的。这就是本小节要详细介绍的抽象类和最终类。

1.抽象类

面向对象的编程思想使得开发者可以编写模块化的程序,然后用类似搭积木的方式来组织这些模块以实现特定的功能。甚至可以对一个未定的功能预留一个模块的位置,留待以后去实现。

将这种思想应用于类的定义中,对于某种未定的操作,可以在类中先只定义方法声明,不定义方法实现,以后再在这个类的子类中去具体实现这个方法。这样的方法被称为抽象方法,而包含抽象方法的类就被称为抽象类。

抽象方法和抽象类的定义方式都是在方法名和类名之前加上abstract关键字。其中,抽象方法的声明与普通方法基本一致,也需要有方法名、参数列表和返回值类型,只是没有方法体。类的成员方法的声明格式如下:

    [<修饰符>] abstract <返回类型> <方法名> ([<参数列表>]);

说明

在参数列表的括号后面一定要加上分号。

抽象类由于包含抽象方法,因此它不能使用new关键字进行实例化,也不能在类中定义构造函数和静态方法,但是抽象类中可以包含具体实现了的非静态方法。所以读者不要误解成抽象类中的方法全部都是抽象方法,而是只有一个类中包含一个抽象方法,那么这个类就是抽象类。抽象类的子类中应该对抽象方法进行具体的实现,否则子类本身也就成为抽象类了。

【实例3-8】抽象类的定义和使用。

首先定义抽象类Student,该类中包含一个抽象方法show_id()。

    01  abstract class Student {
    02      int stu_id;
    03      void set_id(int id) {
    04          stu_id=id;
    05      }
    06      //定义抽象方法
    07      abstract void show_id();
    08  }

【代码说明】在该抽象类的第7行定义了一个抽象方法show_id()。

然后定义继承Student类的子类Granduate类。

    01  class Granduate extends Student {
    02      int dep_number;
    03      void set_dep(int dep_num) {
    04             dep_number=dep_num;
    05      }
    06      //具体定义父类中的抽象方法
    07      void show_id() {
    08             System.out.println(“The department number is:”+dep_number);
    09      }
    10  }

【代码说明】在该类的第7行定义了父类中的抽象方法show_id()的具体实现。

说明

必须具体定义父类中的抽象方法,否则该类也将成为抽象类。

最后定义调用Granduate类的应用程序类。

    01  public class Student_Show {
    02        public static void main(String args[]) {
    03             Granduate sun=new Granduate();//创建子类的实例
    04             sun.set_id(102);
    05             sun.set_dep(6);
    06             sun.show_id();//调用子类中已经实现了的父类中的抽象方法
    07             }
    08  }

【代码说明】在应用程序类的第6行调用show_id()方法,由于该方法已经在Granduate类中被实现,所以可以被正确调用。

【运行结果】执行应用程序,将调用子类中具体定义后的show_id()方法,最终程序输出结果与如图3.4所示的结果一样。

2.最终类

如果在一个类定义前加上final关键字,那么这个类就不能再被其他的类所继承,这样的类被称做最终类。最终类可以避免开发者编写的类被别人继承后加以修改。

例如,下面的类定义和继承:

    final class A {
          …..
    }
    class B extends A {
          ….
    }

这样的继承将是错误的,在程序编译时将提示类A是不能被继承的。

final关键字除了可以用在声明最终类时,还可以应用在成员变量和成员方法的声明上,如果在成员变量的定义前加上final关键字,则这个变量的值在以后的程序中只能被引用,而不能被改变,final在这里的作用相当于C++语言中的const。

说明

使用final的成员变量必须在声明时同时给定初始值。

例如,声明不能被修改的成员变量的示例代码如下:

    class A {
          final float PI=3.14159f;
          final float E=2.71828f;
          ….
    }

如果在成员方法定义前加上final关键字,那么表示这个方法在子类中不能被覆盖。