5.3面向对象特性------继承

5.3.1 类的继承

子类在保留父类基本属性与行为的基础上,可以增加新的属性或行为,或者修改属性和行为。
子类继承父类,父类派生子类,子类还可以派生子类,这样就形成了类的层次结构。
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

5.3.2 super 的使用

子类在继承父类时,可能会出现变量隐藏方法覆盖(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)用来调用父类的构造方法。
 */

5.3.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) 如果父类没有无参构造方法,而子类也没有显示地调用父类的构造方法,编译出错。因为要默认 调用无参,但父类没有。

5.3.4 对象类型转换

如同基本数据类型之间的类型转换,对象在一定范围内也可以进行类型转化。由于子类 拥有父类的方法和属性,因此,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() 子类构造方法执行了");
    }

}

这里写图片描述
本篇结束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值