“is-a” 关系是继承的一个明显特征。java
注释: 咱们使用员工和经理的例子,不过这个例子要有所保留。在真实的世界里,员工也可能会成为经理,因此你建模时可能但愿经理也是员工,而不是员工的一个子类。不过,在例子中,假设公司只有两类人:一些人永远是员工,另外一些人一直是经理。程序员
Employee 类:数组
import java.time.LocalDate; import java.util.Objects; public class Employee { private String name; private double salary; private LocalDate hireDay; public Employee(String name, double salary, int year, int month, int day) { this.name = name; this.salary = salary; hireDay = LocalDate.of(year, month, day); } public String getName() {return name;} public double getSalary() {return salary;} public LocalDate getHireDay() {return hireDay;} public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } @Override public String toString() { String str = "%s[name=%s, salary=%.2f, hireDay=%s]"; return String.format(str, getClass(), name, this.getSalary(), hireDay); } }
Manager 类:ide
public class Manager extends Employee { private double bonus; public Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); this.bonus = 0; } public void setBonus(double bonus) { this.bonus = bonus; } @Override public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } }
继承 Employee 类来定义 Manager 类,使用关键字 extends 表示继承。this
public class Manager extends Employee { // added methods and fields }
在 Java 中,全部的继承都是公共继承。设计
关键字 extends 代表正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)、基类(base class)或父类(parent class);新类称为子类(subclass)、派生类(derived class)或孩子类(child class)。超类和子类是 Java 程序员最经常使用的两个术语,而了解其余语言的程序员可能更加偏心使用父类和孩子类,这可能很贴切地体现了“继承”。code
尽管 Employee 类是一个超类,但并非由于它优于子类或者拥有比子类更多的功能。实际上偏偏相反,子类比超类拥有的功能更多。例如,读过 Manager 类的源代码以后就会发现,Manager 类比超类 Employee 封装了更多的数据,拥有更多的功能。orm
注释: 前缀 “超”(super) 和 “子”(sub) 来源于计算机科学与数学理论中集合语言的术语。全部员工组成的集合包含全部经理组成的集合。能够这样说,员工集合是经理集合的超集,也能够说,经理集合是员工集合的子集。对象
在 Manager 类中,增长了一个用于存储奖金信息的字段,以及一个用于设置这个字段的新方法 :blog
public class Manager extends Employee { // 奖金 private double bonus; ... public void setBonus(double bonus) { this.bonus = bonus; } }
这里定义的方法和字段并无什么特别之处。若是有一个 Manager 对象,就可使用 setBonus 方法。
Manager boss = ...; boss.setBonus(5000);
因为 setBonus 方法不是在 Employee 类中定义的,全部属于 Employee 类的对象不能使用它。
然而,尽管在 Manager 类中没有显式地定义 getName 和 getHireDay 等方法,可是能够对 Manager 对象使用这些方法,这是由于 Manager 类自动地继承了超类 Employee 中的这些方法。
相似地,从超类中还继承了 name、salary 和 hireDay 这 3 个字段。这样一来,每一个 Manager 对象就包含了 4 个字段:name、salary、hireDay 和 bonus。
经过扩招超类定义子类的时候,只须要指出子类与超类的不一样之处。所以在设计类的时候,应当将最通常的方法放在超类中,而将更特殊的方法放在子类中,这种将通用功能抽取到超类的作法在面向对象设计中十分广泛。
超类中的有些方法对子类 Manager 并不必定适用。具体来讲,Manager 类中的 getSalary 方法应该返回薪水和奖金的总和。为此,须要提供一个新的方法覆盖(override)超类中的这个方法:
public class Manager extends Employee { ... public dobule getSalary() { ... } }
应该如何实现这个方法呢?乍看起来彷佛很简单,只要返回 salary 和 bonus 字段的总和就能够了:
public double getSalary() { return salary + bonus; // 不能运行 }
不过,这样作是不行的。回想一下,只有 Employee 方法能直接访问 Employee 类的私有字段。这意味着,Manager 类的 getSalary 方法不能直接访问 salary 字段。若是 Manager 类的方法想要访问那些私有字段,就要像全部其余方法同样使用公共接口,在这里就是要使用 Employee 类中的公共方法 getSalary。
如今,再试一下。你须要调用 getSalary 方法而不是直接访问 salary 字段:
public double getSalary() { double baseSalary = getSalary(); // 仍然不能运行 return baseSalary + bonus; }
这段代码有问题。问题出如今调用 getSalary 的语句上,它只是在调用自身,这是由于 Manager 类也有一个 getSalary 方法,因此这条语句将会致使无限次调用本身,直到整个程序最终崩溃。
这里须要指出:咱们但愿调用超类 Employee 中的 getSalary 方法,而不是当前类的这个方法。为此,可使用特殊的关键字 super 解决这个问题:
super.getSalary()
这个语句调用的是 Employee 类中的 getSalary 方法。下面是 Manager 类中 getSalary 方法的正确版本:
public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; }
注释: 有些人认为 super 与 this 引用是相似的概念,实际上,这样比较并不太恰当。这是由于 super 不是一个对象的引用,例如,不能将值 super 赋给另外一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
在子类中能够增长字段、增长方法或覆盖超类的方法,不过,继承绝对不会删除父类任何字段或方法。
public Manager(String name, double salary, int year, int month, int day) { super(name, salary, year, month, day); bonus = 0; }
这里的关键字 super 具备不一样的含义。语句
super(name, salary, year, month, day);
是 “调用超类中含有 name、 salary、year、month 和 day 参数的构造器” 的简写形式。
因为 Manager 类的构造器不能访问 Employee 类的私有字段,因此必须经过一个构造器来初始化这些私有字段。能够利用特殊的 super 语法调用这个构造器。使用 super 调用构造器的语句必须是子类构造器的第一条语句。
若是子类的构造器没有显式地调用超类的构造器,将自动地调用超类的无参数构造器。若是超类没有无参数构造器,而且在子类的构造器中又没有显式地调用超类的其余构造器,则 Java 编译器将报告错误。
this 关键字有两个含义:
super 关键字有两个含义:
在调用构造器的时候,this 和 super 这两个关键字紧密相关。调用构造器的语句只能做为另外一个构造器的第一条语句出现。构造器参数能够传递给当前类(this)的另外一个构造器,也能够传递给超类(super)的构造器。
从新定义 Manager 对象的 getSalary 方法以后,奖金将会自动添加到经理的薪水中。
下面给出一个例子说明这个类的使用。咱们要建立一个新经理,并设置他的奖金:
Manager boss = new Manager("Carl Cracker", 75000, 1987, 12, 15); boss.setBonus(500000);
下面定义一个包含 3 个员工的数组:
Employee[] staff = new Employee[3];
在数组中混合填入经理和员工:
staff[0] = boss; staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1); staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
输出每一个人的信息:
for (Employee e : staff) { System.out.println(e); }
运行这条循环语句将会输出下列数据:
Carl Cracker 575000.0 Harry Hacker 50000.0 Tommy Tester 40000.0
这里的 staff[1] 和 staff[2] 仅输出了基本薪水,这是由于它们是 Employee 对象,而 staff[0] 是一个 Manager 对象,它的 getSalary 方法会将奖金和基本薪水相加。
须要提醒你们的是,如下调用
e.getSalay()
可以选出应该执行的正确 getSalary 方法。请注意,尽管这里将 e 声明为 Employee 类型,但实际上 e 便可以引用 Employee 类型的对象,也能够引用 Manager 类型的对象。
当 e 应用 Employee 对象时,e.getSalary() 调用的是 Employee 类中的 getSalary 方法;当 e 引用 Manager 对象时,e.getSalary() 调用的是 Manager 类中的 getSalary 方法。虚拟机知道 e 实际引用的对象类型,所以可以正确地调用相应的方法。
一个对象变量能够指示多种实际类型的现象被称为多态(polymorphism)。在运行时可以自动地选择调用哪一个方法的现象称为动态绑定(dynamic binding)。
在 Java 中,动态绑定是默认的行为。若是不但愿让一个方法虚拟的,能够将它标记为 final。
继承并不只限于一个层次。例如,能够由 Manager 类派生 Executive 类。
由一个公共超类派生出来的全部类的集合被称为继承层次(inheritance hierarchy),在继承层次中,从某个特定的类到其祖先的路径称为该类的继承链(inheritance chain)。
一般,一个祖先类能够拥有多个子孙继承链。例如,能够由 Employee 类派生出子类 Programmer 和 Secretary,它们与 Manager 类没有任何关系(它们彼此之间也没有任何关系)。必要的话,能够将这个过程一直延续下去。
Java 不支持多重继承,但提供了一些相似多重继承的功能——接口。