Javase-day09-继承(重写、super、this),抽象

一、继承

1、继承的概述

面向对象的三大特征:封装性,继承性,多态性。
继承是多态的前提,如果没有继承,就没有多态。继承主要解决的问题就是:共性抽取。

继承关系当中的特点:

  • 子类可以拥有父类的“内容”;
  • 子类还可以拥有自己专有的内容。
    在这里插入图片描述

2、继承的格式

在继承关系中,子类可以被当作父类看待。例如:父类是员工,子类是讲师,由于"讲师也是一个员工",所以子类可以作为父类。

定义父类的格式:(一个普通的类定义)

public class 父类名称 {
    // ...
}


定义子类的格式:

 public class 子类名称 extends 父类名称 {
     // ...
 }
示例:

// 定义一个父类:员工 Employee
public class Employee {
    public void method() {
        System.out.println("方法执行!");
    }


// 定义了一个员工的子类:讲师Teacher
public class Teacher extends Employee {
}


// 定义了员工的另一个子类:助教Assistant
public class Assistant extends Employee {
}


调用:
public class Demo01Extends {
    public static void main(String[] args) {
    
        Teacher teacher = new Teacher();        // 创建了一个子类对象
        // Teacher类当中虽然什么都没写,但是会继承来自父类的method方法。
        teacher.method();

        Assistant assistant = new Assistant();  // // 创建另一个子类助教的对象
        assistant.method();
    }
}

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

在父、子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

  • 【直接】通过子类对象【访问成员变量】:
    “等号左边是谁,就优先用谁的成员变量,没有则向上找”
    举例: S y s t e m . o u t . p r i n t l n ( z i . n u m ) ; \color{blue}{System.out.println(zi.num);} System.out.println(zi.num); 其中 zi 是通过 Z i z i = n e w Z i ( ) ; \color{blue}{Zi zi = new Zi();} Zizi=newZi(); 创建的变量名;
    创建的对象名,等号左边是 Zi,优先用 Zi类 里的成员变量num。如果子类中没有成员变量num,就会向上找父类的。

  • 【间接】通过【成员方法】访问成员变量:
    “该方法属于谁,就优先用谁的成员变量,没有则向上找”

调用:
public class DemoExtendsField {
    public static void main(String[] args) {
        Fu fu = new Fu(); // 创建父类对象
        System.out.println(fu.numFu); // 只能使用父类的东西,没有任何子类内容

        Zi zi = new Zi();

        System.out.println(zi.numFu); // 10
        System.out.println(zi.numZi); // 20

        // 等号左边是谁,就优先用谁
        System.out.println(zi.num); // 优先用子类中的成员变量num,200
        
//      System.out.println(zi.abc); // 父、子类中都没有的成员变量,编译报错!

        // 这个方法是子类的,优先用子类的,没有再向上找
        zi.methodZi(); // 200
        // 这个方法是在父类当中定义的,
        zi.methodFu(); // 100
    }

}
// 父类:
public class Fu {
    int numFu = 10;

    int num = 100;                          // 与子类成员变量重名

    public void methodFu() {
        System.out.println(num);            // 使用的是本类当中的成员变量num,不会向下找子类的成员变量
    }
}

// 子类:
public class Zi extends Fu {
    int numZi = 20; 
    
    int num = 200;                          // 与父类成员变量重名
    public void methodZi() {
        System.out.println(num);            
        // 因为本类当中有成员变量num,所以优先用的是本类的num,如果本类中没有num,则向上找父类的
    }
}

4、区分子类方法中【重名】的三种变量

  • 调用方法里的局部变量: 直接写成员变量名(“就近原则”)
  • 调用本类的成员变量: this.成员变量名
  • 调用父类的成员变量: super.成员变量名
例如,在子类中:
public class Zi extends Fu {
    int num = 20;                       // 本类中的成员变量,在其父类中,也有一个成员变量num
    public void method() {
        int num = 30;   // 方法里的局部变量
        
        System.out.println(num);        // 30,局部变量

        System.out.println(this.num);   // 20,本类的成员变量

        System.out.println(super.num);  // 10,父类的成员变量
    }
}

调用:
public class Demo01ExtendsField {

