A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 xiekai_sjz 于 2018-9-9 10:04 编辑

day09 继承, super, this, 抽象类
     今天我们继续学习面向对象的相关知识。首先我们先学习继承,Java中的继承作为Java面向对象三大特性之一,在知识点上而言,还是很多的。接下来我们会学习super和this关键字的使用,最后我们学习一种特殊的类-抽象类。最后我们会完成一个简单嗯对发红包小案例。
以下是今天的学习目标:
  • 能够写出类的继承格式
  • 能够说出继承的特点
  • 能够说出子类调用父类的成员特点
  • 能够说出方法重写的概念
  • 能够说出super可以解决的问题
  • 描述抽象方法的概念
  • 写出抽象类的格式
  • 写出抽象方法的格式
  • 能够说出父类抽象方法的存在意义
  • 能够完成发红包案例的代码逻辑


以下是今天的详细笔记:
继承
继承的概述继承主要解决的问题:
        共性抽取 (避免类中重复代码)

父类: 被继承的类, 也叫作基类, 超类(super class).
        父类, 爷爷类, 曾爷爷类, 都统称为父类
子类: 也叫作派生类, 导出类

子类可以拥有父类的内容
子类也可以有自己特有的内容
补充:
子类继承父类是is-a(是一个 / 是一种 / 属于)关系:        兔子     是一种  食草动物        狮子     是一种  食肉动物        食肉动物 是一种  动物        助教     是一种  员工        讲师     是一种  员工
继承的格式
extends: 继承关键字. 用在类的声明上
继承的格式:
    public class 子类类名 extends 父类类名 {

    }
5分钟练习: 练习继承
需求:
定义动物类(Animal), 类中定义方法吃eat(), 内部输出"吃东西"
定义猫类(Cat), 狗类(Dog), 都继承动物类
定义测试类, 在main()方法中, 创建Cat类对象, 调用吃的方法, 创建Dog类对象, 调用吃的方法
代码:
[Java] 纯文本查看 复制代码
public class Animal {
    public void eat() {
        System.out.println("吃东西");
    }
}

public class Cat extends Animal {
}

public class Dog extends Animal {
}

/*
需求:
定义动物类(Animal), 类中定义方法吃eat(), 内部输出"吃东西"
定义猫类(Cat), 狗类(Dog), 都继承动物类
定义测试类, 在main()方法中, 创建Cat类对象, 调用吃的方法, 创建Dog类对象, 调用吃的方法
 */
public class Test {
    public static void main(String[] args) {
        // 创建猫
        Cat cat = new Cat();
        cat.eat();

        // 创建狗
        new Dog().eat();
    }
}

继承中成员变量的访问特点
继承关系中, 父子类成员变量重名, 则:
        直接通过子类对象访问成员变量: 创建对象时等号左边是哪个类, 就优先用哪个类的成员变量, 没有则向父类中找
        间接通过成员方法访问成员变量: 该方法定义在哪个类, 就优先用哪个类中的成员变量, 没有则向父类中找
5分钟练习: 访问成员变量
需求:
定义Fu类, 类中定义如下:
        成员变量: int num = 100; (不要加private)
        成员方法: public void methodFu(), 方法中打印num
定义Zi类, 继承Fu类, Zi类中定义如下:
        成员变量: int num = 200; (不要加private)
        成员方法: public void methodZi(), 方法中打印num
定义测试类
        创建Fu类对象, 通过Fu类对象调用num和methodFu(), 查看结果
        创建Zi类对象, 通过Zi类对象调用num和methodZi(), 查看结果
[Java] 纯文本查看 复制代码
/*
定义Fu类, 类中定义如下:
        成员变量: int num = 100; (不要加private)
        成员方法: public void methodFu(), 方法中打印num
 */
public class Fu {
    int num = 100;

    public void methodFu() {
        System.out.println(num);
    }
}

/*
定义Zi类, 继承Fu类, Zi类中定义如下:
        成员变量: int num = 200; (不要加private)
        成员方法: public void methodZi(), 方法中打印num
 */
public class Zi extends Fu {
    int num = 200;

    public void methodZi() {
        System.out.println(num);
    }
}

