子类在保留父类基本属性与行为的基础上,可以增加新的属性或行为,或者修改属性和行为。
子类继承父类,父类派生子类,子类还可以派生子类,这样就形成了类的层次结构。
JAVA中继承通过以下形式实现:
class 子类名 extends 父类名 【implements<接口名>】{
}
java语言通过使用extends来实现类的继承,如果类定义时没有使用extends关键字,则默认该类的父类是java.lang.Object类。Object类是java预定义的所有类的父类,包含了所有java的公共属性,其中定义的属性和方法局可以被任何类使用、继承或修改。
为了避免二义性:java语言规定,一个子类只能有一个父类,即java不支持多继承,只是单继承。
子类继承父类时遵循普遍性原则 和 特殊性原则。普遍性原则是子类继承父类已有的成员变量和方法(构造方法不可继承,是调用,个人理解);特殊性原则是指子类可增加父类中没有的变量和方法,或修改父类中已有的变量和方法。
【例 5 -12】继承应用举例。
Employee (雇主)为父类,Manager(经理)为子类,经理是一类特殊的雇员,具有雇员一般的性质和行为(方法)。
另外,经理在一般雇员的基础上,增加了工作的特殊性:享受特殊的津贴。
Ex5_12_Inheritance.java
package com.ch5;
public class Ex5_12_Inheritance {
public static void main(String[] args) {
Manager_ex mrZhang = new Manager_ex();
mrZhang.setName("张刚"); // 调用set方法为对象设置属性值
mrZhang.setDepartment("教务处");
mrZhang.setSalary(2500);
// 以上是public权限的属性 父类属性,子类继承
mrZhang.setSpecial("教务处处长");
mrZhang.setSubsidy(500);
// 以上是private权限的子类 属性 新增属性
System.out.println("*********************员工信息*********************");
System.out.println();
System.out.println(mrZhang.toString());
}
}
class Employee{
protected String name;
protected double salary;
protected String department;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
@Override
public String toString() {
return "Employee [name=" + name + ", salary=" + salary
+ ", department=" + department + "]";
}
}// class Employee end
class Manager_ex extends Employee{
// Manager_ex子类名字 ,因为本包中存在了一个名字为Manager的类,所以换了个名字
private String special;
private double subsidy;
public String getSpecial() {
return special;
}
public void setSpecial(String special) {
this.special = special;
}
public double getSubsidy() {
return subsidy;
}
public void setSubsidy(double subsidy) {
this.subsidy = subsidy;
}
@Override
public String toString() {
return "name=" + name + ", salary=" + salary
+ ", department=" + department + ",special=" + special + ", subsidy=" + subsidy;
}
/**
* 子类可以从父类继承成员变量和方法,但受访问权限的限制。
* 如果父类和子类分别定义在不同的包中,子类只能访问父类中 public,protected权限的变量;
* 如果父类和子类在同一个包中,子类能访问父类中的public,protected,默认权限的变量,
* 父类中的private权限的变量,对子类不可见。
*/
}
结果:
***********员工信息***********
name=张刚, salary=2500.0, department=教务处,special=教务处处长, subsidy=500.0
子类在继承父类时,可能会出现变量隐藏,方法覆盖(Overriding)等现象。
变量隐藏:子类的成员变量和父类的成员变 同名时,此时,父类的成员变量被隐藏。
方法覆盖是指:是指子类的方法名 和父类的方法名相同,方法的返回值类型,入口参数的数目,类型,顺序均相同,只是方法实现的功能不同,此时父类的方法被覆盖。
如果子类服药调用父类被隐藏的变量或被覆盖的方法,可以使用super关键字实现。
package com.ch5;
public class Ex5_13_Super1 {
/**
* 用super关键字访问父类被隐藏的成员变量 或 被覆盖的方法
* @param args
*/
public static void main(String[] args) {
AClass aClass = new AClass();
System.out.println("aClass对象创建完毕");
BClass Bclass = new BClass();
System.out.println("bClass对象创建完毕");
aClass.p1();
System.out.println("aClass.p1();语句执行完毕");
Bclass.p1();
}
}
class AClass{
int a;
float a1;
public AClass(){
a = 50;
a1 = 99.99f;
System.out.println("public AClass()构造方法执行了");
}
public void p1(){
System.out.println("this is a method of A");
System.out.println("a = " + a);
}
} // AClass end
class BClass extends AClass{
int a; // 与父类成员变量名相同,属于变量隐藏现象
public BClass(){
a = 10;
a1 = 123.6f;
System.out.println("public BClass()构造方法执行了");
}
public void p1(){ // 与父类成员方法相同,属于方法覆盖新现象
System.out.println("this is a method of B");
System.out.println("a = " + a); // 此处的a是BClass的变量值 即啊= 10
super.p1(); // 通过super调用被覆盖的父类成员方法
System.out.println("super.a = " + super.a); // 调用 被隐藏的成员变量
}
}
/**
* 在本例中,BClass 继承了AClass时,出现了变量隐藏和方法覆盖的现象,
* BClass通过super关键字调用被隐藏的父类成员变量 a 和 被覆盖的父类成员方法p1().
*
* super除了调用父类被隐藏的变量和 被覆盖的方法外,还可以显示的调用父类的构造方法。
*
* 综上所述,java提供关键字super来访问父类的成员和方法,具体有以下三种情况
* (1)用来调用父类 中被覆盖的方法
* (2)用来调用父类中被隐藏的成员变量
* (3)用来调用父类的构造方法。
*/
当用子类构造方法创建一个子类对象时,子类的构造方法总会显示的或隐式地 先 调用父类的某个构造方法。
如果子类的构造方法没有明显地指明调用父类的哪个构造方法,java会默认地调用父类的无参构造方法;子类也可以通过super关键字,显示地调用父类的构造方法,具体调用哪个根据super()的参数类型决定。
【例5-14】使用super调用父类的构造方法。
package com.ch5;
public class Ex5_14_SuperUse {
public static void main(String[] args) {
// TODO Auto-generated method stub
SubClass sc1 = new SubClass(); // 因为父类有 有参构造方法,所以系统
System.out.println();
SubClass sc2 = new SubClass(400);
}
}
class SuperClass{
private int n;
SuperClass(){
System.out.println("SuperClass()构造方法执行完毕*********");
}
SuperClass(int n){
System.out.println("****标志1****");
System.out.println("SuperClass(" +n+ ")");
this.n = n;
}
}
class SubClass extends SuperClass{
private int n;
SubClass(){
super(300); // B行 显式地调用父类的特定构造方法 SuperClass(int n)
System.out.println("***标志2***");
System.out.println("SubClass()");
}
SubClass(int n){
// super(); // A行 显式的调用父类的构造方法 SuperClass()
System.out.println("SubClass(" +n+ ")");
this.n = n;
}
}
结果:
标志1
SuperClass(300)
标志2
SubClass()
SuperClass()构造方法执行完毕***
SubClass(400)
子类创建时,系统默认自动调用父类的无参构造方法(这里的无参构造方法是指手动创建的无参构造方法 或者 系统提供的默认无参构造方法)【需要注意的是:如果父类有了有参数的构造方法,系统将不在提供无参构造方法,此时子类一定要避免使用父类的无参构造函数(因为可能父类没有这个函数)】。
即使注释掉A行,结果还是不变。因为父类的无参构造函数时被调用的,不管有没有super();
创建sc1的时候,没有执行父类的无参构造函数,因为 B行那里显式调用了特定的构造函数,所有无参的父类构造函数没有执行。要注意“默认”的意思,这时候就不是默认的情况了。
【例5-15】子类错误调用父类无参构造方法的示例
Ex5_15_Convert.java
package com.ch5;
public class Ex5_15_Convert {
/**
* 子类错误的调用父类的无参构造函数
*/
public static void main(String[] args) {
Doctor d = new Doctor("wang",1,"wu");
}
}
class Doctor{
String name;
int ID;
String address;
public Doctor(String name1,int ID1,String address1){
name = name1;
ID = ID1;
address = address1;
System.out.println("名字 :" + name);
System.out.println("编号 :" + ID);
System.out.println("地址 :" + address );
}
public Doctor(){
// 手写 无参构造方法 如果不定义该无参构造方法,将提示 A行错误
/**
* 假设没有这个无参构造方法,因为有了有参构造方法 public Doctor(String name1,int ID1,String address1)
* 所以系统也不会给 Doctor类提供默认的无参构造方法,但是A行要调用无参构造方法,因为父类没有这个方法被调用,所以出错
*
* 其实,只有有了构造方法,不管这个构造方法有没有参数,系统都不会提供无参的构造方法了
*/
}
}
class Specialist extends Doctor{
public Specialist(String name1,int ID1,String address1){
super(name1,ID1,address1);
}
public Specialist(){
super();// 显式 调用 无参构造方法 这里可以注释,因为父类有无参构造方法,子类会隐式调用
name = "dulu";
}
}
在本例中,Specialist 类是Doctor类的子类,而Doctor 类中定义了Doctor(String name1,int ID1,String address1)构造方法,所以系统不再默认提供无参的构造方法(这里自定义了一个,不是系统提供的)
因此Specialist 类如果显式地调用super,A行错误;不显式调用,也会出错,因为会默认调用。所以手动编写了一个无参构造方法,这样就不会出错了。
因此,调用构造方法要注意以下几个原则:
(1)创建对象时调用该父类的构造方法,只要在子类的构造方法中,将第一个语句写为super语句即可,显示调用。可以有参数,也可以没有参数。
(2)如果子类构造方法中第一条语句没有用super来调用父类的构造方法,则编译器也会用super()调用父类的无参构造方法。
(3)如果某个类的构造方法第一条语句是this()调用本类外的第一个构造方法,那么java系统就不会默认用这个构造方法(这个方法是:如果某个类的构造方法)去调用父类的无参构造方法。
(4)如果父类中定义了有参构造方法,则java系统不再提供默认的无参构造方法,因此在子类的构造方法中一定要提供super语句来显式地调用父类的无参构造方法。
(5) 如果父类没有无参构造方法,而子类也没有显示地调用父类的构造方法,编译出错。因为要默认 调用无参,但父类没有。
如同基本数据类型之间的类型转换,对象在一定范围内也可以进行类型转化。由于子类 拥有父类的方法和属性,因此,java中子类可以向上转换为父类对象(也称为向上转换类型),允许将子类实例赋值给父类的引用,也允许一个父类的引用指向子类对象。
假设 SubClass(子)是SuperClass(父) 的子类,下面的语句是合法的:
SuperClass superClass = new SubClass();
// 父类引用指向子类对象
但是反过来,一个父类对象的类型 未必可以转换为子类对象,因为子类具有的信息,父类未必包含,这种转化是不安全的。只有当父类引用实际上指向一个子类对象时,才可以进行转换。
下面是错误的:
SubClass subClass = new SuperClass();
【例5-16】对象类型转换示例
Ec5_16_Convert .java
package com.ch5;
/**
* 对象类型转换示例
* @author Administrator
*
*/
public class Ec5_16_Convert {
public static void main(String[] args) {
C c = new D(); // 父类引用(c)指向子类对象 创建一个子类对象,再转换为父类
System.out.println("c.n = " + c.n);// n = 0;n的值是父类的值,而不是子类n的值
c.n = 3.1415926; // 修改的是父类引用的被隐藏的变量,子类中n 的值12 没有改变
System.out.println("c.n = " + c.n);
// c.w = 300; // A行 父类引用不能操作子类 新增的 成员变量
// c.cry(); // B行 父类引用不能操作子类 新增的 成员方法
c.m = 186;
c.f();
c.g();
// C行,c 是一个子类对象,因此实际调用的是子类的g()方法,输出的n和m的值,是子类中的值,因为g()方法是继承的,不是新增的
System.out.println();
System.out.println();
D d = (D) c; // 强制将父类对象c 转换为 子类对象 用d 保存这个对象
d.n = 55;
d.f();
d.g();
d.cry();
}
}
class C{
int m = 1;
double n;
void f(){
System.out.println("被子类继承的方法 f()");
}
void g(){
System.out.println("你好,n = " + n + " m = " + m);
}
}
class D extends C{
int n = 12; // 变量隐藏
int w;
void g(){ // 方法覆盖
System.out.println("父类中现在n = " + super.n);
System.out.println("父类中现在m = " + super.m);
System.out.println("super开始");
super.g();// 调用父类的g() 和上面2行功能相同
System.out.println("super结束");
System.out.println("子类重写方法g()n, = " + n + " m = " + m);
}
void cry(){
System.out.println("子类新增的方法 void cry()");
}
}
例5-16中,如果注释掉A,B两行,将会出现语法错误。C行调用的是子类重写的方法。
结论:
(1)上转型 对象 不能操作子类 新增的 成员变量和成员方法。
(2)上转型 对象 可以代替子类对象调用子类重写的实例方法。
(3)上转型 对象 可以调用子类继承的成员变量 和 隐藏的成员变量。
对象转换不仅只发生在对象赋值的情况下,也会发生在方法调用的参数传递的情况下。如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,也可以使用子类作为手机参数。
【例5-17】
Ex5_17_Convert .java
package com.ch5;
public class Ex5_17_Convert {
public static void main(String[] args) {
// TODO Auto-generated method stub
TaxRate taxRate = new TaxRate();
Manager2 manager = new Manager2();
taxRate.findTaxRate(manager); // 参数传递时,对象类型转换
}
}
class TaxRate{
void findTaxRate(Employee2 e){
System.out.println("这是一条输出语句");
}
}
class Employee2{
public Employee2(){
System.out.println("public Employee2()父类构造构造方法执行了");
}
}
class Manager2 extends Employee2{
public Manager2(){
System.out.println("public Manager2() 子类构造方法执行了");
}
}