JavaEE鸿蒙应用开发HTML&JS+前端Python+大数据开发人工智能开发电商视觉设计软件测试新媒体+短视频直播运营产品经理集成电路应用开发(含嵌入式)Linux云计算+运维开发C/C++拍摄剪辑+短视频制作PMP项目管理认证电商运营Go语言与区块链大数据PHP工程师Android+物联网iOS.NET

【Java教程】代码的“认爹”之路, 面向对象-继承

来源:黑马程序员

浏览1646人

2022.08.24

前言 :

Hello , 各位同学朋友大家好,今天给大家分享的技术,是面向对象三大特征之一的继承。我们今天主要按照以下几个点,展开继承的讲解:

目录 :

1、继承的介绍

2、继承的好处和弊端

3、继承中成员访问特点 - 成员变量

4、继承中成员访问特点 - 成员方法

· 方法重写

5、继承中成员访问特点 - 构造方法

6、this 和 super 关键字

1、继承的介绍

提到了继承,大家想到了什么?是的,子女继承到的家产,那家产是存在着一种关系的,父亲和儿子的关系。

对比生活中的继承,Java 中的继承,其实就是让类与类之间产生关系,什么关系?子父类关系,当这种关系建立起来之后,子类就可以直接使用父类中,非私有的成员了。

聊到这个地方,很多同学就在想,我为什么让我的类与类之间建立这种关系呢?请同学们先阅读下方代码

假设,我要编写一款 xxx 公司的人员管理系统,这款管理系统需要对项目经理和程序员的信息做管理,所以就需要编写如下两个类:

现在大家可以发现,这两个类中的东西,完全是长一样的!!!那我将来要是再多一个 Hr 的类,对人事的信息做管理,这些重复的代码,岂不是要再写一遍?麻烦!

所以,这时候我要用继承来优化代码了。

我将这些类当中, [共性] 或者说是 [相同] 的内容, 抽取到一个父类 (Employee 员工 ) 当中 , 再让 Coder 和 Manager 继承 Employee, 代码就不需要重复编写了 , 这就能够提高代码的复用性了 ~

但是, 代码层面, 我给如何让 Coder 和 Manager 继承 Employee 呢 ? 请同学们继续看继承的格式

继承的格式 :

可以看到, 实现继承, 我们是通过 extends 关键字进行编写的 , 下面我们编写下代码

示例代码 :

class Employee {
    String name;
    int age;
    double salary;
}

class Coder extends Employee {

}

class Manager extends Employee {

}

public class Test {
    public static void main(String[] args) {
        Coder c = new Coder();
        c.name = "张三";
        c.age = 23;
        c.salary = 12000;

        System.out.println(c.name + "---" + c.age + "---" + c.salary);

        Manager m = new Manager();
        m.name = "李四";
        m.age = 24;
        m.salary = 18000;

        System.out.println(m.name + "---" + m.age + "---" + m.salary);
    }
}

通过上面的代码, 同学们可以发现, Coder 和 Manager 中, 明明什么都没写, 但是却能够访问到父类 Employee 中的属性了

所以, 重复的代码就不需要重复编写, 代码的复用性就提高了 ~

 

但是, 我相信有些同学可能会想

Q : 按照标准 JavaBean 来说, 成员变量不应该私有化吗 ? 那如果父类中的成员私有了, 子类岂不是用不到这些数据了.

A : 能想到这个问题非常好, 说明之前的知识掌握的很扎实, 但是继续想, 我们私有成员变量之后, 会提供对应的 set 和 get 方法吧, 这些set get 可都是公共的, 子类是可以继承到直接用的

 

示例代码 :

package com.itheima.myextends;


class Employee {
    private String name;
    private int age;
    private double salary;


    public Employee() {
    }

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

}

class Coder extends Employee {

}

class Manager extends Employee {

}

public class Test {
    public static void main(String[] args) {
        Coder c = new Coder();
        c.setName("张三");
        c.setAge(23);
        c.setSalary(12000);
        
        System.out.println(c.getName() + "---" + c.getAge() + "---" + c.getSalary());

        Manager m = new Manager();
        m.setName("李四");
        m.setAge(24);
        m.setSalary(18000);

        System.out.println(m.getName() + "---" + m.getAge() + "---" + m.getSalary());
    }
}

2、继承的好处和弊端

好处 :

通过上面的代码, 同学们感受到继承的什么好处了啊 ? 是的, 非常明显的发现, 代码的复用性提高了.

除此之外呢, 继承还可以提高代码的维护性 , 这什么意思 ?

假设, 我要在这款管理系统中, 加入一个 id 的属性, 那使用继承前, 我就需要在每一个类中, 挨个编写, 但是现在有了继承后, 我只需要在父类中编写一个 id, 所有的子类, 是不是就都具备了这个属性了 ? 是的 ! 这就提高了代码的维护性了 !

弊端 :

继续刚刚的思路, 同学们可能会想, 增加一个, 所有子类都有了, 那删除一个, 岂不是所有子类都没有了啊...

这不就牵一发而动全身了嘛... 是的, 这就是继承的弊端, 类的耦合性太强了.

何时使用继承 :

聊到现在, 大家发现继承有好处, 也有弊端, 那该如何设计, 才能让利大于弊呢 ?

正确示例 :

错误示例 :

3、继承中成员变量访问特点

思考:子父类中,如果出现了重名的成员变量,使用的时候会优先使用??

运行效果 :

20

原因 :

一定要使用父类的, 可以使用 super 关键字进行区分

这里同学们可以先建立起一个使用思路

this. 调用本类成员

super.调用父类成员

public class Zi extends Fu {
	int num = 20;
    
