ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

继承和多态

2021-04-13 16:04:12  阅读:120  来源: 互联网

标签:调用 构造方法 继承 子类 多态 父类 方法 public


  面向对象编程允许从已经存在的类中定义新类,这称为继承。继承是面向对象编程的一个重要特征。假设在QQ宠物游戏中要定义一个类,对狗狗,企鹅还有猪猪建模。这些类有很多共同的特性。我们可以使用继承来编码冗余并使系统更易于理解和易于维护。

父类和子类

  使用类来对同一类型的对象建模。不同的类也可能会有一些共同的特征和行为,这些共同的特征和行为都统一放在一个类中,它是可以被其他类所共享的。可以定义特定的类继承自通用类。这些特定的类继承通用类中的特征和方法。考虑一下QQ宠物对象。假设要设计类建模像Dog和Penguin这样的宠物对象。宠物对象有许多共同的行为和属性。这样一个通用类Pet可以用来建模所有的宠物对象。

package edu.uestc.avatar.inherit.pet;

/**
 * 宠物这一类事务共同的属性和行为抽取在一个通用的公共类-----Pet
 * @author Tiger
 *
 */
public class Pet {
    //共同的特征---名字
    private String name;
    //共同的特征---健康值
    private int health;
    //共同的特征---爱心值
    private int love;
    
    public Pet(String name, int health, int love) {
        System.out.println("pet---构造方法");
        this.name = name;
        this.health = health;
        this.love = love;
    }

    /**
     * 自白
     */
    public void sayHello() {
        System.out.print("我的名字叫" + this.name + ",我的健康值是" + health + ",我和主人的亲密度是" + love);
    }
    
    /**
     * 吃东西
     */
    public void eat() {
        if(this.health <= 95) {
            this.health += 3;
            this.love -= 5;
        }
    }
    
    /**
     * 陪主人玩
     */
    public void play() {
        if(this.health > 60) {
            this.health -= 5;
            this.love += 3;
        }
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getHealth() {
        return health;
    }
    public void setHealth(int health) {
        this.health = health;
    }
    public int getLove() {
        return love;
    }
    public void setLove(int love) {
        this.love = love;
    }
}

  Dog类继承了Pet类所有可以访问的数据域和方法。除此之外,它还有个新的数据域strain以及与strain相关的setter和getter方法。

package edu.uestc.avatar.inherit.pet;

/**
 * Dog扩展自通用的宠物类Pet(继承)
 *
 */
public class Dog extends Pet{
    private String strain;
    //在子类中通过super()调用父类的构造方法
    public Dog(String name,int health,int love,String strain) {
        super(name,health,love);//调用父类的构造方法必须在第一句
        this.strain = strain;
        System.out.println("dog---构造方法");
    }
    /**
     * super关键字
     *         1、子类调用父类中的方法
     *         2、在子类构造方法中调用父类的构造方法
     */
    public void sayHello() {
        super.sayHello();//子类调用父类中的方法
        System.out.println(",我的类型是" + strain);
    }
    
    public String getStrain() {
        return strain;
    }

    public void setStrain(String strain) {
        this.strain = strain;
    }
    
}

  Penguin类继承了Pet类所有可以访问的数据域和方法。除此之外,它还有个新的数据域gender以及与gender相关的setter和getter方法

package edu.uestc.avatar.inherit.pet;

/**
 * 子类是父类的特殊化,每个子类的实例都是父类的实例。
 *
 */
public class Penguin extends Pet{
    private String gender;
    
    public Penguin(String name,String gender) {
        super(name,100,100);
        this.gender = gender;
        System.out.println("penguin---构造方法");
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
    
    /**
     * 为满足子类的需要,子类需要覆盖父类中定义的方法实现----------方法重写
     * 1、访问修饰符:子类重写方法访问权限不能小于父类的访问修饰权限
     * 2、返回值类型:一般具有相同的返回值类型,子类重写的方法返回值类型不能大于父类的返回值类型
     * 3、异常类型:子类重写的方法不能抛出比父类更大类型的异常
     * 4、仅当实例方法可访问时,才能进行方法重写
     * 5、静态方法也能被继承,静态方法不能被覆盖。
     * @Override 编译器可帮我们检查是否是符合方法重写要求的
     */
    @Override
    public void sayHello() {
        System.out.println("我的名字叫" + this.getName() + ",我的健康值是" + getHealth() + ",我和主人的亲密度是" + getLove() + ",我的性别是:" + gender);
    }
//企鹅所特有的方法:撒娇
   public void 撒娇(){

} }

  在java术语中,如果Dog类继承自Pet类,那么就将Dog类称为次类,将Pet称为超类。超类也称为父类或基类;次类也称为子类,扩展类或者派生类

  关于继承应该注意的几个关键点