/*
需求:
定义Fu类, 类中定义如下:
        成员变量: int num = 100; (不要加private)
        成员方法: public void methodFu(), 方法中打印num
定义Zi类, 继承Fu类, Zi类中定义如下:
        成员变量: int num = 200; (不要加private)
        成员方法: public void methodZi(), 方法中打印num
定义测试类
        创建Fu类对象, 通过Fu类对象调用num和methodFu(), 查看结果
        创建Zi类对象, 通过Zi类对象调用num和methodZi(), 查看结果
 */
public class Test {
    public static void main(String[] args) {
        // 创建父类对象
        Fu fu = new Fu();
        System.out.println(fu.num);  // 100 等号左边是Fu类, 所以获取的是Fu类中的num
        fu.methodFu();               // 100 methodFu()定义在Fu类, 所以获取的是Fu类中的num

        // 创建子类对象
        Zi zi = new Zi();
        System.out.println(zi.num);  // 200 等号左边是Zi类, Zi类中有num, 所以获取的是Zi类中的num
        zi.methodZi();               // 200 methodZi()定义在Zi类, 所以获取的是Zi类中的num
    }
}

区分子类方法中重名的三种变量
变量使用的就近原则:
        如果方法中有局部变量, 则使用局部变量
        如果方法中没有局部变量, 有本类的成员变量, 则使用本类的成员变量
        如果方法中没有局部变量, 也没有本类的成员变量, 有父类的成员变量, 则使用父类的成员变量
        如果局部变量, 本类成员变量, 父类成员变量都没有, 则报错

指定访问:
        变量名: 局部变量(就近原则)
        this.成员变量名: 可以访问本类的成员变量 (当本类没有, 也可以访问父类成员变量)
        super.成员变量名: 可以访问父类的成员变量
        
this: 当前类对象的引用
super: 当前类对象的父类引用
补充:
[Java] 纯文本查看 复制代码
// 说出结果
public class Fu {
    int num = 10;      // 父类成员变量
}

public class Zi extends Fu {
    int num = 20;      // 子类成员变量

    public void method() {
        int num = 30;  // 局部变量
        
        System.out.println(num);       // 30
        System.out.println(this.num);  // 20
        System.out.println(super.num); // 10
    }
}

继承中成员方法的访问特点
父子类中有相同方法的访问规则:
        创建的对象是哪个类的, 就优先使用哪个类中定义的方法, 如果没有定义则向上找 (其实就是方法的重写)
        (创建的是子类对象, 就优先使用子类中定义的方法)
5分钟练习:
需求:
定义Fu类, 类中定义如下:
        成员方法: public void method(), 方法中打印"父类重名方法"
定义Zi类, 继承Fu类, Zi类中定义如下:
        成员方法: public void method(), 方法中打印"子类重名方法"
定义测试类
        创建Fu类对象, 通过Fu类对象调用method(), 查看结果
        创建Zi类对象, 通过Zi类对象调用method(), 查看结果
代码:
[Java] 纯文本查看 复制代码
/*
定义Fu类, 类中定义如下:
        成员方法: public void method(), 方法中打印"父类重名方法"
 */
public class Fu {
    public void method() {
        System.out.println("父类重名方法");
    }
}

/*
定义Zi类, 继承Fu类, Zi类中定义如下:
        成员方法: public void method(), 方法中打印"子类重名方法"
 */
public class Zi extends Fu {
    public void method() {
        System.out.println("子类重名方法");
    }
}

/*
需求:
定义Fu类, 类中定义如下:
        成员方法: public void method(), 方法中打印"父类重名方法"
定义Zi类, 继承Fu类, Zi类中定义如下:
        成员方法: public void method(), 方法中打印"子类重名方法"
定义测试类
        创建Fu类对象, 通过Fu类对象调用method(), 查看结果
        创建Zi类对象, 通过Zi类对象调用method(), 查看结果
 */
public class Test {
    public static void main(String[] args) {
        // 创建父类对象
        Fu fu = new Fu();
        fu.method();  // 父类对象调用父类的方法  父类重名方法

        // 创建子类对象
        Zi zi = new Zi();
        zi.method();  // 子类对象中有相同的方法, 调用的是子类中定义的方法   子类重名方法
    }
}

