Java面向对象编程-第6章学习笔记

来源:互联网 发布:人工智能医疗龙头股 编辑:程序博客网 时间:2024/06/05 17:20

第6章 继承

继承是复用程序代码的有力手段,当多个类之间存在相同的属性和方法时,可从这些类中抽象出父类,在父类中定义这些相同的属性和方法,而子类只需要通过extends语句来声明继承父类就无须重新定义这些属性和方法。
一、继承的基本语法

public class Sub extends Base{   ...}

上述表示Sub类继承了Base类,即Base是父类,Sub是其子类。(1)当父类子类在同一个包的时候,Sub类继承Base类中public、protected和默认访问级别的成员变量和成员方法。(2)当父类子类不在同一个包的时候,Sub类继承Base类中public、protected访问级别的成员变量和成员方法。
如下例:

public class Base{  public int publicVarOfBase=1;  private int privateVarOfBase=1;  int defaultVarOfBase=1;  protected void methodOfBase(){}}public class Sub extends Base{  public void methodOfSub(){    publicVarOfBase=2;    //合法    defaultVarOfBase=1;   //合法    privateVarOfBase=1;   //非法    methodOfBase();       //合法public static void main(String[] args){  Sub sub=new Sub();  sub.publicVarOfBase=3;    //合法  sub.privateVarOfBase=3;    //非法  sub.defaultVarOfBase=3;    //合法  sub.methodOfBase();       //合法  sub.methodOfSub();       //合法}}

二、方法重载(Overload)
对于类的方法(包括从父类中继承的方法),如果有多个方法重名,但参数不同,则可以说某一个方法是另一个方法的重载方法,重载方法必须满足以下条件:
(1)方法名相同;
(2)方法的参数类型、个数、顺序至少有一项不同;
(3)方法的修饰符可以不同。
*方法的重载,可以理解为当调用某一个方法时,而该方法对应的方法名有多个同名方法,JVM能否确认调用对应的方法。

三、方法覆盖(Override)
如果有100个类,这些类都有“写字”的功能,其中有99个类实现写字功能都通过“用手写字”这个方法,只有一个是通过“用脚写字”,那么可以对这100个类抽象出一个父类,直接在父类里定义“用手写字”这个方法,这100个类通过继承可以得到“用手写字”的方法,而其中用脚写字的类可以在类的定义中重写这个方法为“用脚写字”,这个就称为覆盖。
覆盖方法必须满足多种约束:
(1)子类方法的名称、参数签名和返回类型都必须和父类一致。(如果名称不同,则为不同方法,不存在覆盖的含义,父类中方法和子类中方法都可以调用;如果参数签名不一样,名称相同,则父类中的方法也可以使用,这里其实就是重载了;如果返回类型不一样,参数签名一样的话,则编译非法,因为调用的话JVM不知道选哪个方法,如果返回类型不一样,参数签名也不一样,则为重载。)
(2)子类方法不能缩小父类方法的访问权限。(多态冲突)
(3)子类方法不能抛出比父类方法更多的异常。(多态冲突)
(4)方法覆盖只存在于父类和子类之间。(同一类中方法只能被重载,不能被覆盖)
(5)父类静态方法不能被子类覆盖为非静态方法。子类可以定义与父类静态方法同名的静态方法,以便在子类中隐藏父类静态方法。

如下例:

package tsjava;class Base{    void method(){  //实例方法        System.out.println("method of Base");    }    static void staticMethod(){  //静态方法        System.out.println("static method of Base");    }}public class Test extends Base{    void method(){       //覆盖父类的实例方法        System.out.println("method of Test");    }    static void staticMethod(){  //隐藏父类的静态方法        System.out.println("static method of Test");    }    public static void main(String[] args){        Base sub1=new Test();     //创建Base的实例        sub1.method();        sub1.staticMethod();      //父类静态方法不能被覆盖        Test sub2=new Test();     //创建Test的实例        sub2.method();        sub2.staticMethod();      //隐藏了父类的静态方法    }}

(6)父类的非静态方法不能被子类覆盖为静态方法。
(7)父类的私有方法不能被子类覆盖。(首先覆盖的前提是子类要先继承了父类的方法,然后再通过覆盖来隐藏这个继承于父类的方法,当父类方法为私有方法时候子类无法继承该方法,也就不存在覆盖的概念)

package tsjava;class Base{    private String showMe(){        return "Base!";    }    public void print(){        System.out.println(showMe());    }}public class Sub extends Base{    public String showMe(){        return "Sub!";    }    public static void main(String[] args){        Sub sub=new Sub();        sub.print();   //虽然sub为Sub类型,但这里print()方法继承于Base类,而print()方法调用的showMe()为父类中的私有方法        System.out.println(sub.showMe());  //这里的showMe()为子类自定义的方法    }    /*本例中的两个showMe()方法可以通过编译,但不存在覆盖关系,实质上就是因为子类并没有继承到父类的showMe()。*/}

四、方法覆盖和重载的异同
方法的覆盖,顾名思义,即对同名方法而言,用一种定义覆盖之前的定义,之前定义的方法被隐藏起来了,要完全覆盖就必须在返回类型、参数签名、方法名均保持一致,覆盖只存在于子类和父类(直接或简接)之间。
方法的重载是指对某一个名称的方法,有多种不同的定义,这里定义的不同可以包括返回类型的不同(但返回类型也可以相同),但参数签名必须不同。设想,当一个代码中有多个同名方法,方法名均为pHit,那么在调用在pHit的时候,JVM只有通过调用时的参数签名去确定该对应哪一个方法,如果在同名方法的不同定义中出现同样的参数签名,JVM则无法确认。方法的重载存在于同一类,或子类和父类之间。
1、相同点:
(1)都要求方法同名;
(2)都可以用于抽象方法和非抽象方法。
2、不同点:
(1)覆盖要求参数签名必须一致,重载要求参数签名必须不一致。
(2)覆盖要求返回类型必须一致,重载对返回类型不作要求。
(3)覆盖只能存在于父子类之间,方法可以在同一个类之间。
(4)方法覆盖对访问权限和抛出异常有要求,方法重载对此没有要求。
(5)父类的一个方法只能被子类覆盖一次,而一个方法可以被重载多次。
*其实,覆盖可以理解为替代,而重载是定义多个虽然同名但实质并不同的方法。

五、super关键字
super和this关键字都用来覆盖Java语言的默认作用域,使被屏蔽的方法或变量变为可见,一般下面几种情况会出现方法或变量被屏蔽的现象:
(1)在一个方法内,当局部变量和类的成员变量(或其父类的成员变量)同名时,只有局部变量在该方法内可见。
(2)当子类某个方法覆盖了父类的一个方法,在该子类范围内,这个同名父类方法不可见。
(3)当子类定义了和父类同名的成员变量,在子类范围内,父类成员变量不可见。

package tsjava;class Base{    String var="Base's Variable";           //父类成员变量    void method(){        System.out.println("call Base's method.");    }}public class Sub extends Base{    String var="Sub's Variable";             //子类成员变量    void method(){        System.out.println("call Sub's method.");    }    void test(){        String var="Local variable";          //局部变量        System.out.println("var is "+var);    //输出局部变量        System.out.println("this.var is "+this.var);   //输出Sub实例中的实例变量        System.out.println("super.var is "+super.var);   //输出Sub实例所属类的父类中的中的实例变量        method();          //调用Sub实例对象中的方法        this.method();     //调用Sub实例对象中的方法        super.method();    //调用Sub实例所属类的父类中的方法    }    public static void main(String[] args){        Sub sub=new Sub();        sub.test();    }}

通过上例可以看出,在一个类的成员方法中,如果该方法中类有与其同名的成员变量(或方法),则可以通过this关键字去引用类的成员变量(或方法),如果父类有与其同名的成员变量(或方法),则通过super关键字去引用。
如果父类中的成员变量和方法被定义为private类型,那么子类永远无法访问到,当然也无法通过super关键字去访问。
在静态方法和代码块内也不能使用super关键字。

六、多态
Java允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。
如Animal是Dog和Cat类的父类:

Animal dogAnimal=new Dog();    //Animal的引用变量引用子类Dog的实例对象Animal catAnimal=new Cat();    //Animal的引用变量引用子类Cat的实例对象Dog dog=(Dog)dogAnimal;    //合法,可以转换Cat cat=(Cat)catAnimal;    //合法Cat cat2=(Cat)dogAnimal;   //非法
package tsjava;class Base{    String var="BaseVar";                            //实例变量    static String staticVar="StaticBaseVar";         //静态变量    void method(){        System.out.println("Base method");    }    static void staticMethod(){        System.out.println("Static Base method");    }}public class Sub extends Base{    String var="SubVar";                            //实例变量    static String staticVar="StaticSubVar";         //静态变量    void method(){                                  //覆盖父类的method()方法        System.out.println("Sub method");    }    static void staticMethod(){        System.out.println("Static Sub method");    //隐藏父类的staticMethod()方法    }    String subVar="Var only belongs to Sub";    void subMethod(){        System.out.println("Method only belongs to Sub");    }    public static void main(String[] args){        Base who=new Sub();                 //who声明为Base类型,引用Sub实例        System.out.println("who.var="+who.var);      //打印Base类的var变量        System.out.println("who.staticVar="+who.staticVar);      //打印Base类的staticVar变量        who.method();               //打印Sub实例的method()方法        who.staticMethod();         //打印Base类的staticmethod()方法    }}

Java的绑定机制:
(1)实例方法与引用变量实际引用的对象的方法绑定,这种绑定属于动态绑定,因为在运行时由Java虚拟机动态决定的,如上述代码中的who.method()实际为调用引用变量who实际引用的Sub的实例对象的方法。
(2)静态方法与引用变量所声明的类型的方法绑定,这种绑定属于静态绑定,实际上这是在编译阶段就已经完成的工作。如上述代码中的who.staticMethod()实际为调用Base类的静态方法。
(3)成员变量(包括静态变量和实例变量)与引用变量所声明的类型的成员变量绑定,也属于静态绑定,在编译阶段就已完成。如上述中的who.var和who.staticVar。

上述代码运行结果如下:

who.var=BaseVarwho.staticVar=StaticBaseVarSub methodStatic Base method
package tsjava;class A{    void method(){        System.out.println("Base");    }    void test(){        method();    }}public class B extends A{    void method(){        System.out.println("Sub");    }    public static void main(String[] args){        new A().test();     //对应A的实例对象方法        new B().test();     //对应B的实例对象方法        //记住上述(1):实例方法是与引用变量实际引用的对象的方法绑定的,即动态绑定    }}

运行结果:

BaseSub

七、继承的利弊和使用原则
1、继承树的层次不可太多:除了顶层的Object类,继承层次尽量保持在两到三层。层次太多会使得对象模型结构过于复杂,增加开发和维护难度。
2、当开发时使用一个继承树上的类时,应尽可能把引用变量声明为继承树的上层类型,这样可以提高两个系统之间的耦合性。
3、继承关系最大的弱点:打破封装。
4、精心设计的建议:
(1)对类提供必要而良好的文档说明,使得其子类开发人员知道该如何正确安全地扩展它。
(2)尽可能封装父类的实现细节,对于必要的属性和方法定义为private类型。如果部分实现细节必须被子类访问,可以在父类中设计一些方法,把这部分方法定义为protected类型,提供给子类但又不对外公开的接口。
(3)把不允许子类覆盖的方法定义为final类型。
(4)父类的构造方法不允许调用可被子类覆盖的方法。
(5)如果某些类不是专门为继承而设计,那么随意访问是不安全的,可以:把类声明为final类型。或者把这个类的所有构造方法设置为private,然后通过一些静态方法来负责构造自身的实例。

八、关于类型转换
1、Java允许具有直接或间接继承关系的类之间进行类型转换,对于向上转换,不必使用强制类型转换,因为子类对象肯定也是父类的对象,如:
Dog dog=new Dog();
Animal animal=dog;
Object obj=dog;
对于向下转换,则必须进行强制类型转换:
Object obj=new Object();
Animal animal=(Animal)obj;
Dog dog=(Dog)animal;
2、对于没有继承关系的两个类,当进行强制类型转换时候会发生编译错误。
3、前述向下强制类型转换虽然可以通过编译,但有时候运行时会抛出异常,因为子类拥有的成员父类不一定有。如
Creature creature=new Cat();
Animal animal=(Animal)creature; //运行正常,creature实际引用的为Cat实例对象,而Cat是Animal的子类
Cat cat=(Cat)creature; //运行正常,creature实际引用的为Cat实例对象
Dog dog=(Dog)creature;//运行抛出异常

0 0