JAVA-继承与多态

第一节 继承

1.1 继承的概述

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

继承关系当中的特点:
1、子类可以拥有父类的"内容"
2、子类还可以拥有自己专有的内容

1.2 继承的格式

在继承的关系中,”子类就是一个父类“,也就是说,子类可以被当作父类看待

定义父类的格式:(一个普通的类定义)
public class 父类名称{
//…
}
定义子类的格式:
public class 子类名称 extends 父类名称{
//…
}

//定义一个父类:员工
public class Employee {
    public void method(){
        System.out.println("方法执行!");
    }
}
//定义一个子类:讲师
public class Teacher extends Employee{
}
//定义一个子类:助手
public class Assistant extends Employee{
}
public class Demo01Extends {
    public static void main(String[] args) {
        Teacher teacher = new Teacher();
        teacher.method();
        Assistant assistant = new Assistant();
        assistant.method();
    }
}

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

在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
直接通过子类对象访问成员变量:
等号左边是谁,就优先用谁,没有则向上找
间接通过成员方法访问成员变量:
该方法属于谁,就优先用谁,没有则向上找

public class Fu {
    int numFu = 10;
    int num = 100;
    public void methodFu(){
        //使用的是本类当中的,不会向下找子类的
        System.out.println(num);
    }
}

public class Zi extends Fu{
    int numZi = 20;
    int num = 200;
    public void methodZi(){
        //因为本类当中有num,所以这里用的是本类的num
        System.out.println(num);
    }
}
public class Demo02ExtendsField {
    public static void main(String[] args) {
        Fu fu = new Fu();//创建父类对象
        System.out.println(fu.numFu);//只能使用父类的东西,没有任何子类内容
        Zi zi = new Zi();
        System.out.println(zi.numZi);
        System.out.println(zi.numFu);
        System.out.println("==========");
        //等号左边是谁,就优先用谁
        System.out.println(zi.num);//200
        System.out.println(fu.num);//100
        System.out.println("==========");
        //这个方法是子类的,优先用子类的,没有再向上找
        zi.methodZi();//200
        //这个方法是父类当中定义的
        zi.methodFu();//100
    }
}

1.4 区分子类方法中重名的三种

局部变量: 直接写成员变量名
本类的成员变量: this.成员变量名
父类的成员变量: super.成员变量名

public class Fu1 {
    int num = 10;
}
public class Zi1 extends Fu1{
    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
    }
}
public class Demo03ExtendsField {
    public static void main(String[] args) {
        Zi1 zi = new Zi1();
        zi.method();
    }
}

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

在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就有先用谁,如果没有则向上找

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

public class Fu2 {
    public void fuMethod(){
        System.out.println("父类方法执行!");
    }
    public void method(){
        System.out.println("父类重名方法执行!");
    }
}
public class Zi2 extends Fu2 {
    public void ziMethod(){
        System.out.println("子类方法执行!");
    }
    public void method(){
        System.out.println("子类重名方法执行!");
    }
}
public class Demo04ExtendsMethod {
    public static void main(String[] args) {
        Zi2 zi = new Zi2();
        zi.ziMethod();//子类方法执行!
        zi.fuMethod();//父类方法执行!
        //创建了子类对象,所以优先用子类方法
        zi.method();//子类重名方法执行!
    }
}

1.6 继承中方法的覆盖重写

重写(Override)
概念:在继承关系当中,方法的名称一样,参数列表也一样

重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写 重载(Overload):方法的名称一样,参数列表【不一样】
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法

方法覆盖重写的注意事项:
1、必须保证父子类之间方法的名称相同,参数列表也相同
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写
2、子类方法的返回值必须【小于等于】父类方法的返回值范围
Object类是所有类的公共最高父类
3、子类方法的权限必须【大于等于】父类方法的权限修饰符
public>protected>(default)>private
备注:(default)不是关键字default,而是什么都不写,留空

//老款手机
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() {
        super.show();
        System.out.println("显示名字");
        System.out.println("显示头像");
    }
}
public class Demo06Phone {
    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();
    }
}

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