继承中方法的覆盖重写: 概念与特点
方法的重写: Override, 也叫覆盖, 覆写.
    在继承关系中, 方法名称和参数列表都相同, 这种情况称为方法重写
               
重写: Override. 继承关系中, 方法名相同, 参数列表也相同
重载: Overload.            方法名相同, 参数列表不同
继承中方法的覆盖重写: 注意事项
方法覆盖重写的注意事项:
    1. 父子类之间 方法名 和 参数列表 必须都相同
    2. 子类方法的 "返回值类型" 必须 "小于等于" 父类方法的返回值类型
    3. 子类方法的 "权限修饰符" 必须 "大于等于" 父类方法的权限修饰符
            权限修饰符按权限从大到小: public > protected > 默认(default) > private

@Override: 写在方法声明的上面, 用来检测是不是正确的覆盖重写
        这个注解只是帮助检查. 就算不写, 只要满足重写的要求, 也是正确的方法覆盖重写
补充:
[Java] 纯文本查看 复制代码
public class Fu {
        // 父类 默认权限, 返回值类型 Object
    Object method() {
        System.out.println("父类方法执行!");
        return new Object();
    }
}

public class Zi extends Fu {
    // 子类 public比父类权限大, 返回值类型String比Object小
        @Override
    public String method() {
        System.out.println("子类重写方法执行!");
        return "a";
    }
}
继承中方法的覆盖重写: 应用场景方法重写的应用场景:
        子类增强父类的方法
5分钟练习: 利用方法覆盖重写增强子类功能
需求:
定义老款手机类(Phone), 类中定义方法show(), 打印"显示号码"
定义新款手机类(NewPhone), 类中重写父类show()方法, 使用@Override注解, 方法中利用super调用父类已有功能, 同时增加"显示姓名", "显示头像"的功能(打印模拟)
代码:
[Java] 纯文本查看 复制代码
/*
定义老款手机类(Phone), 类中定义方法show(), 打印"显示号码"
 */
public class Phone {
    public void show () {
        System.out.println("显示号码");
    }
}

/*
定义新款手机类(NewPhone), 类中重写父类show()方法,
使用@Override注解, 方法中利用super调用父类已有功能, 同时增加"显示姓名", "显示头像"的功能(打印模拟)
 */
public class NewPhone extends Phone {
    @Override
    public void show() {
        super.show();  // 调用老手机已有的功能
        // 增加额外的新功能
        System.out.println("显示姓名");
        System.out.println("显示头像");
    }
}

/*
需求:
定义老款手机类(Phone), 类中定义方法show(), 打印"显示号码"
定义新款手机类(NewPhone), 类中重写父类show()方法,
使用@Override注解, 方法中利用super调用父类已有功能, 同时增加"显示姓名", "显示头像"的功能(打印模拟)
 */
public class Test {
    public static void main(String[] args) {
        // 创建老手机对象
        Phone oldPhone = new Phone();
        oldPhone.show();  // 老手机只能显示号码

        System.out.println("-----------");

        // 创建新手机对象
        NewPhone newPhone = new NewPhone();
        newPhone.show();  // 新手机既可以实现老手机功能, 还可以显示姓名和头像
    }
}

继承中构造方法的访问特点
继承关系中, 构造方法的访问特点:
    1. 子类构造方法中第一行默认隐含调用 super(); 子类一定会先调用的父类构造, 然后再执行的子类构造
    2. 子类构造中, 可以通过 super(参数); 来调用父类任何构造
    3. super(); 必须是子类构造方法的第一行语句. 不能多次调用super构造
    4. 子类必须调用父类的构造方法, 不写则赠送无参的 super();
补充:
[Java] 纯文本查看 复制代码
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(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
    }
}

this和supersuper关键字的三种用法
super关键字的3种用法:
    1. 在子类的成员方法中, 访问父类的成员变量: super.age;
    2. 在子类的成员方法中, 访问父类的成员方法: super.eat();
    3. 在子类的构造方法中, 访问父类的构造方法: super();