    public static void main(String[] args) {
        Zi zi = new Zi();   // 根据Zi类创建一个对象
        
        zi.method();        // 调用Zi类的方法method
    }
}

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

  • 在父子类的继承关系当中,创建子类对象"new Zi()",访问成员方法的规则:
    “通过谁创建对象,就优先用谁的成员方法,如果没有则向上找”

注意:无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。

在这,只写出调用时候的代码:
(父类、子类中都有 成员方法method()public class Demo01ExtendsMethod {
    public static void main(String[] args) {
        Zi zi = new Zi();

        zi.methodFu();  // 通过Zi类,调用Fu类中的方法
        zi.methodZi();  // 调用Zi类中的方法

        // 调用【重名的成员方法】
        zi.method();    // 由于是通过Zi创建的对象,所以优先用子类方法。如果Zi类中没有,那么就会找父类的
    }
}

二、重写(Override)

1、重写(Override)与重载(Overload)的区别

  • 重写(Override)的概念:在继承关系中,方法名一样,参数列表【也一样】。(叫“覆盖重写”不容易弄错)
    “重写——重新写一遍,也就是一模一样了”

  • 重载(Overload)的概念:方法名称一样,参数列表【不一样】。
    “重载——载的东西不同,即参数列表不同”

方法覆盖重写(Override)的特点:创建的是子类对象,则优先用子类方法。

2、方法重写的注意事项

  • 必须保证父子类之间方法的名称相同,参数列表也相同
    @Override:写在方法前面,用来检测是不是有效的正确覆盖重写,起到安全检测的作用。(@是注解)
    这个注解,也可以不写,主要是为了方便检查。

  • 子类方法的【返回值】必须【小于等于】父类方法的返回值类型
    提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。

  • 子类方法的【权限】必须【大于等于】父类方法的权限修饰符。
    提示:public > protected > (default) > private
    备注:(default)不是关键字default,而是什么都不写,留空。

```java
示例:

父类
public class Fu {
    public String method() {
        return null;
    }
}

子类
public class Zi extends Fu {
    @Override   // 可以省略,但最好不要!
    
    // 重写,要保证方法名,参数列表都跟父类的一样
    public String method() {    
    // 父类方法的返回值是String,那么子类重写的方法必须【小于等于】父类
        return null;
    }
}

3、继承中方法重写的应用场景

父类
// 本来的老款手机
public class Phone {
    public void call() {
        System.out.println("打电话");
    }
    
    public void send() {
        System.out.println("发短信");
    }

    public void show() {
        System.out.println("显示号码");
    }
}


子类
// 定义一个新手机,使用老手机作为父类
public class NewPhone extends Phone {

    @Override
    public void show() {    // 重写父类的show()方法
    
        super.show(); // super-->访问父类的东西,把父类的show方法拿过来重复利用
        
        // 自己子类再来添加更多内容
        System.out.println("显示姓名");
        System.out.println("显示头像");
    }
}
调用:
public class Demo01Phone {
    public static void main(String[] args) {
        // 老手机
        Phone phone = new Phone();
        phone.call();
        phone.send();
        phone.show();
        
        // 新手机
        NewPhone newPhone = new NewPhone();
        newPhone.call();
        newPhone.send();
        newPhone.show();
    }
}

三、继承中父子类构造方法的访问

在这里插入图片描述
继承关系中,父子类构造方法的访问特点:

  • 在子类构造方法中,有一个默认隐含的 “super();” 调用父类构造方法,所以一定是先调用的父类构造方法,后执行的子类构造方法。
  • 在子类构造方法中,可以通过super关键字来调用父类重载的构造方法。
  • 在子类构造方法中,super语句(调用父类构造方法)必须是子类构造方法的第一个语句,而且最多只能有一个super语句!

总结:
1、子类的构造方法中必须调用父类构造方法,不写则赠送super();
2、写了则用所写的super调用,而且super语句只能有一个,还必须是子类构造中的第一个。

示例:

父类
public class Fu {
    public Fu() {
        System.out.println("父类无参构造");
    }
    public Fu(int num) {
        System.out.println("父类有参构造!");
    }
}


子类
public class Zi extends Fu {
    public Zi() {
        super();        // 在调用父类无参构造方法
//      super(20);      // 在调用父类重载的构造方法(即调用有参构造方法)
        System.out.println("子类构造方法!");
    }

    public void method() {
//      super();        // 错误写法!只有子类构造方法,才能调用父类构造方法!
    }
}
调用
public class Demo01Constructor {
    public static void main(String[] args) {
        Zi zi = new Zi();
        // new Zi() 调用了Zi类的构造方法,子类构造方法中又会调用父类构造方法
    }
}

四、super、this关键字

1、super关键字

super关键字的用法有三种:

  1. 在子类的成员方法中,访问父类的成员变量。

  2. 在子类的成员方法中,访问父类的成员方法。

  3. 在子类的构造方法中,访问父类的构造方法。


父类
public class Fu {
    int num = 10;

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

子类
public class Zi extends Fu {
    int num = 20;

    public Zi() {
        super();                // 子类构造方法中调用父类构造方法
    }

    public void methodZi() {
        System.out.println(super.num); // 父类中的成员变量num
    }

    public void method() {
        super.method();         // 访问父类中的成员方法method
        System.out.println("子类方法");
    }
}

super关键字用来访问父类内容,而this关键字用来访问本类内容。

2、this关键字

this关键字的用法也有三种:

  1. 在本类的成员方法中,访问本类的成员变量。

  2. 在本类的成员方法中,访问本类的另一个成员方法。

  3. 在本类的构造方法中,访问本类的另一个构造方法。

在第3种用法中,要注意:

  • this(…)调用语句,也必须是一个构造方法的第一个语句,唯一一个。
  • super和this两种构造调用语句,不能同时在同一个构造方法里面使用。
父类
public class Fu {
    int num = 30;
}


子类
public class Zi extends Fu {
    int num = 20;
    // 无参构造方法
    public Zi() {
//      super();    // 错误!由于有了this调用本类的构造方法,故父类的构造方法的调用语句"super();"不再赠送了

        this(123);  // 本类的无参构造,调用本类的有参构造方法1
        
//      this(1, 2); // 错误写法!this语句在构造方法中,只能有一个,必须是第一个语句!
    }
    
    // 有参构造方法1
    public Zi(int n) {
        this(1, 2);             // 调用本类的有参构造2。这个也说明
    }
    
    // 有参构造方法2
    public Zi(int n, int m) {   // 子类构造方法中,默认有一个"super();",调用父类的构造方法
    }

    public void showNum() {
        int num = 10;
        System.out.println(num);        // 局部变量
        System.out.println(this.num);   // 本类中的成员变量
        System.out.println(super.num);  // 父类中的成员变量
    }

    public void methodA() {
        System.out.println("AAA");
    }

    public void methodB() {
        this.methodA();                 // 等价于"methodA();",调用本类的成员方法
        System.out.println("BBB");
    }
}

3、super与this关键字在内存中的情况

super与this内存图

五、Java继承的三个特征

1、Java语言是单继承的,一个类的直接父类只能有唯一的一个;

2、Java语言可以多级继承;比如说:C继承B,B继承A,A继承 j a v a . l a n g . O b j e c t \color{blue}{java.lang.Object } java.lang.Object

3、一个子类的直接父类是唯一的,但是一个父类可以应用好多个子类。例如:B的直接父类是A,C的直接父类也是A。
在这里插入图片描述

六、抽象

1、抽象的概述

\quad  父类知道子类应该包含什么样的方法,但却无法确定子类该如何实现这些方法,在分析事物时,会发现事物的共性,将共性抽取出来,实现的时候,就会有这样的情况:方法功能声明相同,但方法功能具体的主体不同,这时,可以将方法声明抽取出,那么,父类中的这个方法就是一个抽象方法。

2、抽象方法与抽象类的格式

  • 抽象方法:父类中的方法不确定如何用方法体{······}来实现,那么这就应该是一个抽象方法。抽象方法没有方法体!
      格式: p u b l i c \color{red}{public} public a b s t r a c t \color{red}{abstract} abstract 返回值类型 方法名(参数);

  • 抽象类:抽象方法所在的类,必须是抽象类!抽象类,在class前面加上abstract即可。
     格式: p u b l i c \color{red}{public} public a b s t r a c t \color{red}{abstract} abstract c l a s s \color{red}{class} class 类名称{······};

示例
public abstract class Animal {  // 抽象类

    // 抽象方法没有方法体!
    public abstract void eat(); // 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
}

3、抽象方法与抽象类的使用

  • 不能直接创建抽象类对象;
  • 必须用一个子类来继承抽象类;
  • 子类必须重写抽象父类当中【所有的抽象方法】;
    重写(实现):子类去掉抽象方法的 a b s t r a c \color{red}{abstrac} abstrac关键字,然后补上方法体大括号。
  • 创建子类对象进行使用(前提是子类已经全部重写抽象父类的抽象方法,不然子类还是抽象类,就不能直接创建对象)

4、抽象方法与抽象类使用的注意事项

  • 抽象类不能直接创建对象,只能创建其非抽象子类的对象;
    假设可以直接创建抽象类对象,调用其抽象方法时,由于没有具体的方法体,没有意义。

  • 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的;
    子类的构造方法中,有默认的 s u p e r ( ) ; \color{red}{super();} super();可以调用抽象父类构造方法。抽象父类构造方法也只能这样调用了。

抽象父类:
public abstract class Fu {
    // 父类的构造方法不能直接通过new对象来调用,只能通过子类构造方法中的super语句来调用
    public Fu() {
        System.out.println("抽象父类构造方法执行!");
    }
    
    public abstract void eat();
}

子类:
public class Zi extends Fu {
    public Zi() {
        // 在子类的构造方法中,没写super或this语句时,会送一个super();
        System.out.println("子类构造方法执行");
    }

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

调用:
public class DemoMain {
    public static void main(String[] args) {
        Zi zi = new Zi();   // 根据子类创建对象,即调用了子类的构造方法
        zi.eat();
    }
}
  • 抽象类中,不一定有抽象方法,但是由抽象方法的类一定是抽象类;
示例:这样没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用(设计模式中的适配器模式)

public abstract class MyAbstract(){
}
  • 抽象类的子类,必须重写抽象父类中所有的抽象方法,除非该子类也是抽象类。
示例

最高级的抽象父类:
public abstract class Animal {
    public abstract void eat();

    public abstract void sleep();
}

子类Dog也是一个抽象类:
public abstract class Dog extends Animal {
    @Override                               // 只重写了抽象父类的一个抽象方法
    public void eat() {                     
        System.out.println("狗吃骨头");     // 子类继承父类,那么子类拥有了父类的所有
    }                                       // 可以通过Dog类调用其父类Animal的所有方法,即便没有重写,也可以调用
}

可以算"孙子类"的Dog2Ha:
public class Dog2Ha extends Dog {           // 重写了另外一个抽象方法sleep
    @Override                               // 由于已经把Animal类中的所有抽象方法都已经重写了,故这是个普通类
    public void sleep() {
        System.out.println("嘿嘿嘿……");
    }
}

"孙子类"DogGolden:
public class DogGolden extends Dog {        // 也是一个普通类
    @Override
    public void sleep() {
        System.out.println("呼呼呼……");
    }
}
调用:
public class DemoMain {
    public static void main(String[] args) {
//      Animal animal = new Animal(); // 错误!抽象类不能直接创建对象!

//      Dog dog = new Dog();          // 错误,这也是抽象类

        Dog2Ha ha = new Dog2Ha();     // 这是普通类,可以直接new对象。
        ha.eat();
        ha.sleep();

        DogGolden golden = new DogGolden();     // 这也是普通类,可以直接new对象。
        golden.eat();
        golden.sleep();
    }
}

七、继承的综合示例——发红包案例1

1、发红包——分析

在这里插入图片描述

2、发红包——实现

定义一个用户类:
public class User {
    private String name; // 姓名
    private int money; // 余额,也就是当前用户拥有的钱数

    public User() {
    }

    public User(String name, int money) {
        this.name = name;
        this.money = money;
    }

    // 展示一下当前用户有多少钱
    public void show() {
        System.out.println("我叫:" + name + ",我有多少钱:" + money);
    }

    public String getName() {
        return name;
    }

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

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}
定义一个群主类,继承用户类:

import java.util.ArrayList;
public class Manager extends User {
    public Manager() {      // 子类的构造方法默认会送一个 "super();" 来调用父类的无参构造方法
    }

    public Manager(String name, int money) {
        super(name, money);
    }

    public ArrayList<Integer> send(int totalMoney, int count) {
        // 首先需要一个集合,用来存储若干个红包的金额
        ArrayList<Integer> redList = new ArrayList<>();

        // 首先看一下群主自己有多少钱
        int leftMoney = super.getMoney(); // 群主当前余额
        if (totalMoney > leftMoney) {
            System.out.println("余额不足");
            return redList; // 返回空集合
        }

        // 扣钱,其实就是重新设置余额
        super.setMoney(leftMoney - totalMoney);

        // 发红包需要平均拆分成为count份
        int avg = totalMoney / count;
        int mod = totalMoney % count; // 余数,也就是甩下的零头

        // 除不开的零头,包在最后一个红包当中
        // 下面把红包一个一个放到集合当中
        for (int i = 0; i < count - 1; i++) {
            redList.add(avg);
        }

        // 最后一个红包
        int last = avg + mod;
        redList.add(last);

        return redList;
    }
}
定义普通成员类:

import java.util.ArrayList;
import java.util.Random;

public class Member extends User {

    public Member() {
    }

    public Member(String name, int money) {
        super(name, money);
    }

    public void receive(ArrayList<Integer> list) {
        // 从多个红包当中随便抽取一个,给我自己。
        // 随机获取一个集合当中的索引编号
        int index = new Random().nextInt(list.size());
        
        // 根据索引,从集合当中删除,并且得到被删除的红包,给我自己
        int delta = list.remove(index);
        
        // 当前成员自己本来有多少钱:
        int money = super.getMoney();
        
        // 加法,并且重新设置回去
        super.setMoney(money + delta);
    }
}
调用:
import java.util.ArrayList;

public class MainRedPacket {
    public static void main(String[] args) {
        Manager manager = new Manager("群主", 100);

        Member one = new Member("成员A", 0);
        Member two = new Member("成员B", 0);
        Member three = new Member("成员C", 0);

        manager.show(); // 100
        one.show();     // 0
        two.show();     // 0
        three.show();   // 0
        System.out.println("===============");

        // 群主总共发20块钱,分成3个红包
        ArrayList<Integer> redList = manager.send(20, 3);
     
        // 三个普通成员收红包
        one.receive(redList);
        two.receive(redList);
        three.receive(redList);

        manager.show(); // 100-20=80
        // 6、6、8,随机分给三个人
        one.show();
        two.show();
        three.show();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值