继承关系中,父子类构造方法的访问特点:
1、子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用父类构造方法,后执行的子类构造
2、子类构造可以通过super关键字来调用父类重载构造
3、super的父类构造调用,必须是子类构造方法的第一个语句,不能一个子类构造调用多次super构造

总结:
子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用,super只能有一个,还必须是第一个

1.8 super关键字的三种用法

super关键字的用法有三种:
1、在子类的成员方法中,访问父类的成员变量
2、在子类的成员方法中,访问父类的成员方法
3、在子类的构造方法中,访问父类的构造方法

public class Fu3 {
    int num = 10;
    public void method(){
        System.out.println("父类方法");
    }
}

public class Zi3 extends Fu3{
    int num = 20;
    public Zi3(){
        super();
    }
    public void methodZi(){
        System.out.println(super.num);//父类中的num
    }
    public void method(){
        super.method();//访问父类中的method
        System.out.println("子类方法");
    }
}

1.9 this关键字的三种用法

super关键字用来访问父类内容,而this关键字用来访问本类内容。用法有三种:
1、在本类的成员方法中,访问本类的成员变量
2、在本类的成员方法中,访问本类的另一个成员方法
3、在苯类的构造方法中,访问本类的另一个构造方法

注意事项:
1、this(…)调用也必须是构造方法的第一个语句,唯一一个
2、super和this两种构造调用,不能同时使用

1.10 this和super关键字的内存图

在这里插入图片描述

Java语言是单继承的,一个类的直接父类只能有唯一一个
Java语言可以多级继承
一个子类的直接父类是唯一的,但是一个父类可以拥有很多个子类

第二节 抽象类

2.1 抽象的概念

如果父类当中的方法不确定如何进行{}方法体实现,那么这就应该是一个抽象方法

抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束
抽象类:抽象方法所在的类,必须是抽象类才行,在class之前写上abstract即可

public abstract class Animal {
    //这事一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定
    public abstract void eat();
    //这事普通的成员方法
    public void normalMethod(){
    }
}

2.2 抽象方法和抽象类的使用

如何使用抽象类和抽象方法:
1、不能直接创建new抽象类对象
2、必须用一个子类来继承抽象父类
3、子类必须覆盖重写抽象父类当中所有的抽象方法
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号
4、创建子类对象进行使用

public abstract class Animal {
    //这事一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定
    public abstract void eat();
    //这事普通的成员方法
    public void normalMethod(){
    }
}
public class Cat extends Animal{
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }
}
public class Demo01Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
    }
}

注意事项:
1、抽象类不能创建对象,如果创建,编译无法通过而报错,只能创建其非抽象子类的对象
2、抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
3、抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
4、抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错,除非该子类也是抽象类

一个抽象类不一定含有抽象方法
只要保证抽象方法所在的类是抽象类即可
这样没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途

2.3 练习

群聊发红包

//定义用户
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 startMoney(){
        System.out.println("我是" + name + ",我的余额有:" + money + "元");
    }
    //展示用户抢红包后的余额
    public void endMoney(){
        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() {
    }

    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);
        System.out.println("我是" + getName() + ",我抢到了:" + delta + "元");
        //当前用户原本的金额
        int money = super.getMoney();
        //重新设置余额
        super.setMoney(money + delta);
    }
}

import java.util.ArrayList;

public class RedPackage {
    public static void main(String[] args) {
        Manager manager = new Manager("小松狮",1000);
        Member memberA = new Member("成员A", 20);
        Member memberB = new Member("成员B", 50);
        Member memberC = new Member("成员C", 30);
        manager.startMoney();
        memberA.startMoney();
        memberB.startMoney();
        memberC.startMoney();
        System.out.println("开始抢红包啦!");
        ArrayList<Integer> redList = manager.send(200,3);
        memberA.receive(redList);
        memberB.receive(redList);
        memberC.receive(redList);
        manager.endMoney();
        memberA.endMoney();
        memberB.endMoney();
        memberC.endMoney();
    }
}