    public void method(){
    	System.out.println(super.num);
    }
}

4、继承中成员方法访问特点

思考:子类继承了父类之后,是否可以自己继续定义方法?

回答:当然可以, 子类继承父类, 是可以在继承到父类成员之后, 继续加东西的

问题 : 那子父类中, 要是出现了重名的成员, 逻辑却不一样呢 ?

public class Fu {
	public void method() {
       // 父类的方法
       System.out.println("父类method...");
    }
}

class Zi extends Fu {
    public void method() {
        System.out.println("子类method...");
    }
}

运行结果 :

子类method...

结论 :

子父类中如果出现了重名的成员方法,优先使用子类的方法 , 但这其实是子类方法对父类方法进行了重写

方法重写 Override :

先跟大家聊聊什么是方法重写,在继承体系中,子类可以继承到父类的方法, 但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改, 这就需要采用方法的重写,方法重写又称方法覆盖

class Fu {
	public void catch(){
		System.out.println("使用弓箭捉羊...");
	}
}

class Zi extends Fu {
	@Override 
	public void catch(){
		System.out.println("使用98Kar狙击枪捉羊...");
	}
}

注意事项 :

解释 :

父类中私有的方法, 子类访问不到, 就更不能重写了

子类重写父类方法, 可以理解为是对功能进行增强, 那如果允许把访问权限降低, 那岂不是功能越做越差了嘛

Java 中继承的特点 :

了解了 Java 中方法重写后, 我们再来说下 Java 中继承的特点

特点 : Java只支持单继承,不支持多继承,但支持多层继承

其实就是说, 一个类, 最多只能有一个父类, 不能同时有多个父类

class A {}

class B {}

class C extends A , B {}   // 编译出错

为什么 ? 因为担心逻辑冲突

问题 : 那多层继承呢 ?

5、继承中构造方法的访问特点

思考问题 : 父类的构造方法, 子类能不能继承到呢 ?

回答 : 不允许, 因为构造方法要求, 方法名和类名必须相同, 如果子类继承到父类的构造方法, 那方法名和类名就不一致了

结论 : 子类不能继承父类的构造方法, 将来子类的构造方法, 需要自己编写

public Fu {
	public Fu(){}
}

class Zi extends Fu {
	public Fu(){}   // 如果子类继承到父类的构造方法,  那方法名和类名就不一致了
}

继承中构造方法的访问流程

阅读代码材料 :

public class Test {
    public static void main(String[] args) {
        Zi z1 = new Zi();
        Zi z2 = new Zi(10);
    }
}

class Fu {
    public Fu() {
        System.out.println("Fu类空参构造方法");
    }

    public Fu(int num) {
        System.out.println("Fu类带参数构造方法");
    }
}

class Zi extends Fu {

    public Zi() {
        System.out.println("Zi类空参数构造方法");
    }

    public Zi(int num) {
        System.out.println("Zi类带参数构造方法");
    }

}

运行效果 :

Fu类空参构造方法
Zi类空参数构造方法
Fu类空参构造方法
Zi类带参数构造方法

通过打印效果同学们可以看出, 我们无论通过子类的哪一个构造方法创建对象, 都会执行到 Fu类的空参构造方法

为什么 ? 因为在子类的构造方法, 第一行代码, 系统会默认帮我们加入 super ();

通过super() 在访问父类的构造方法 , 现在我们手动把 super(); 加上, 同学们再阅读下试试吧~

public class Test {
    public static void main(String[] args) {
        Zi z1 = new Zi();
        Zi z2 = new Zi(10);
    }
}

class Fu {
    public Fu() {
        System.out.println("Fu类空参构造方法");
    }

    public Fu(int num) {
        System.out.println("Fu类带参数构造方法");
    }
}

class Zi extends Fu {

    public Zi() {
    	super();
        System.out.println("Zi类空参数构造方法");
    }

    public Zi(int num) {
    	super();
        System.out.println("Zi类带参数构造方法");
    }

}

 

okk, 看懂了执行流程之后, 新的问题来了, 我们知道学习这个细节, 对代码有什么帮助呢 ?

答案是 : 子类将来可以把一部分数据, 交给父类初始化了

我们来看一段代码

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
public class Student extends Person {
    private double score;

    public Student() {
    }

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    public void study() {
        System.out.println(
                "姓名为" + super.getName()
                + ", 年龄为" + super.getAge()
                + ", 成绩为" + score + "分的学生, 正在学习Java");
    }
}
public class ExtendsTest2 {
    public static void main(String[] args) {
        Student stu = new Student("李四", 24, 100);
        stu.study();
    }
}

 

在上述代码中, 我们可以发现, main方法中 , 创建学生对象, 我们给了 3个参数, 先执行子类的带参数构造方法

其中前两个数据, 我们通过super, 交给了父类初始化, 最后一个成绩数据, 父类没有, 子类自己完成了初始化

public Student(String name, int age, double score) {	// "李四", 24, 100
        super(name, age);	// "李四", 24 交给父类
        this.score = score; // 100 子类自己初始化
}

配合一张内存图 , 大家再看一下

这里我们可以发现, 堆内存的对象中, 会存在一个 super 区域, 专门存放父类的成员

所以, 前两个数据, 父类中有, 就交给父类初始化, 最后一个数据, 父类没有, 子类就自己完成初始化.

6、this 和 super

上述文章中, 我们其实关于 this 和 super 做了很多使用了, 接下来我们来梳理下

this:代表本类对象的引用

super:代表父类存储空间的标识(可以理解为父类对象引用)

好啦, 本次关于继承的技术, 就跟大家聊到这里了

我们下次再见 ~

掰掰~ (^▽^)