补充:
[Java] 纯文本查看 复制代码
// 补全代码
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() {
        // 打印父类成员变量num
        System.out.println(super.num); 
    }

    public void method() {
        // 访问父类成员方法method
        super.method();            
    }
}

this关键字的三种用法
this关键字的3种用法:
    1. 在本类的成员方法中, 访问本类的成员变量: this.age;
    2. 在本类的成员方法中, 访问本类的其他成员方法: this.eat();
    3. 在本类的构造方法中, 访问本类的其他构造方法: this(...);
    在第三种用法当中要注意:
        this(...); 调用也必须是构造方法的第一行语句, 也只能有唯一一个
        super() 和 this() 两种构造调用, 不能同时使用
补充:
this.成员变量 / this.成员方法() 如果本类没有该成员变量/成员方法, 也可以调用父类继承过来的成员变量或方法
[Java] 纯文本查看 复制代码
// 补全代码
public class Zi extends Fu {
    int num = 20;

    public Zi() {
        // 调用本类的构造方法 Zi(int num)
        this(213);  
    }

    public Zi(int num) {
        this.num = num;
    }

    public void showNum() {
        // 打印本类中的成员变量
        System.out.println(this.num);    
    }

    public void methodB() {
        // 调用本类其他成员方法 showNum()
        this.showNum();
    }
}

super与this关键字图解

Java继承的三个特点Java中继承的3个特点:
    1. Java只支持单继承, 不支持多继承
    2. Java支持多层继承 (继承体系)
    3. 一个父类可以有多个子类, 一个子类只能有一个直接父类

    // 1. Java只支持单继承, 不支持多继承
    class C extends A{}      //正确
    class C extends A,B {}  //错误

    // 2. Java支持多层继承(继承体系)
    class Yeye {}
    class Fu extends Yeye {}
    class Zi extends Fu {}

抽象类抽象的概念
抽象方法: 没有方法体的方法
        格式: 修饰符 abstract 返回值类型 方法名(参数列表);
抽象类: 包含抽象方法的类就是抽象类
        格式: 修饰符 abstract class 类名 {}
[Java] 纯文本查看 复制代码
 // 抽象类
    public abstract class Animal {
        // 抽象方法
        public abstract void eat();
    }

public class Cat {
    public void eat() {
        sout("吃老鼠");
    }
}

new Cat().eat();

抽象方法和抽象类的使用
如何使用抽象类和抽象方法:
    1. 抽象类不能创建对象
    2. 必须用一个子类来继承抽象父类
    3. 子类必须覆盖重写抽象父类当中所有的抽象方法
                覆盖重写要求:去掉 abstract, 补上方法体大括号{}
    4. 创建子类对象调用方法
5分钟练习: 定义抽象类
需求:
定义抽象类动物(Animal), 其中包含抽象方法吃eat()
定义猫类(Cat), 继承动物类, 重写吃的方法, 方法中输出"猫吃鱼"
代码:
[Java] 纯文本查看 复制代码
/*
定义抽象类动物(Animal), 其中包含抽象方法吃eat()
 */
public abstract class Animal {
    // 抽象方法吃
    public abstract void eat();
}

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}

/*
需求:
定义抽象类动物(Animal), 其中包含抽象方法吃eat()
定义猫类(Cat), 继承动物类, 重写吃的方法, 方法中输出"猫吃鱼"
 */
public class Test {
    public static void main(String[] args) {
        // 创建子类对象
        Cat cat = new Cat();
        cat.eat();  // 子类调用子类中重写的方法
    }
}

抽象方法和抽象类的注意事项
抽象类注意事项:
    1. 抽象类不能创建对象. 只能创建其非抽象子类的对象
    2. 抽象类中可以有构造方法, 是供子类创建对象时, 初始化父类成员使用的(子类的构造方法有默认的super();)
    3. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类
    4. 抽象类的子类, 必须重写抽象父类中所有的抽象方法. 除非该子类也是抽象类
群主发普通红包: 案例分析
需求:
群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:
        1. 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取。
        2. 成员领取红包后,保存到成员余额中。
请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作

群主发红包 (发多少钱, 分多少份)
20 / 3 = 6
20 % 3 = 2
{6, 6, 8} ArrayList<Integer>


