面向对象进阶一:继承

面向对象进阶一:继承

第三章 继承

3.1 概述

3.1.1 引入

假如我们要定义如下类:
学生类,老师类和工人类,分析如下。

  1. 学生类
    属性:姓名,年龄
    行为:吃饭,睡觉

  2. 老师类
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,教书

  3. 班主任
    属性:姓名,年龄,薪水
    行为:吃饭,睡觉,管理

如果我们定义了这三个类去开发一个系统,那么这三个类中就存在大量重复的信息(属性:姓名,年龄。行为:吃饭,睡觉)。这样就导致了相同代码大量重复,代码显得很臃肿和冗余,那么如何解决呢?

假如多个类中存在相同属性和行为时,我们可以将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。如图所示:

在这里插入图片描述

其中,多个类可以称为子类,单独被继承的那一个类称为父类超类(superclass)或者基类

3.1.2 继承的含义

继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

继承:就是子类继承父类的属性行为,使得子类对象可以直接具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。

3.1.3 继承的好处

  1. 提高代码的复用性(减少代码冗余,相同代码重复利用)。
  2. 使类与类之间产生了关系。

3.1.4 继承的格式

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

class 父类 {
	...
}

class 子类 extends 父类 {
	...
}

需要注意:Java是单继承的,一个类只能继承一个直接父类,跟现实世界很像,但是Java中的子类是更加强大的。

3.2 继承案例

各位同学,学习完继承的快速入门之后,接下来我们学习一下继承的好处,以及它的应用场景。

我们通过一个案例来学习

在这里插入图片描述

观察代码发现,我们会发现Teacher类中和Consultant类中有相同的代码;其实像这种两个类中有相同代码时,没必要重复写。

我们可以把重复的代码提取出来,作为父类,然后让其他类继承父类就可以了,这样可以提高代码的复用性。改造后的代码如下:

在这里插入图片描述

接下来使用继承来完成上面的案例,这里只演示People类和Teacher类,然后你尝试自己完成Consultant类。

  • 先写一个父类 People,用来设计Teacher和Consultant公有的成员。
public class People{
    private String name;
    
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name=name;
    }
}
  • 再写两个子类Teacher继承People类,同时在子类中加上自己特有的成员。
public class Teacher extends People{
    private String skill; //技能
    
    public String getSkill(){
        return skill;
    }
    
    public void setSkill(String skill){
        this.skill=skill;
    }
    
    public void printInfo(){
        System.out.println(getName()+"具备的技能:"+skill);
    }
}
  • 最后再写一个测试类,再测试类中创建Teacher、Consultant对象,并调用方法。
public class Test {
    public static void main(String[] args) {
        // 目标:搞清楚继承的好处。
        Teacher t = new Teacher();
        t.setName("播仔");
        t.setSkill("Java、Spring");
        System.out.println(t.getName());
        System.out.println(t.getSkill());
        t.printInfo();
    }
}

执行代码,打印结果如下:

在这里插入图片描述

关于继承的好处我们只需要记住:继承可以提高代码的复用性

3.3 权限修饰符

​ 在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,我们之前已经学习过了public 和 private,接下来我们研究一下protected和默认修饰符的作用。

  • public:公共的,所有地方都可以访问。

  • protected:本类 ,本包,其他包中的子类都可以访问。

  • 默认(没有修饰符):本类 ,本包可以访问。

    注意:默认是空着不写,不是default

  • private:私有的,当前类可以访问。
    public > protected > 默认 > private

publicprotected默认private
同一类中
同一包中的类
不同包的子类
不同包中的无关类

可见,public具有最大权限。private则是最小权限。

编写代码时,如果没有特殊的考虑,建议这样使用权限:

  • 成员变量使用private ,隐藏细节。
  • 构造方法使用 public ,方便创建对象。
  • 成员方法使用public ,方便调用方法。

小贴士:不加权限修饰符,就是默认权限

3.4 单继承 Object

