• day14_面向对象的三大特征之继承


    继承的由来

    多个类中存在相同属性和行为时,我们每个类都编写一遍会造成代码的冗余。为了解决这种问题,继承的就应运而生了。

    继承的概述

    多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:

    其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类超类(superclass)或者基类继承描述的是事物之间的所属关系,这种关系是:is-a 的关系例如,图中猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。继承后,“子类”中就“拥有”了“父类”中所有的成员(成员变量、成员方法)。 子类就不需要再定义了从父类中继承过来的成员了。

    继承的好处

    • 提高代码的复用性
    • 提高代码的扩展性
    • 类与类之间产生了关系,是学习多态的前提

    继承的格式

    通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

    继承演示,代码如下:
    package demo01;
    //父类
    class Person {
        //父类中的成员
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
        //父类中的方法
        public void eat() {
            System.out.println("我是干饭人");
        }
    }
    
    /*
     * 定义学生类 Student 继承 人类 Person
     */
    class Student extends Person {
    
    }
    
    public class Test {
        public static void main(String[] args) {
            //创建学生类对象
            Student student = new Student();
            //给学生类的name赋值
            student.setName("张三");
            //输出学生类的name属性
            System.out.println(student.getName()); //张三
            //调用从父类中继承下来的eat()方法
            student.eat(); //我是干饭人
    
        }
    }

    继承后构造方法的访问规则

    当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?

    首先我们要回忆两个事情,构造方法的定义格式和作用。

    1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
    2. 构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。代码如下:
    package demo02;
    
    class Fu {
        String name;
        //父类有参构造
        public Fu(String name) {
            this.name = name;
        }
        //父类无参构造
        public Fu() {
            System.out.println("父类空参数");
        }
    }
    
    class Zi extends Fu {
        // 子类无参构造
        public Zi() {
            System.out.println("子类空参数");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            // Zi zi = new Zi("张三"); 编译报错,因为没有继承
            
            //调用子类无参数构造,创建对象
            Zi zi = new Zi();
        }
    }

    执行结果


    如果父类没有无参构造怎么办? 解决办法:在子类构造器中,用super(实参列表),显示调用父类的有参构造解决。

    public class Student extends Person{
        private int score;
    
        public Student(String name, int age) {
            super(name, age);
        }
        public Student(String name, int age, int score) {
            super(name, age);
            this.score = score;
        }
        
        //其他成员方法省略
    }

    结论:

    子类对象实例化过程中必须先完成从父类继承的成员变量的实例初始化,这个过程是通过调用父类的实例初始化方法来完成的。

    • super( ):表示调用父类的无参实例初始化方法,要求父类必须有无参构造,而且可以省略不写;
    • super(实参列表):表示调用父类的有参实例初始化方法,当父类没有无参构造时,子类的构造器首行必须写super(实参列表)来明确调用父类的哪个有参构造(其实是调用该构造器对应的实例初始方法)
    • super( )和super(实参列表)都只能出现在子类构造器的首行
    • 实际开发中,强烈建议自己定义有参数和无参构造。

    继承后成员的访问规则

    访问私有成员变量的规则

    • 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
    • 子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。

    如图所示

    代码示例

    /*
     * 定义动物类Animal,做为父类
     */
    class Animal {
        // 定义父类私有name属性
        private String name;
    
        // 定义父类私有的吃东西方法
        private void eat() {
            System.out.println(name + "在吃东西");
        }
    
        public void show() {
            eat();
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    /*
     * 定义猫类Cat 继承 动物类Animal
     */
    class Cat extends Animal {
    
    }
    
    /*
     * 定义测试类
     */
    public class Test {
        public static void main(String[] args) {
            // 创建一个猫类对象
            Cat cat = new Cat();
            
            // cat.name ="糖猫"; 编译报错,不能直接访问父类中私有的属性
    
            cat.setName("糖猫");//间接访问
            System.out.println(cat.getName());//糖猫
            
            //cat.eat();编译报错,不能直接访问父类中私有方法
            
            //间接访问
            cat.show();//糖猫在吃东西
        }
    }

    当父类的成员变量私有化时,在子类中是无法直接访问的,所以是否重名不影响,如果想要访问父类的私有成员变量,只能通过父类的get/set方法访问;

    访问非私有成员的规则

    不管非私有成员是否重名,当通过“子类”访问非私有成员时,先在子类中找,如果找到就使用子类的,找不到就继续去“父类”中找。

    package demo04;
    //父类
    class Fu {
        int money = 100;
    
        public void method() {
            System.out.println("Fu 类中的成员方法method");
        }
    }
    //子类
    class Zi extends Fu {
        int money = 1;
    
        public void method() {
            System.out.println("Zi 类中的成员方法method");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Zi z = new Zi();
            System.out.println(z.money);//1
            z.method();// Zi 类中的成员方法method
        }
    }

    方法重写

    我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)

    注意事项:

    • @Override:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留
    • 必须保证父子类之间方法的名称相同,参数列表也相同。
    • 子类方法的返回值类型必须【小于等于】父类方法的返回值类型(其实就是是它的子类,例如:Student < Person)。注意:如果返回值类型是基本数据类型和void,那么必须是相同
    • 子类方法的权限必须【大于等于】父类方法的权限修饰符。小扩展提示:public > protected > 缺省 > private
    • 几种特殊的方法不能被重写:静态方法不能被重写丶私有等在子类中不可见的方法不能被重写丶final方法不能被重写
    • 子类不能抛出比父类更大的异常

    代码示例

    //父类
    class Phone {
        public void sendMessage() {
            System.out.println("发短信");
        }
    
        public void call() {
            System.out.println("打电话");
        }
    
        public void showNum() {
            System.out.println("来电显示号码");
        }
    }
    
    //子类
    class NewPhone extends Phone {
    
        //重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
        public void showNum() {
            //调用父类已经存在的功能使用super
            super.showNum();
            //增加自己特有显示姓名和图片功能
            System.out.println("显示来电姓名");
            System.out.println("显示头像");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            // 创建子类对象
            NewPhone np = new NewPhone();
    
            // 调用父类继承而来的方法
            np.call();
            np.sendMessage();
    
            // 调用子类重写的方法
            np.showNum();
    
        }
    }

    继承体系对象的内存图

    父类空间优先于子类对象产生在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。

    代码体现:在子类的构造方法调用时,一定先调用父类的构造方法。

    查看下面代码

    class Fu{
        int num = 10;
        int numFu = 100;
        public void method(){
            System.out.println("Fu method...");
        }
    }
    class Zi extends Fu{
        int num = 20;
        int numZi = 200;
        public void method(){
            System.out.println("Zi method...");
        }
        public void show(){
            int num = 30;
            System.out.println("局部变量num:"+num);// 30
            System.out.println("本类成员变量num:"+this.num);// 20
            System.out.println("父类成员变量num:"+super.num);// 10
            // 访问本类的method方法
            this.method();// Zi method...
            // 访问父类的method方法
            super.method();// Fu method...
        }
    }
    public class Test {
        public static void main(String[] args) {
            Zi zi = new Zi();
            zi.show();
        }
    }

    继承注意事项

    Java只支持单继承,不支持多继承。

    //一个类只能有一个父类,不可以有多个父类。
    class C extends A{}     //ok
    class C extends A,B...    //error

    Java支持多层继承(继承体系)。java中所有类都是直接或者间接继承Object,所有类都是Object类的子类

    class A{}
    class B extends A{}
    class C extends B{}

    子类和父类是一种相对的概念。 例如:B类对于A来说是子类,但是对于C类来说是父类。一个父类可以同时拥有多个子类

  • 相关阅读:
    同余方程
    倒酒
    机器翻译
    vue 锚点定位
    解决vuex刷新页面数据丢失
    h5 input失去焦点软键盘把页面顶起
    js 监听ios手机键盘弹起和收起的事件
    js 将数组中的每一项安装奇偶重新组合成一个数组对象
    moment.js获取本周本月本年的开始日期和结束日期
    vue 所有的路由跳转加一个统一参数
  • 原文地址:https://www.cnblogs.com/wurengen/p/16423451.html
Copyright © 2020-2023  润新知