第三节 接口

3.1 接口的概述与定义格式

接口就是多个类的公共规范

接口是一种引用数据类型,最重要的内容就是其中的:抽象方法
如何定义一个接口的格式:
public interface 接口名称{
//接口内容
}
备注:换成了关键字interface之后,编译生成的字节码文件仍然是.java–>.class

3.2 接口的抽象方法定义与使用

在任何版本的java中,接口都能定义抽象方法:
格式:
public abstract 返回值类型 方法名称(参数列表);
注意事项:
1、接口当中的抽象方法,修饰符必须是两个固定的关键字: public abstract
2、这两个关键字修饰符,可以选择性地省略
3、方法的三要素,可以随意定义

接口使用步骤:
1、接口不能直接使用,必须有一个”实现类“来”实现“该接口
格式:
public class 实现类名称 implements 接口名称{
//…
}
2、接口的实现类必须覆盖重写(实现)接口中所有的抽象方法
实现:去掉abstract关键字,加上方法体大括号
3、创建实现类的对象,进行使用
注意事项:
如果实现类并没有覆盖重写接口中的所有的抽象方法,那么这个实现类自己就必须是抽象类

public interface MyInterfaceAbstract {
    public abstract void methodAbs();
}
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract{
    @Override
    public void methodAbs() {
        System.out.println("这是一个方法");
    }
}
public class Interface {
    public static void main(String[] args) {
        //错误写法!不能直接new接口对象使用
        //MyInterfaceAbstract inter = new MyInterfaceAbstract();
        //创建实现类的对象使用
        MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
        impl.methodAbs();
    }
}

3.3 接口的默认方法定义与使用

从Java 8开始,接口里允许定义默认方法

格式: public default 返回值类型 方法名称(参数列表)
{
方法体
}

备注:接口当中的默认方法,可以解决接口升级的问题

public interface MyInterfaceDefault {
    //抽象方法
    public abstract void methodAbs();
    public default void methodDefault(){
        System.out.println("这是新添加的默认方法!");
    }
}
public class MyInterfaceDefaultA implements MyInterfaceDefault{
    @Override
    public void methodAbs() {
        System.out.println("实现类A");
    }
}
public class MyInterfaceDefaultB implements MyInterfaceDefault{
    @Override
    public void methodAbs() {
        System.out.println("实现类B");
    }

    @Override
    public void methodDefault() {
        System.out.println("实现类B覆盖重写了接口的默认方法");
    }
}
public class Demo01Interface {
    public static void main(String[] args) {
        //创建了实现类的对象
        MyInterfaceDefaultA a = new MyInterfaceDefaultA();
        a.methodAbs();//调用抽象方法,实际运行的是右侧实现类
        //调用默认方法,如果实现类当中没有,会向上找接口
        a.methodDefault();//这是新添加的默认方法!
        MyInterfaceDefaultB b = new MyInterfaceDefaultB();
        b.methodAbs();//实现类B
        b.methodDefault();//实现类B覆盖重写了接口的默认方法
    }
}

3.4 接口的静态方法定义与使用

从Java8开始,接口当中允许定义静态方法
格式:
public static 返回值类型 方法名称(参数列表)
{
方法体
}
提示:就是将abstract或者default换成static即可,带上方法体

public interface MyInterfaceStatic {
    public static void methodStatic(){
        System.out.println("这是接口的静态使用!");
    }
}

注意:不能通过接口实现类的对象来调用接口当中的静态方法
正确用法:通过接口名称,直接调用其中的静态方法
格式:
接口名称.静态方法名(参数);

public class Demo02Interface {
    public static void main(String[] args) {
        MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();//没必要写
        //impl.methodStatic();错误写法!
        MyInterfaceStatic.methodStatic();
    }
}

3.5 接口的私有方法定义与使用

问题描述:
我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题
但是这个共有方法不应该让实现类使用,应该是私有化的
解决方案:
从java9开始,接口当中允许定义私有方法