刚才我们写的代码中,都是一个子类继承一个父类,那么有同学问到,一个子类可以继承多个父类吗?

Java语言只支持单继承,不支持多继承,但是可以多层继承。就像家族里儿子、爸爸和爷爷的关系一样:一个儿子只能有一个爸爸,不能有多个爸爸,但是爸爸也是有爸爸的。

在这里插入图片描述

public class Test {
    public static void main(String[] args) {
        // 目标:掌握继承的两个注意事项事项。
        // 1、Java是单继承的:一个类只能继承一个直接父类;
        // 2、Object类是Java中所有类的祖宗。
        A a = new A();
        B b = new B();

        ArrayList list = new ArrayList();
        list.add("java");
        System.out.println(list.toString());
    }
}

class A {} //extends Object{}
class B extends A{}
// class C extends B , A{} // 报错
class D extends B{}

3.5 方法重写

各位同学,学习完继承之后,在继承的基础之上还有一个很重要的现象需要给大家说一下。

叫做方法重写。为了让大家能够掌握方法重写,我们先认识什么是方法重写,再说一下方法的应用场景。

什么是方法重写

当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。

注意:重写后,方法的访问遵循就近原则。下面我们看一个代码演示

例如:我们定义了一个动物类代码如下:

public class Animal  {
    public void run(){
        System.out.println("动物跑的很快!");
    }
    public void cry(){
        System.out.println("动物都可以叫~~~");
    }
}

然后定义一个猫类,猫可能认为父类cry()方法不能满足自己的需求

代码如下:

public class Cat extends Animal {
    public void cry(){
        System.out.println("我们一起学猫叫,喵喵喵!喵的非常好听!");
    }
}

public class Test {
	public static void main(String[] args) {
      	// 创建子类对象
      	Cat ddm = new Cat()// 调用父类继承而来的方法
        ddm.run();
      	// 调用子类重写的方法
      	ddm.cry();
	}
}

执行代码,我们发现真正执行的是Cat类中的cry方法

知道什么是方法重写之后,还有一些注意事项,需要和大家分享一下。

- 1.重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法
- 2.子类复写父类方法时,访问权限必须大于或者等于父类方法的权限
	public > protected > 缺省
- 3. 重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小
- 4. 私有方法、静态方法不能被重写,如果重写会报错。