  • 和传统的理解不同,子类并不是父类的一个子集。实际上,一个子类比它的父类包含更多的信息和方法。
  • 父类中的私有属性在该类之外是不可访问的。因此,不能在子类中直接使用。但是,如果父类中定义了公共的setter/getter,那么可以通过这些公共的setter/getter来修改和访问它们
  • 继承是用来为is-a关系建模的。不要仅仅为了重用方法而盲目扩展一个类。例如:尽管Person类和Tree类可以共享类似高度和重量这样的通用特性,但是从Person类扩展出Tree类是毫无意义的。一个父类和它的子类之间必须存在“是一种”(is-a)关系
  • 不是所有的is-a关系都该用继承来建模。例如:正方形是一种矩形,但是不应该定义一个Square类来扩展Rectangle类,因为width和height属性并不适合于正方形。
  • java是不允许多重继承的。一个java类只能直接继承自一个父类。多重继承可以通过接口来实现。

  使用super关键字

  关键字super指代父类,可以用于调用父类中的普通方法和构造方法,前面介绍了关键字this的作用,它是对调用对象的引用。关键字super是指这个super关键字所在的类的父类。关键字super可以用于两种途径:

1)调用父类构造方法

  构造方法用于构建一个类的实例。不同于属性和普通方法,父类的构造方法不会被子类继承。它们只能使用super从子类的构造方法中调用。语法:super或者super(parameters)

public  Penguin(String name,String gender) {
    //显式的调用父类的构造方法
    super(name);//必须是构造方法的第一题语句
    this.gender = gender;
    System.out.println("我是Penguin的构造方法");
}

 构造方法链

  在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个子类对象时,子类构造方法会在完成自己任务之前,首先调用它的父类构造方法。如果父类继承自其他类,那么父类构造方法又会在完成自己任务前调用它自己的父类的构造方法。这个过程持续到沿着这个继承体系结构的最后一个构造方法被调用为止。如果没有被显式的调用,编译器将会自动添加super()作为构造方法的第一条语句。

2)调用父类方法

方法重写

  出现的前提:有子类继承父类

       出现的场景:当子类继承了父类的方法后发现父类的方法不适用于子类,则子类可以通过重写父类的方法。

       在调用时,调用的是子类重写后的方法,而不是来自于父类的方法。

       重写的规则:

              1)要求子类与父类的方法:返回值类型、方法名、形参列表必须完全相同。

              2)子类的修饰符权限不能小于父类的修饰符权限

              3)若父类方法抛异常,那么子类方法抛的异常类型不能大于父类方法抛的异常类型

              4)子类可以重写父类的静态方法,但是必须是同为静态的(对父类静态方法来说是隐藏,可以使用父类.静态方法的形式访问)

Object类及其toString()方法

  java中的所有类都继承自java.lang.Object类。toString()方法:

   1)当我们打印一个引用数据类型的对象时,默认调用的是这个对象的toString();//Loan@15037e5

        2)当我们重写了toString()后再打印该对象,会调用重写后的toString();

        3)像String、File、Date等类已经重写了toString方法。

多态

  多态意味着父类的变量可以指向子类对象。理解成一种事物的多种表现形态.

  首先,定义两个有用的术语:子类型和父类型。一个类实际上定义了一种类型。子类定义的类型称为子类型(subtype),父类定义的类型称为父类型(supertype)。因此,可以说Dog时Pet的子类型,而Pet是Dog的父类型。

  继承关系使一个子类继承父类的特征,并且附加一些新特征。子类是它的父类的特殊化,每个子类的实例都是其父类的实例,但是反过来就不成立。例如:每个狗狗都是一个宠物对象,但并非每个宠物对象都是狗狗。因此,总可以将子类的实例传给需要父类型的参数。 

/*
 *  多态
 *  动态绑定:编译看左边(父类型),运行看右边
 *  Pet[]:声明类型
 *  dog,penguin:实际类型
 */
Pet[] pets = {new Dog("毛毛",100,100,"斑点狗"),new Penguin("pedro", "male"),new Dog("天天",100,100,"二哈")};
   for(Pet pet : pets)
      pet.sayHello();
}

动态绑定

  在程序运行过程中,根据具体的实例对象才能具体确定是哪个方法。

  动态绑定是多态性得以实现的重要因素,它通过方法表来实现:每个类被加载到虚拟机时,在方法区保存元数据,其中,包括一个叫做方法表(methodtable)的东西,表中记录了这个类定义的方法的指针,每个表项指向一个具体的方法代码。如果这个类重写了父类中的某个方法,则对应表项指向新的代码实现处。从父类继承来的方法位于子类定义的方法的前面。

  声明类型和实际类型 