1、普通私有方法,解决多个默认方法之间重复代码问题
格式: private 返回值类型 方法名称(参数列表){ 方法体 }
2、静态私有方法,解决多个静态方法之间重复代码问题
格式: private static 返回值类型 方法名称(参数列表){ 方法体 }

public interface MyInterfacePrivateA {
    public default void methodDefault1(){
        System.out.println("默认方法1");
        methodCommon();
    }
    public default void methodDefault2(){
        System.out.println("默认方法2");
        methodCommon();
    }
    private void methodCommon(){
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
}
public interface MyInterfacePrivateB {
    public static void methodDefault1(){
        System.out.println("静态方法1");
        methodCommon();
    }
    public static void methodDefault2(){
        System.out.println("静态方法2");
        methodCommon();
    }
    private static void methodCommon(){
        System.out.println("AAA");
        System.out.println("BBB");
        System.out.println("CCC");
    }
}

3.6 接口的常量定义与使用

接口当中也可以定义”成员变量“,但是必须使用public static final三个关键字进行修饰
从效果上看,这其实就是接口的【常量】
格式:
public static final 数据类型 常量名称 = 数据值;
备注:
一旦使用final关键字进行修饰,说明不可改变
注意事项:
1、接口当中的常量,可以省略public static final,注意:不写也照样是这样
2、接口当中的常量,必须进行赋值,不能不赋值
3、接口中常量的名称,使用完全大写的字母,用下划线进行分隔(推荐命名)

public interface MyInterfaceConst {
    //这其实就是一个常量,一旦赋值,不可以修改
    public static final int NUM_OF_MY_CLASS = 10;
}
public class Demo03Interface {
    public static void main(String[] args) {
        System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS);
    }
}

3.7 继承父类并实现多个接口

使用接口的时候,需要注意:
1、接口是没有静态代码块或者构造方法的
2、一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
格式:
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB{
//覆盖重写所有抽象方法
}
3、如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可
4、如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类
5、如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
6、一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法

public interface MyInterfaceA {
    public abstract void methodA();
    public abstract void methodAbs();
}
public interface MyInterfaceB {
    public abstract void methodB();
    public abstract void methodAbs();
}

public class MyInterfaceImpl implements MyInterfaceA,MyInterfaceB{
    @Override
    public void methodA() {
        System.out.println("覆盖重写了A方法");
    }

    @Override
    public void methodAbs() {
        System.out.println("覆盖重写了AB接口都有的抽象方法");
    }

    @Override
    public void methodB() {
        System.out.println("覆盖重写了B方法");
    }
}

1、类与类之间是单继承的,直接父类只有一个
2、类与接口之间是多实现的,一个类可以实现多个接口
3、接口与接口之间是多继承的
注意事项:
1、多个父接口当中的抽象方法如果重复,没关系
2、多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且要带着default关键字

第四节 多态

4.1 多态的概述和使用

面向对象三大特征:封装性、继承性、多态性
extends继承或者implements实现,是多态性的前提
一个对象同时拥有多种形态,这就叫做对象的多态性

代码当中体现多态性,其实就是一句话:父类引用指向子类对象
格式:
父类名称 对象名 = new 子类名称();
或者:
接口名称 对象名 = new 实现类名称();

public class Fu {
    public void method(){
        System.out.println("父类方法");
    }
    public void methodFu(){
        System.out.println("父类持有方法");
    }
}
public class Zi extends Fu{
    @Override
    public void method() {
        System.out.println("子类方法");
    }
}
public class Demo01Multi {
    public static void main(String[] args) {
        //使用多态写法
        //左侧父类的引用,指向了右侧子类的对象
        Fu obj = new Zi();
        obj.method();//子类方法
        obj.methodFu();//父类持有方法
    }
}

4.2 多态中成员变量的使用特点

访问成员变量的两种方式:
1、直接通过对象名称访问:看等号左边是谁,优先用谁,没有则向上找
2、间接通过成员方法访问:看该方法属于谁,优先用谁,没有则向上找