关于这些注意事项,同学们其实只需要了解一下就可以了。实际上我们实际写代码时,只要和父类写的一样就可以( 总结起来就8个字:声明不变,重新实现

方法重写的应用场景

学习完方法重写之后,接下来,我们还需要大家掌握方法重写,在实际中的应用场景。方法重写的应用场景之一就是:子类重写Object的toString()方法,以便返回对象的内容。

比如:有一个Student类,这个类会默认继承Object类。

public class Student extends Object{
    private String name;
    private int age;

    public Student() {
    }

    public Student(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;
    }
}

其实Object类中有一个toString()方法,直接通过Student对象调用Object的toString()方法,会得到对象的地址值。

public class Test {
    public static void main(String[] args) {
        Student s = new Student("播妞", 19);
        // System.out.println(s.toString());
        System.out.println(s);
    }
}

在这里插入图片描述

但是,此时不想调用父类Object的toString()方法,那就可以在Student类中重新写一个toSting()方法,用于返回对象的属性值。

package com.itheima.extends05;

public class Student {

    private String name;
    private int age;

    public Student() {
    }

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

    // 快捷键 toS 回车


    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

}

重新运行测试类,结果如下

在这里插入图片描述

好了,到这里方法什么是方法重写,以及方法重写的应用场景我们就学习完了。

在这里插入图片描述

package com.itheima.extends06;

public class Dog {

    public void eat(){
        System.out.println("吃狗粮");
    }

    public void drink(){
        System.out.println("狗舔水..");
    }

    public void lookDoor(){
        System.out.println("狗子看家");
    }


}

package com.itheima.extends06;

public class EnZuo extends Dog{

    //不重写
    //特有功能
    public void chaiJia(){
        System.out.println("二哈拆家");
    }
}

package com.itheima.extends06;

public class PiPi extends Dog{

    @Override
    public void eat() {
        super.eat();//调用父类的 eat方法
        System.out.println("吃骨头");
    }
}

package com.itheima.extends06;

public class BaJie extends Dog{

    @Override
    public void eat() {
        System.out.println("吃剩饭..");
    }
}

package com.itheima.extends06;

public class Demo {

    public static void main(String[] args) {
        PiPi pp = new PiPi();
        pp.eat();
        pp.drink();
        pp.lookDoor();
    }
}

3.6 继承后的特点—子类中访问成员的特点

各位同学,刚才我们已经学习了继承,我们发现继承至少涉及到两个类,而每一个类中都可能有各自的成员(成员变量、成员方法),就有可能出现子类和父类有相同成员的情况,那么在子类中访问其他成员有什么特点呢?

  • 原则:在子类中访问其他成员(成员变量、成员方法),是依据就近原则的

定义一个父类,代码如下

package com.itheima.extends07;

public class Fu {
    int num = 100 ;

    public void method(){
        System.out.println("父method");
    }
}

再定义一个子类,代码如下。有一个同名的num成员变量,有一个同名的method成员方法;

package com.itheima.extends07;

public class Zi extends Fu{
    int num = 200;

    public void show(){
        int num = 300;
        System.out.println(num);//300
        System.out.println(this.num);//200
        System.out.println(super.num);//100
        //访问method
        this.method();
        //父类的method
        super.method();
    }

    @Override
    public void method() {
        System.out.println("子method");
    }
}

接下来写一个测试类,观察运行结果,我们发现都是调用的子类变量、子类方法。

package com.itheima.extends07;
/*
  理解
   1:子类方法中访问成员的时候,遵循就近原则。
      变量--局部位置找--成员位置--父类成员位置
      方法--当前类中找---父类中找
   2:
     this表示当前对象,
       通过它可以访问成员变量 成员方法
     super 父类的标识
       通过它可以访问 父类的成员变量 成员方法
 */
public class Demo {
    public static void main(String[] args) {
        //创建子类对象
        Zi z = new Zi();
        z.show();
    }
}

  • 如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分。

在这里插入图片描述

3.7 继承后的特点—构造方法

3.7.1 引入

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化对象成员变量数据的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。(先有爸爸,才能有儿子

继承后子类构方法器特点:子类所有构造方法的第一行都会默认先调用父类的无参构造方法

3.7.2 案例演示

代码如下:

package com.itheima.extends08;

public class Fu {

    public Fu(){
        System.out.println("fu的无参构造~");
    }

    public Fu(String name){
        System.out.println("父的有参构造");
    }
}

package com.itheima.extends08;

public class Zi extends Fu{

    public Zi(){
        // 先执行了父类的无参构造...
//        super();
        // 调用父类的带参构造
        super("haha");
        System.out.println("子的无参构造");

    }

    public Zi(String name){
        // 先执行了父类的无参构造
        System.out.println("子的带参构造");
    }
}

package com.itheima.extends08;

public class Demo {
    /*
     理解
       1:子类构造执行时,默认会先执行父类的无参构造。
        子类构造的第一行 默认是 super()
                            访问父类的构造
       为什么有这个情况,因为我们想在子类对象构建的时候
                      先初始化父类成员。
                      所以在执行子类构造的时候,
                      调用了父类的无参构造。
       2: 怎么调用父类的有参构造呢?
          super() 无参构造
          super(...)
     */
    public static void main(String[] args) {
        Zi zi1 = new Zi();

        Zi zi2 = new Zi("jack");

    }
}

3.7.3 小结

  • 子类构造方法执行的时候,都会在第一行默认先调用父类无参数构造方法一次。
  • 子类构造方法的第一行都隐含了一个**super()**去调用父类无参数构造方法,**super()**可以省略不写。

3.8 super(…)和this(…)

3.8.1 引入

请看上节中的如下案例:

package com.itheima.thisuper;

public class Fu {

    private String name;



    public Fu(String name){
        this.name = name;
    }
}

package com.itheima.thisuper;

public class Zi extends Fu{

    public Zi(){
      //super()  访问父类有参
//        super("哈哈");
       this("哈哈");
    }

    public Zi(String name){
        super(name);
    }
}

我们发现,通过super(…)可以访问父类带参构造,this(…)可以访问本类的构造.

3.8.2 super和this的用法格式

super和this完整的用法如下,其中this,super访问成员我们已经接触过了。

this.成员变量    	--    本类的
super.成员变量    	--    父类的

this.成员方法名()  	--    本类的    
super.成员方法名()   --    父类的

接下来我们使用调用构造方法格式:

super(...) -- 调用父类的构造方法,根据参数匹配确认
this(...) -- 调用本类的其他构造方法,根据参数匹配确认

3.8.3 super(…)案例图解

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构造七调用时,一定先调用父类的构造方法。理解图解如下:

在这里插入图片描述

3.8.4 小结

  • 子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。

  • super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

  • super(…)和this(…)是根据参数去确定调用父类哪个构造方法的。

  • super(…)可以调用父类构造方法初始化继承自父类的成员变量的数据。

  • this(…)可以调用本类中的其他构造方法。

4. 关于今天知识的小结:

会写一个继承结构下的标准Javabean即可

  1. 经理

    成员变量:工号,姓名,工资,管理奖金

    成员方法:工作(管理其他人),吃饭(吃米饭)

  2. 厨师

    成员变量:工号,姓名,工资

    成员方法:工作(炒菜),吃饭(吃米饭)

分享书写技巧:

​ 1.在大脑中要区分谁是父,谁是子

​ 2.把共性写到父类中,独有的东西写在子类中

​ 3.开始编写标准Javabean(从上往下写)

​ 4.在测试类中,创建对象并赋值调用

在这里插入图片描述

代码示例:

员工类

package com.itheima.test;
/*
  员工类 作为父类出现
 */
public class Employee {
    private String id;//工号
    private String name;//姓名
    private double salary;//工资

   //生成满参空参构造
    public Employee() {
    }

    public Employee(String id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
    //生成set get方法

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    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 void work(){
        System.out.println("员工都在干活....");
    }

    public void eat(){
        System.out.println("员工都在吃米饭!!!");
    }
}

管理类

package com.itheima.test;
/*
  经理类 继承  员工类
 */
public class Manager extends Employee {
    //特有成员变量
    private double bonus;
    //写构造
    //  子父类--产生了基础 父类还有成员变量
    // 构造  子类无参调用父类无参  子类带参调用父类带参
    //          只有这样才能实现数据的初始化

   //alt+insert 找到构造 选择 空参的 select none
    public Manager() {
    }
    //alt+insert 找到构造 选择带参  后面 ok
    public Manager(String id, String name, double salary, double bonus) {
        super(id, name, salary);//三个给父类
        this.bonus = bonus;//一个给自己
    }
    //set get

    public double getBonus() {
        return bonus;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    //重写 work

    @Override
    public void work() {
        System.out.println(getName()+"正在管理其他人..."+"管理奖金:"+getBonus());
    }
}

厨师类

package com.itheima.test;
/*
  作为子类 继承自 父类员工
 */
public class Cooker extends Employee{
   //无参和带参
    public Cooker() {
    }

    public Cooker(String id, String name, double salary) {
        super(id, name, salary);//调用父类带参构造
    }

    @Override
    public void work() {
        System.out.println("工号为"+getId()+"厨师:"+getName()+"正在炒菜...."+"工资是:"+getSalary());
    }
}

测试类

package com.itheima.test;

public class Test {

    public static void main(String[] args) {
        Cooker cooker = new Cooker("9527","华安",1000);
        cooker.work();
        //测试 经理
        Manager m = new Manager("001", "吴宇航", 100000, 1000000);
        m.work();
    }
}

  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值