收红包 (ArrayList<Integer> list)
    index Random
   int money remove(i)
3个成员随机获取

群主发普通红包: 实现10分钟练习: 实现发红包
子类Manager, 继承User类:
            无参/有参构造
            发红包方法: public ArrayList<Integer> send(int totalMoney, int count) {}
                    a. 创建容器.
                            创建ArrayList<Integer>对象redList, 用于装入平分的金额
                    b. 判断余额.
                            getMoney()获取余额, 判断是否小于要发的金额totalMoney
                                    如果小于, 则提示"余额不足", 并return redList空集合
                                    否则继续
                    c. 从群主余额中扣除发出的红包钱
                    d. 平分红包金额
                            int avg = totalMoney / count;  // 平均金额
                            int mod = totalMoney % count;  // 平分的余数
                            for循环向红包集合中 count - 1 个 avg平均金额
                            添加最后一个金额 avg+mod
                    e. 返回红包集合
                            return redList;
    3. 定义子类Member, 继承User类:
            无参/有参构造
            收红包方法: public void receive(ArrayList<Integer> list) {}
                    a. 生成随机索引
                    b. 利用索引从集合中删除一个金额, 获取被删除的金额作为我领到的红包
                            int money = list.remove(index);
                    c. 将红包金额累加到我的余额
                            super.setMoney(super.getMoney() + 红包金额);
    4. 定义测试类MainRedPacket
            创建群主Manager对象, 姓名为"群主", 余额100
            创建3个成员Member对象, 姓名随意, 余额0
            使用manager对象调用send()方法, 发出20元, 分3份, 获取到集合
            使用3个成员对象, 分别调用receive(...)方法, 获取红包
            调用show()查看效果
代码:
[Java] 纯文本查看 复制代码
/*
用户类
 */
public class User {
    private String name; // 姓名
    private int money;   // 余额

    public User() {
    }

    public User(String name, int money) {
        this.name = name;
        this.money = 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;
    }

    // 显示信息
    public void show() {
        System.out.println("我是:" + name + ", 我的余额:" + money);
    }
}

public class Manager extends User {

    public Manager() {
    }

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

    // 发红包
    public ArrayList<Integer> send(int totalMoney, int count) {
        // 创建红包集合
        ArrayList<Integer> redList = new ArrayList<>();

        // 先判断余额是否充足
        int leftMoney = this.getMoney();
        if (leftMoney < totalMoney) {
            System.out.println("余额不足!");
            return redList;
        }

        // 如果余额充足, 则扣除群主金额
        this.setMoney(leftMoney - totalMoney);

        // 分钱
        int avg = totalMoney / count;
        int mod = totalMoney % count;
        // 除了最后一份钱, 其他都按照平均值
        for (int i = 0; i < count - 1; i++) {
            redList.add(avg);
        }
        // 单独添加最后一份钱
        redList.add(avg + mod);

        // 返回集合
        return redList;
    }
}

public class Member extends User {

    public Member() {
    }

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

    // 收红包
    public void receive(ArrayList<Integer> redList) {
        // 生成随机索引
        int index = new Random().nextInt(redList.size());
        // 从集合中取出金额
        int money = redList.remove(index);
        // 将金额添加到我的余额
        this.setMoney(this.getMoney() + money);
    }
}

/*
发红包
 */
public class Test {
    public static void main(String[] args) {
        // 创建群主
        Manager manager = new Manager("王健林", 100);
        // 创建成员
        Member member1 = new Member("奔波儿灞", 0);
        Member member2 = new Member("霸波尔奔", 0);
        Member member3 = new Member("英特尔", 0);

        // 群主发红包
        ArrayList<Integer> redList = manager.send(20, 3);
        // 群员抢红包
        member1.receive(redList);
        member2.receive(redList);
        member3.receive(redList);

        // 展示结果
        manager.show();
        member1.show();
        member2.show();
        member3.show();
    }
}































2 个回复

倒序浏览
冯光 来自手机 初级黑马 2018-9-3 18:55:19
沙发
多谢老师分享
回复 使用道具 举报
谢谢谢美女!
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马