public class Fu {
    int num = 10;
    public void showNum(){
        System.out.println(num);
    }
}

public class Zi extends Fu{
    int num = 20;
    int age = 18;

    @Override
    public void showNum() {
        System.out.println(num);
    }
}
public class Demo01MultiField {
    public static void main(String[] args) {
        //使用多态写法,父类引用指向子类对象
        Fu obj = new Zi();
        System.out.println(obj.num);//10
//        System.out.println(obj.age);//错误写法!
        System.out.println("========");
        //子类没有覆盖重写,就是父:10
        //子类如果覆盖重写,就是子:20
        obj.showNum();//20
    }
}

在多态的代码当中,成员方法的访问规则是:
看new的是谁,就优先用谁,没有则向上找
口诀:编译看左边,运行看右边
对比:
成员变量:编译看左边,运行还看左边
成员方法:编译看左边,运行看右边

public class Fu {
    int num = 10;
    public void showNum(){
        System.out.println(num);
    }
    public void method(){
        System.out.println("父类方法");
    }
    public void methodFu(){
        System.out.println("父类特有方法");
    }
}

public class Zi extends Fu{
    int num = 20;
    int age = 18;

    @Override
    public void showNum() {
        System.out.println(num);
    }

    @Override
    public void method() {
        System.out.println("子类方法");
    }
    public void methodZi(){
        System.out.println("子类特有方法");
    }
}
public class Demo02MultiMethod {
    public static void main(String[] args) {
        Fu obj = new Zi();
        obj.method();//优先用子
        obj.methodFu();//子类没有,父类有,找到父类
//        编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错
//        obj.methodZi(); 错误写法!
    }
}

4.3 对象的向上转型

对象的向上转型,其实就是多态写法:
格式:父类名称 对象名 = new 子类名称();
含义:右侧创建一个子类对象,把它当作父类看待使用
注意事项:向上转型一定是安全的,从小范围转向了大范围
类似于:
double num = 100;//正确,int---->double,自动类型转换

public abstract class Animal {
    public abstract void eat ();
}
public class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal one = new Cat();
        one.eat();
    }
}

4.4 对象的向下转型

向上转型一定是安全的,没有问题的,但是有一个弊端:
对象一旦向上转型为父类,那么就无法调用子类原本持有的内容
解决方案:用对象的向下转型【还原】

格式:子类名称 对象名 = (子类名称) 父类对象;
含义:将父类对象【还原】成为本来的子类对象
注意事项:
1、必须保证对象本来创建的时候,就是猫,才能向下转型成为猫
2、如果对象创建的时候本来不是猫,现在非要向下转型成为猫,就会报错,ClassCastException
类似于:
int num = (int) 10.0;//正确
int num = (int) 10.5;//不可以,精度损失

public abstract class Animal {
    public abstract void eat ();
}
public class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void catchMouse(){
        System.out.println("猫捉老鼠");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal one = new Cat();
        one.eat();//猫吃鱼
        Cat cat = (Cat) one;
        cat.catchMouse();//猫捉老鼠
    }
}

4.5 instanceof关键字

如何才能知道一个父类引用的对象,本来是什么子类?

格式: 对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例

public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("够吃肉");
    }
    public void watchHouse(){
        System.out.println("狗看家");
    }
}
public class Instanceof {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.eat();//猫吃鱼
        //如果希望调用子类特有方法,需要向下转型
        //判断一下父类引用animal本来是不是Cat
        if (animal instanceof Cat){
            Cat cat = (Cat) animal;
            cat.catchMouse();
        }
        if (animal instanceof Dog){
            Dog dog = (Dog) animal;
            dog.watchHouse();
        }
    }
}

4.6 练习

定义USB接口,具备最基本的开启功能和关闭功能。鼠标和键盘要想在电脑上使用,那么鼠标和键盘也必须遵守USB规范,实现USB接口,否则鼠标和键盘生产出来也无法使用

