本节将学习类的继承特性及一些特殊类,本节所述实例中的类设计参考自《java核心技术》。
· java中使用关键字extends表示继承,所有的继承都是公有public继承(区别于c++中的三种继承方式即public、protected、private)。
· 子类中可以增加域、增加方法或者覆盖超类的方法,但是绝对不能删除继承的任何域和方法。因此在设计类的时候应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。
· 子类可以通过super关键字类调用超类的方法或者超类的构造器,可以使用this关键字类引用隐式参数或者调用本类的其他构造器。注意super关键字不是一个对象的引用,不能将其赋给另一个对象变量,它只是一个编译器调用超类方法的特有关键字。
经理与雇员类的设计如图1所示:
图1 职员与经理类的继承关系图
测试代码如例5-1:
例5-1 ManagerTest.java
package com.learningjava; import java.util.Date; import java.util.GregorianCalendar; /** * this program demonstrate inheritance * from the book 《Core Java,Volume I:Fundamentals》 */ public class ManagerTest { public static void main(String[] args) { Employee[] staffs = new Employee[3]; staffs[0] = new Employee("Steve",5000,1985,9,4); staffs[1] = new Employee("Jason",6000,1983,8,12); Manager mgr = new Manager("Jery",8000,1980,11,7); mgr.setBonus(5000); staffs[2] = mgr; for(Employee item : staffs) System.out.println("name= "+item.getName()+ ",salary= "+item.getSalary()); } } /** * a class to descript employee * from the book 《Core Java,Volume I:Fundamentals》 */ class Employee { /** * @param name name to set * @param salary salary to set * @param hireday hireday to set */ public Employee(String name, double salary, Date hireday) { this.name = name; this.salary = salary; this.hireday = hireday; setId(); } /** * * @param name name to set * @param salary salary to set * @param year month day to create a GregorianCalendar */ public Employee(String name, double salary, int year,int month,int day) { this.name = name; this.salary = salary; GregorianCalendar calendar = new GregorianCalendar(year,month-1,day); this.hireday = calendar.getTime(); setId(); } public void raiseSalary(double percent) { double raise = salary*percent/100; salary += raise; } public String getName() { return name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public Date getHireday() { return hireday; } public void setHireday(Date hireday) { this.hireday = hireday; } public int getId() { return id; } private void setId() { this.id = nextId; nextId++; } private String name; private double salary; private Date hireday; private int id; private static int nextId = 1; } /** * a class to descript manager * */ class Manager extends Employee { public void setBonus(double b) { bonus = b; } public Manager(String name, double salary, int year,int month,int day) { super(name, salary,year,month,day); bonus = 0; } public Manager(String name, double salary, Date hireday) { super(name, salary, hireday); bonus = 0; } @Override public double getSalary() { double baseSalary = super.getSalary(); return baseSalary+bonus; } private double bonus; }
· 检验是否应该使用继承的规则
类之间最常见的有三种关系即,依赖(use-a)、聚合(has-a)、继承(is-a)关系。
is-a表明子类的每个对象也是超类的对象。例如每个经理都是雇员,因此可以将Manager类设计成为Employee类的子类,反之每个雇员却并不都是经理。
1) final类——阻止继承
如果希望阻止人们继承某个类,可以使用final关键字将该类定义为final类,String类就是一个final类,声明为public final class String。final关键字作用于类的域时称为final域,表明该域在构造对象以后就不能修改其值了;final作用于方法时称为final方法,表明该方法不能被子类覆盖。注意,final类的方法自动地成为final方法,而域不自动为final域。
阻止继承主要是为了确保它们不会在子类中改变语义。
2) abstract类——高层次抽象
抽象类是对类进行高度抽象后形成的类,它比一般类更加通用。
规定,包含一个或多个抽象方法的类本身必须声明为抽象类。
抽象方法起着占位的作用,它的具体实现在它的具体实现子类中。注意,拓展抽象类有两种选择。一种是在子类中定义全部的抽象的方法,这样一来,子类不再抽象;还有一种是子类中定义部分抽象方法或抽象方法也不定义,这样子类也成为抽象类。
注意,类即使不含抽象方法,也可以将其声明为抽象类。
注意,抽象类不能创建出实例,而只作为派生其他类的基类。但是可以定义一个抽象类的 对象引用变量,用于引用非抽象子类的对象。
例如设计一个抽象类层次如下图2所示:
图2 抽象继承关系
具体可参考例5-2:
例5-2 PersonTest.java
package com.learingjava; import java.util.Date; import java.util.GregorianCalendar; /** * this program demonstrates abstract classes * from the book 《Core Java,Volume I:Fundamentals》 */ public class PersonTest { public static void main(String[] args) { Person[] people = new Person[2]; //fill array people[0] = new Employee("Steven",5000,1985,9,4); people[1] = new Student("Jobs","computer science"); //print info for(Person item : people) System.out.println(item.getName()+","+item.getDescription()); } } /** * an abstract class * with abstract method getDescription() */ abstract class Person { public Person(String name) { super(); this.name = name; } public String getName() { return name; } //abstract method public abstract String getDescription(); private String name; } /** * a class to descript employee * from the book 《Core Java,Volume I:Fundamentals》 */ class Employee extends Person{ /** * @param name name to set * @param salary salary to set * @param hireday hireday to set */ public Employee(String name, double salary, Date hireday) { super(name); this.salary = salary; this.hireday = hireday; setId(); } /** * * @param name name to set * @param salary salary to set * @param year month day to create a GregorianCalendar */ public Employee(String name, double salary, int year,int month,int day) { super(name); this.salary = salary; GregorianCalendar calendar = new GregorianCalendar(year,month-1,day); this.hireday = calendar.getTime(); setId(); } public void raiseSalary(double percent) { double raise = salary*percent/100; salary += raise; } @Override public String getDescription() { return String.format("an employee with a salary of ¥%.2f", salary); } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public Date getHireday() { return hireday; } public void setHireday(Date hireday) { this.hireday = hireday; } public int getId() { return id; } private void setId() { this.id = nextId; nextId++; } private double salary; private Date hireday; private int id; private static int nextId = 1; } /** * a class to descript student * */ class Student extends Person { /** * * @param name the student's name * @param major the student's major */ public Student(String name,String major) { super(name);//pass the name to the superclass constructor this.major = major; } @Override public String getDescription() { // TODO Auto-generated method stub return "a student majoring in "+major; } private String major; }
3) Object类——所有类的超类
java中每个类都是由Object类派生而来的,如果没有明确指出某个类的超类,则该类的超类就是Object。
注意,Object类作为所有类的超类,这种类型的引用变量可以引用任何类型的对象。
但是要想对其引用的对象进行具体的操作,还需要清楚该对象该的原始类型,并进行相应的类型转换。如:
Object obj = new Student("Jobs","computer science");
Student stu = (Student) obj;//change to the origin type
还要注意,所有的数组类型,不管是对象数组还是基本数据类型的数组都拓展于Object类。
Student[] students = new Student[10];
Object obj = students;//ok
Object类有四个关键方法,需要我们注意,参见《JAVA学习脚印7: Object类的四个关键方法》。
4)enum枚举类
java中所有的枚举类型都是Enum类的子类,定义简单的枚举类如下:
public enum Size {SMALL,MEDIUM,LARGE,EXTRAL_LARGE};可以给枚举类增加构造函数、方法和域。
获取枚举常量时使用Enum类的静态方法valueOf,其原型为:
public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name)
例如: Size size = Enum.valueOf(Size.class, "SMALL");构造了枚举变量
注意,在比较两个枚举类型的值时,不要使用equals,而是直接使用"=="就可以了。
5)Class类
java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息保存着每个对象所属的类足迹,保存着些信息的类被称为Class类。例如如下代码:
Manager mgr = new Manager("Jery",8000,1980,11,7); Class<? extends Manager> cl = mgr.getClass(); System.out.println(cl.getName()); System.out.println(cl.getClass().getName());
输出:com.learningjava.Manager
java.lang.Class
Class对象表示一个特定类的属性,例如这里的cl表示com.learningjava.Manager类的属性。
Class类是java语言提供的反射库中的一个类,能够分析类能力的程序被称为反射(reflective)。反射机制主要应用在工具构造上,反射机制可参考:《java学习脚印:Class类与反射机制》。