//一个变量必须被声明为某种类型 Pet pet声明类型
Pet pet = new Dog("毛毛",100,100,"斑点狗");//new Dog("毛毛",100,100,"斑点狗");实际类型

  pet调用哪个play()方法由pet的实际类型决定。这称为动态绑定。

  我们知道,向上转型时,用父类引用执行子类对象,并可以用父类引用调用子类中重写了的同名方法。但是不能调用子类中新增的方法。     在代码的编译阶段,编译器通过声明对象的类型(即引用本身的类型)在方法区中该类型的方法表中查找匹配的方法(最佳匹配法:参数类型最接近的被调用),如果有则编译通过。(这里是根据声明的对象类型来查找的,所以此处是查找Father类的方法表,而Father类方法表中是没有子类新增的方法的,所以不能调用。)编译阶段是确保方法的存在性,保证程序能顺利、安全运行。      pet.play()调用的是Dog中的play(),这里就是动态绑定机制的真正体现。     编译阶段在声明对象类型的方法表中查找方法,只是为了安全地通过编译(也为了检验方法是否是存在的)。而在实际运行这条语句时,在执行Pet pet = new Dog("毛毛",100,100,"斑点狗");这一句时创建了一个Dog实例对象,然后在pet.play()调用方法时,JVM会把刚才的pet对象压入操作数栈,用它来进行调用。而用实例对象进行方法调用的过程就是动态绑定:根据实例对象所属的类型去查找它的方法表,找到匹配的方法进行调用。我们知道,子类中如果重写了父类的方法,则方法表中同名表项会指向子类的方法代码;若无重写,则按照父类中的方法表顺序保存在子类方法表中。故此:动态绑定根据对象的类型的方法表查找方法是一定会匹配(因为编译时在父类方法表中以及查找并匹配成功了,说明方法是存在的。这也解释了为何向上转型时父类引用不能调用子类新增的方法:在父类方法表中必须先对这个方法的存在性进行检验,如果在运行时才检验就容易出危险——可能子类中也没有这个方法)。 

  动态绑定和静态绑定

  程序在JVM运行过程中,会把类的类型信息、static属性和方法、final常量等元数据加载到方法区,这些在类被加载时就已经知道,不需对象的创建就能访问的,就是静态绑定的内容;需要等对象创建出来,使用时根据堆中的实例对象的类型才进行取用的就是动态绑定的内容。 

  编译器在每次调用方法时都要进行搜索,时间开销相当大。因此虚拟机会预先为每个类创建一个方发表(method table),其中列出了所有方法的签名和实际调用的方法。  

对象转换和instanceof运算符

  将一个类型强制转换为另外一个类型的过程称为类型转换,语法格式为:

Pet pet = new Penguin();
Penguin penguin = (Penguin)penguin 

  在java中,每个对象变量都属于一个类型。将一个值存入变量时,编译器将检查是否允许该操作。将一个子类的引用赋给超类类型(向上转型),编译器是允许的。但将一个超类的引用赋给一个子类变量(向下转型),必须进行类型转换。

  如果视图在继承链上进行向下的类型转换,并且谎报有关对象包含的内容,会发生什么情况呢?

Pet pet = new Penguin();
Dog dog = (Dog)pet;

  java运行时系统将报告这个错误,产生一个ClassCastException异常。如果没有捕获这个异常,程序就会终止。因此,应该养成一个良好的程序设计习惯,在进行类型转换之前,先查看一下是否能够成功地转换。使用instanceof运算符就可以实现

if(pet instanceof Penguin){
   Penguin penguin = (Penguin)pet;
   ...
}

//jdk 14的模式匹配写法
if(pet instanceof Penguin penguin){
  ...
}

  实际上,通过类型转换调整对象的类型并不是一个好的做法。在列举的示例中,大多数情况并不需要将Pet对象转成Penguin对象。两个类的对象都能够调用play方法,这是因为实现多态性的动态绑定机制能够自动的找到相应的方法。只有在使用Penguin中特有的方法时才需要进行类型转换。

Object类的equals方法

   1)equals不适用于基本数据类型,equals只适用于引用数据类型

        2)如果不重写equals方法,则比较的是两个对象的地址值,等效于“==”.因此,应该根据需要在自己的客户类中重写equals方法。

        3)像String、File、Date等类重写了Object的equals方法,比较的是两个String对象的具体的值,而不是地址值。

protected数据和方法

  1、基类的protected成员是包内可见的,并且对子类可见;

  2、若子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的protected方法,而不能访问基类实例的protected方法

防止扩展和重写

  final关键字:最终的,可以用来修饰类、属性、方法

  1.final修饰类:这个类不能被继承

  2.final修饰方法:这个方法不能被重写,当一个方法所要体现的功能已经被确定,则用final修饰。

  3.final修饰属性:表示常量,则该常量必须有初始化值。

标签:调用,构造方法,继承,子类,多态,父类,方法,public
来源: https://www.cnblogs.com/adan-chiu/p/12887502.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有