进行描述笔记本类,实现笔记本使用USB鼠标、USB键盘
USB接口,包含打开设备功能、关闭设备功能
笔记本类,包含开机功能、关机功能、使用USB设备功能
鼠标类,要实现USB接口,并具备点击的方法
键盘类,要实现USB接口,具备敲击的方法

public interface USB {
    public abstract void open();
    public abstract void close();
}
public class Computer {
    public void powerOn (){
        System.out.println("电脑开机");
    }
    public void powerOff(){
        System.out.println("电脑关机");
    }
    //电脑使用USB设备的方法,使用接口作为方法的参数
    public void usbDevice(USB usb){
        usb.open();
        if (usb instanceof Mouse){
            Mouse mouse = (Mouse) usb;//向下转型
            mouse.click();
        } else if (usb instanceof Keybroad){
            Keybroad keybroad = (Keybroad) usb;
            keybroad.input();
        }
        usb.close();
    }
}
public class Mouse implements USB{
    @Override
    public void open() {
        System.out.println("打开鼠标");
    }

    @Override
    public void close() {
        System.out.println("关闭鼠标");
    }

    public void click(){
        System.out.println("鼠标点击");
    }
}

public class Keybroad implements USB{
    @Override
    public void open() {
        System.out.println("打开键盘");
    }

    @Override
    public void close() {
        System.out.println("关闭键盘");
    }

    public void input(){
        System.out.println("键盘输入");
    }
}
public class Main {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();
//        Mouse mouse = new Mouse();
        USB usbMouse = new Mouse();//多态写法
        computer.usbDevice(usbMouse);

        Keybroad keybroad = new Keybroad();//没有使用多态写法
        //方法参数是USB类型,传递进去的是实现类对象
        computer.usbDevice(keybroad);
        computer.powerOff();
    }
}

第五节 final关键字

5.1 final关键字概念与四种用法

final关键字代表最终、不可改变的

常见四种用法:
1、可以用来修饰一个类
2、可以用来修饰一个方法
3、可以用来修饰一个局部变量
4、可以用来修饰一个成员变量

5.2 final关键字修饰类

当final关键字用来修饰一个类的时候
格式:
public final class 类名称{
// …
}
含义:当前这个类不能有任何的子类

注意事项:
1、不能使用一个final类作为父类
2、一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写

public final class MyClass {
    public void method(){
        System.out.println("方法执行");
    }
}

5.3 final关键字修饰成员方法

当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重新

格式:
修饰符 final 返回值类型 方法名称(参数列表){
//方法体
}
注意事项:
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾

public abstract class Fu {
    public void method(){
        System.out.println("父类方法执行");
    }
    //public abstract final void methodAbs();错误!
    public abstract void methodAbs();
}
public class Zi extends Fu{
    @Override
    public void method() {
        System.out.println("子类覆盖重写父类的方法");
    }

    @Override
    public void methodAbs() {

    }
}

5.4 final关键字修饰局部变量

一旦使用final用来修饰局部变量,那么这个变量就不能进行更改
对于基本类型来说,不可变说的是变量当中的数据不可改变
对于引用类型来说,不可变说的是变量当中的地址值不可改变

5.5 final关键字修饰成员变量

对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样不可变
1、由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值
2、对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值
3、必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值

public class Person {
    private final String name;

    public Person() {
        name = "小松狮";
    }

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

    public String getName() {
        return name;
    }

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

5.6 权限修饰符

在这里插入图片描述

第六节 内部类

6.1 内部类的概念与分类

如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类
分类:
1、成员内部类
2、局部内部类(包含匿名内部类)

成员内部类的定义格式: 修饰符 class 外部类名称{
修饰符 class 内部类名称{
//…
}
//…
}
注意:内用外,随意访问;外用内,需要借助内部类对象

public class Body {//外部类
    public class Heart{//成员内部类
        //内部类的方法
        public void beat(){
            System.out.println("心脏跳动!砰砰砰");
            System.out.println("我叫:" + name);
        }
    }
    //外部类的成员变量
    private String name;

    //外部类的方法
    public void methodBody(){
        System.out.println("外部类的方法");
    }

    public String getName() {
        return name;
    }

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

如何使用成员内部类?两种方式:
1、间接方式:在外部类的方法当中,使用内部类;然后main只能调用外部类的方法
2、直接方式:公式:
类名称 对象名 = new 类名称();
外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();

public class Body {//外部类
    public class Heart{//成员内部类
        //内部类的方法
        public void beat(){
            System.out.println("心脏跳动!砰砰砰");
            System.out.println("我叫:" + name);
        }
    }
    //外部类的成员变量
    private String name;

    //外部类的方法
    public void methodBody(){
        System.out.println("外部类的方法");
        Heart heart = new Heart();
        heart.beat();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class Demo01InnerClass {
    public static void main(String[] args) {
        //间接方式
        Body body = new Body();
        body.methodBody();
        //直接方式
        Body.Heart heart = new Body().new Heart();
        heart.beat();
    }
}

6.2 内部类的同名变量访问

如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名

public class Outer {
    int num = 10;
    public class Inner{
        int num = 20;
        public void methodInner(){
            int num = 30;
            System.out.println(num);//30
            System.out.println(this.num);//20
            System.out.println(Outer.this.num);//10
        }
    }
}
public class Demo02InnerClass {
    public static void main(String[] args) {
        Outer.Inner obj = new Outer().new Inner();
        obj.methodInner();
    }
}

6.3 局部内部类定义

如果一个类是定义在一个方法内部的,那么这就是一个局部内部类
”局部“:只有当前所属的方法才能使用它,出了这个方法外面就不能用了

定义格式:
修饰符 class 外部类名称{
修饰符 返回值类型 外部类方法名称(参数列表){
class 局部内部类名称{
//…
}
}
}

小节一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1、外部类:public/(default)
2、成员内部类:public/protected/(default)/private
3、局部内部类:什么都不能写

public class Outer {
    public void methodOuter(){
        class Inner{
            int num = 10;
            public void methodInner(){
                System.out.println(num);
            }
        }
        Inner inner = new Inner();
        inner.methodInner();
    }
}
public class DemoMain {
    public static void main(String[] args) {
        Outer obj = new Outer();
        obj.methodOuter();
    }
}

局部内部类如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略
原因:
1、new出来的对象在堆内存当中
2、局部变量是跟着方法走的,在栈内存当中
3、方法运行结束之后,立刻出栈,局部变量就会立刻消失
4、但是new出来的对象会在堆当中持续存在,直到垃圾回收消失

6.4 匿名内部类

如果接口的实现类(或者是父类的子类)只需要使用唯一的一次
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】

匿名内部类的定义格式:
接口名称 对象名 = new 接口名称(){
//覆盖重写所有抽象方法
};

public interface MyInterface {
    void method();
}
public class MyDemo {
    public static void main(String[] args) {
        MyInterface myInterface = new MyInterface() {
            @Override
            public void method() {
                System.out.println("匿名内部类实现了方法");
            }
        };
        myInterface.method();
    }
}

对格式"new 接口名称(){…}"进行解析:
1、new代表创建对象的动作
2、接口名称就是匿名内部类需要实现哪个接口
3、{…}这才是匿名内部类的内容

另外还要注意几点问题:
1、匿名内部类,在【创建对象】的时候,只能使用唯一一次
如果希望多次创建对象,而且类的内容一样的话,那么就必须使用单独定义的实现类了
2、匿名对象,在【调用方法】的时候,只能调用唯一一次
如果希望同一个对象,调用多次方法,那么必须给对象起个名字
3、匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】

public interface MyInterface {
    void methodA();
    void methodB();
}
public class MyDemo {
    public static void main(String[] args) {
        MyInterface myInterface1 = new MyInterface() {
            @Override
            public void methodA() {
                System.out.println("匿名内部类实现了方法111");
            }
            @Override
            public void methodB() {
                System.out.println("匿名内部类实现了方法222");
            }
        };
        myInterface1.methodA();
        myInterface1.methodB();
        new MyInterface(){
            @Override
            public void methodA() {
                System.out.println("匿名内部类实现了方法333");
            }
            @Override
            public void methodB() {
                System.out.println("匿名内部类实现了方法444");
            }
        }.methodA();
    }
}

6.5 类作为成员变量类型

public class Hero {
    private String name;
    private int age;
    private Weapon weapon;

    public Hero() {
    }

    public Hero(String name, int age, Weapon weapon) {
        this.name = name;
        this.age = age;
        this.weapon = weapon;
    }

    public void attack(){
        System.out.println(age + "岁的" + name + "使用" + weapon.getCode() + "攻击对方");
    }

    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 Weapon getWeapon() {
        return weapon;
    }

    public void setWeapon(Weapon weapon) {
        this.weapon = weapon;
    }
}
public class Weapon {
    private String code;

    public Weapon() {
    }

    public Weapon(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}
public class Main {
    public static void main(String[] args) {
        Hero hero = new Hero();
        hero.setName("盖伦");
        hero.setAge(20);
        Weapon weapon = new Weapon();
        weapon.setCode("多兰剑");
        hero.setWeapon(weapon);
        hero.attack();
    }
}

6.6 接口作为成员变量类型

public class Hero {
    private String name;
    private Skill skill;

    public Hero() {
    }

    public Hero(String name, Skill skill) {
        this.name = name;
        this.skill = skill;
    }

    public void attack(){
        System.out.println(name + "开始施放技能:");
        skill.use();
        System.out.println("施放技能完毕");
    }

    public String getName() {
        return name;
    }

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

    public Skill getSkill() {
        return skill;
    }

    public void setSkill(Skill skill) {
        this.skill = skill;
    }
}
public interface Skill {
    void use();
}
public class Main {
    public static void main(String[] args) {
        Hero hero = new Hero();
        hero.setName("艾希");
/*        匿名内部类:
            Skill skill = new Skill() {
            @Override
            public void use() {
                System.out.println("biu~biu~biu");
            }
        };
        hero.setSkill(skill);*/
        //使用匿名内部类和匿名对象
        hero.setSkill(new Skill() {
            @Override
            public void use() {
                System.out.println("Biu~pia~biu~pia");
            }
        });
        hero.attack();
    }
}

6.7 接口作为方法的参数和或返回值

import java.util.ArrayList;
import java.util.List;
/*
java.util.list正是ArrayList所实现的接口
 */
public class MainInterface {
    public static void main(String[] args) {
        //左边是接口名称,右边是实现类名称,这就是多态写法
        List<String> list = new ArrayList<>();
        List<String> result = addNames(list);
        for (int i = 0; i < result.size(); i++) {
            System.out.println(result.get(i));
        }
    }
    public static List<String> addNames(List<String> list){
        list.add("小松狮");
        list.add("小锦鲤");
        return list;
    }
}

6.8 随机红包案例

public class RandomMode {
    public ArrayList<Integer> divide(final int totalMoney, final int totalCount){
        ArrayList<Integer> list = new ArrayList<>();
        //随机分配,有可能多,有可能少
        //最少一分钱,最多不超过"剩下金额平均数的2倍"
        //第一次发红包,随即范围是0.01元-6.66元
        //第一次发完之后,剩下的至少3.34元
        //此时还需要再发2个红包
        //此时的再发范围应该是0.01元-3.34元(取不到右边,剩下0.01)
        Random r = new Random();//创建一个随机数生成器
        int leftMoney = totalMoney;//剩下多少钱
        int leftCount = totalCount;//剩下多少份
        //随机发前n-1个,最后一个不需要随机
        for (int i = 0; i < totalMoney - 1; i++){
            //按照公式生成随机金额
            int money = r.nextInt(leftMoney / leftCount * 2) + 1;
            list.add(money);//将一个随机红包放入集合
            leftMoney -= money;//剩下的金额越发越少
            leftCount --;//剩下还应该再发的红包个数,递减
        }
        list.add(leftMoney);//最后一个红包不需要随机,直接放入
        return list;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值