Java基础课的中下基础课02

 

目录

九、类的关系之继承

9.0 类的关系

9.1 继承特点

9.2 重写override和重载overload的区别

9.3 Object类中的方法

9.4 继承在内存中的存储形式(mspaint画图工具)​编辑

9.5 关于this和super的区别

十、类的关系之包含和依赖

10.1 包

10.2 包含 has-a 关系

10.3 依赖 use-a 关系

十一、类和类关系练习

11.1 模拟一个学生在机房内使用电脑的例子

(1)扩展:机房内放置5太电脑,让学生进入机房选择一台关闭的电脑使用

11.2 模拟一个警车 小汽车 测速器之间的关系

十二、修饰符(权限修饰符 + 封装 + final)

12.1 类中的四个成员

12.2 修饰符

(1)权限修饰符和特征修饰符都包含什么?

(2)权限修饰符

12.3 封装

12.4 特征修饰符之final最终的,不可变的

十三、修饰符(static + 静态常量应该场景)

13.1静态的static(特征修饰符)

13.2 静态常量的应用场景

(1)小任务:按照买书人的身份做相应的折扣

十四、修饰符之应用(单例模式)

14.1 设计模式

14.3 单例模式(Singleton)

(1)单例模式应用在哪?

(2)对象的加载:

(3)单例模式的实现:

(4)单例模式的形成

十五、类的加载顺序 + 抽象类 +接口

15.1 类的加载顺序

15.2 特征修饰符之native本地的

15.3 特征修饰符之abstract抽象类

15.4 接口

十六、LinkedBox封装

16.1 回顾前面的ArrayBox封装

(1)为什么写ArrayBox,它的优点和缺点

16.2 LinkedBox封装

十七、 多态

17.1 多态


九、类的关系之继承

9.0 类的关系

1、is-a (泛化) 继承 实现

2、has-a (包含)组合 聚合 关联;

(1)组合:整体和部分的关系,就好比人和心脏的关系,整体要出现都出现,要灭亡都灭亡 ,不可以分割

(2)聚合:整体和部分的关系,就好比汽车和车轮子,创建时有可能是分开的,汽车没轮子就跑不了

(3)关联:整体和部分的关系,就好比人有电脑,是因为后来因为某种原因形成在一起的,可以分割

(4)从Java程序来描述包含的关系,就是一个类的对象当做另一个类的属性来存储的

3、use-a (依赖)依赖

(1)一个类的方法中使用到了另一个类的对象,方法内部new,方法传递参数

(2)不是整体和部分关系,某件事情产生了关系,临时组合在一起,这件事情一旦做完,关系即刻解散

9.1 继承特点

(1)子类继承父类,通过一个关键字 enxtends

(2)子类的对象可以调用父类中的(public、protected)属性和方法,当做自己的来使用

(3)构造方法严格意义来说,不算做子类继承过来的,只是单纯的在子类调用构造方法时默认调用父类的构造方法

(4)子类可以添加自己独有的属性和方法的

(5)为什么有重写?子类从父类中继承过来的方法不能满足子类需求,可以在子类中重写(覆盖)父类的方法,更多指的是内容

(6)每一个类都有继承类,如果不写extends关键字,默认继承Object,如果写了extends则继承后面那个父类

(7)Object类非常重要,是一个引用类型的父类(都是直接或间接的继承Object),Object没有父类

(8)Java中继承是单个存在的(单继承)每一个类只能由一个继承类(在extends关键字后面只能写一个类);可以通过传递的方式实现多继承的效果,后续还会有多实现

9.2 重写override和重载overload的区别

方法重写OVERRIDE 方法重载OVERLOAD
产生两个继承关系的类,子类重写父类的方法 一个类中的一组方法
权限 子类可以大于等于父类 无要求
特征 ①父类方法是final,子类不能重写 ②是static,子类不存在 ③abstract,子类必须重写 (子类是具体必须重写,否则子类是抽象类,可以不重写) 无要求
返回值 子类可以<=父类 无要求
名字 子类与父类一致 一个类中的好多方法名必须一致
参数 子类与父类一致 每一个方法的参数必须不一致(个数、类型、顺序)

9.3 Object类中的方法

OBJECT类中的方法 解释
hashCode() 将对象在内存中的地址经过计算得到一个int整数
equals() 1、用来比较两个对象的内容,Object默认效果是== 2、==可以比较基本类型(比较值)可以比较引用类型(比较地址) 3、equals方法是Object类中继承过来的方法,默认效果是比较地址,如果想要改变规则,可以进行方法重写,重写后比较存储的值
toString() 打印输出时将对象变化string字符串
getClass() 获取对象对应类的类映射(反射)
wait() 让线程进入挂起等待状态,还存在方法重载
notify() 让线程唤醒
notifyAll() 唤醒所有
finalize() 权限修饰符是protected,在对象被GC回收的时候,默认调用执行的方法
clone() 权限修饰符是protected,为了克隆对象的

9.4 继承在内存中的存储形式(mspaint画图工具)Java基础课的中下基础课02_第1张图片

9.5 关于this和super的区别

(1)this和super都是指代词,代替的是对象

(2)this代替的是当前执行方法时的那个对象,不一定是当前类的

(3)super代替的是当前执行方法时的对象的父类对象,空间内部那个

(4)this和super都能调用一般属性 和 一般方法

(5)this和super可以放置在类成员的任意位置(属性 方法 构造 块),注意调用一般方法的时候可以来回互相调用,执行可能产生问题(StackOverflowError)

(6)this和super可以调用构造方法(但是必须放在构造器的第一行),构造方法之间不能来回互相调用(编译就不好用了),但是this和super在构造方法中调用另一个类的构造方法不能同时出现在第一行

十、类的关系之包含和依赖

10.1 包

在我们类的第一行出现package关键字,如果package和import同时出现,先写package后写import,package只能有一个,impact是可以多个引入的

10.2 包含 has-a 关系

从Java程序来描述这样的关系,通过一个类的对象当做另一个类的属性来存储的,一个类的对象放置在另一个类中作为属性

package contain;

public class Wheel {
    //属性
    public String brand; //车轮子品牌
    public int size; //尺寸
    public String color; //颜色

    //方法
    public void turn() {
        System.out.println("车轮子可以旋转");
    }

    //构造方法
    //最好是创建一个无参的构造,因为有时老程序员或老项目,可能会不用有参的构造方法,防止报错
    public Wheel(){}

    public Wheel(String brand, int size, String color) {
        this.brand = brand;
        this.size = size;
        this.color = color;
    }

}
package contain;

public class Car {
    //属性
    public String brand; //汽车品牌
    public String type; //汽车型号
    public String color; //汽车颜色
    //通过一个类的对象当做另一个类的属性来存储
    public Wheel wheel; //假设车上有一个轮子 --> 包含关系

    //方法(这个车里有什么?)
    public void showCar() {
        System.out.println("这是一辆" + brand + "品牌的汽车");
        //描述一些这个车里面的轮子
        System.out.println("车上有一个轮子" + wheel.brand + "品牌的轮子");
        //让车轮子转动起来
        //方法一定是对象调用的(谁的方法谁调,turn()是wheel(对象)的方法),车轮子的方法肯定是车轮子对象调用的,可以放置在任何地方
        wheel.turn();
    }

    //构造方法
    //最好是创建一个无参的构造,因为有时老程序员或老项目,可能会不用有参的构造方法,防止报错
    public Car(){}

    public Car(String brand, String type, String color, Wheel wheel) {
        this.brand = brand;
        this.type = type;
        this.color = color;
        this.wheel = wheel;
    }
}
package contain;

public class Test {

    public static void main(String[] args) {

//        Car car = new Car();
//        car.showCar(); //展示汽车
//        car.brand = "宝马";
//        /*
//        给对象中的属性赋值需要new一个,但是现在这个车轮子是车里面的,car.wheel = new Wheel()
//        Wheel wheel = new Wheel();
//        */
//        car.wheel = new Wheel();
//        car.wheel.brand = "米其林";

        new Car("宝马","x7","蓝色", new Wheel("米其林",400,"黑色"));

    }
          

}

10.3 依赖 use-a 关系

1、屠夫-----杀------猪,一个类屠夫 ----做一件事情------杀猪,不是整体和部分关系,某件事情产生了关系,临时组合在一起,这件事情一旦做完,关系即刻解散

2、Java程序体现的形式为:一个类的方法中使用到了另一个类的对象,第一个可以在方法中传递参数,第二个可以在方法中(内部)自己创建new

  • 农夫做了一件事情--养猪

  • 屠夫做事情---杀猪

  • 猪做了事情---猪被杀了,猪长大了,猪能告诉你,它自己的名字和体重 可以看出每一个类只负责做自己的事,类和类之间的关系,完全的符合现实生活的本质,这就是面向对象的思想

package depend;

public class Pig {//描述猪

    //属性
    //
    public String name;//名字
    public int weight = 20;//体重

    //构造方法
    public Pig() {}
    public Pig(String name) {
        this.name = name;
    }

    //方法
    //描述一个方法 表示猪被杀
    public void beKilled() {
        System.out.println(this.name + "被杀");
    }

    /*
        1.上面方法实现了屠夫杀猪,如果我想让猪涨体重咋办?
        2.描述一个方法:让猪长大(让猪每一个月涨到前一个与的两倍)
         参数:需要提供几个月了 返回值: 返回长大后的猪体重
        3.你会发现你如果每次想看体重的时候都要去调用你这个方法,但是你每次调用这个方法,
         猪就会自动涨体重,如果我只想看体重,不想涨体重呢?
        4.一个方法只让它做一件事时最好的,要实现 只涨不看,或者 只看不涨才好,所以看体重
         和长大是两件事情
        public int growUp(int month) {
            for (int i = 1; i < month; i++) {
                this.weight *= 2;
            }
            return  this.weight;
        }
    */
    public int growUp(int month) {
        for (int i = 1; i <= month; i++) {
            this.weight *= 2;
        }
        return  this.weight;
    }

    //方法: 获取猪的体重
    public int getWeight() {
        return this.weight;
    }
    //方法:获取猪的名字
    public String getName() {
        return this.name;
    }

}
package depend;

public class Hunter {
    public String name;

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

    /*
        1. 描述方法:一个屠夫杀猪的方法 参数:需要提供 一个猪
        2. 发现这方法有缺陷,屠夫是杀猪的,它不需要自己养一头猪,只要有个猪给它,它就杀
           所以这个方法需要个猪没有错,是new好(new相当于自己创建猪),还是传过来好,
           肯定是别人给才可以。因为你new的猪又是一个新的地址了,就不是同一个猪了
        3. 这时就需要一个农夫来养猪(农夫和猪依赖关系)
        public void killPig() {
            System.out.println("屠夫执行了杀猪方法");
            //依赖-----在屠夫的方法中使用到了猪的对象(这个就是养猪的过程)
            Pig pig = new Pig("GG-bond");
            pig.growUp(5);//让猪涨体重

            String pigName = pig.getName();
            int pigWeight = pig.getWeight();
            System.out.println(pigName + " 的体重为:" + pigWeight);
            pig.beKilled(); //因为是猪死了,不是屠夫死了
        }
    */

    public void killPig(Pig pig) {
        System.out.println("屠夫执行了杀猪方法");

        String pigName = pig.getName();
        int pigWeight = pig.getWeight();
        System.out.println(pigName + " 的体重为:" + pigWeight);
        pig.beKilled(); //因为是猪死了,不是屠夫死了
    }

}
package depend;

public class Farmer {//农夫

    //农夫养猪也是个方法
    //参数:几个月    返回值:返回一个猪
    public Pig feedPig(int month) {
        //依赖-----在屠夫的方法中使用到了猪的对象(这个就是养猪的过程)
        Pig pig = new Pig("GG-bond");
        pig.growUp(5);//让猪涨体重,涨肉是Pig类中的事情
        return pig;
    }
}
p
ackage depend;

public class Test {
    public static void main(String[] args) {
        /*
        * 农夫做了一件事情--养猪
        * 屠夫做事情---杀猪
        * 猪做了事情---猪被杀了,猪长大了,猪能告诉你,它自己的名字和体重
        * 所以每一个类只负责做自己的事,类和类之间的关系,完全的符合现实生
        * 活的本质,这就是面向对象的思想
        * */
        //创建农夫对象
        Farmer farmer = new Farmer();
        //农夫做了一件事情 ---养猪,然后再返回一头猪
        Pig pig = farmer.feedPig(5);
        //创建屠夫对象
        Hunter hunter = new Hunter("曹先生");
        //屠夫做事情----》杀猪
        hunter.killPig(pig);
    }
}

十一、类和类关系练习

11.1 模拟一个学生在机房内使用电脑的例子

  • 模拟一个学生在机房内使用电脑的例子

  • 有一个机房

  • 有一台电脑 电脑有开机/关机状态,电脑被打开,被关闭,被使用

  • 有一个学生 使用电脑Java基础课的中下基础课02_第2张图片

/*
* 2.分析:
*    一共有几个具体的类?(电脑、学生、机房)
*    类和类之间的关系?
*       学生--电脑 依赖关系 学生使用电脑 (一个类的方法中使用到了另一个类的对象)
*       机房--电脑 聚合关系 机房内有电脑 (一个类的对象为另一个类的属性)
*       机房--学生 依赖关系 机房欢迎学生进来使用
*
*       学生使用电脑,useComputer是学生的方法
*       欢迎学生进入机房,welcomeStudent是机房的方法
*       电脑被打开、使用、关闭是电脑的方法
*
*
* 3.分别描述三个类:(找最简单的类)
* 先从电脑下手,聚合关系,因为电脑是在机房里面的,学生要用电脑,是电脑自己的事)
*   ①描述电脑  属性---来描述状态,编号    方法---被打开 被关闭 被使用
*   ②描述机房  属性---姓名  方法---使用电脑
*   ③描述学生  属性--电脑  方法---欢迎学生进来
* 4.测试
*   创建对象  对象去做事情
*
* 观看步骤:① ② ③
* */
package contain.Demo01;

public class Computer {//①
    // 一、属性--描述自己状态
    // 开/关闭,可以赋初值,电脑一开始咱们默认它关闭的状态
    private boolean used = false;//true开着的 false关闭
    //属性--编号,方便查找
    public int number;

    //设置构造方法
    public Computer(){}
    public Computer(int number) { //给定电脑编号
        this.number = number;
    }

    //方法:电脑被学生打开、关闭、使用
    public void beOpen() {
        this.used = true; //状态切换成开着的
        System.out.println(this.number + "号电脑被打开啦!");
    }
    public void beClose() {
        this.used = false; //状态切换成关闭
        System.out.println(this.number + "号电脑被关闭啦!");
    }
    public void beUsing() {
        this.used = false; //状态切换成关闭
        System.out.println(this.number + "号电脑正在被使用中!");
    }
}
package contain.Demo01;

public class Student {//②
    //属性--学生名字
    private String name;

    //构造方法
    public Student() {}
    public Student(String name) {
        this.name = name;
    }

    //学生---电脑 依赖关系  学生使用电脑
    public void useComputer(Computer computer) {
        System.out.println("学生" + this.name +"开始使用电脑了");
        //学生用电脑,电脑怎么用的?当然是被打开了(调用电脑中的方法)
        computer.beOpen();  //打开
        computer.beUsing(); //使用
        computer.beClose(); //关闭
    }

    //在MachineRoom中,想要获取Student中的私有名字,就要在Student中设置get方法进行获取
    public String getName() {
        return name;
    }

}
package contain.Demo01;

public class MachineRoom { //③
    //机房---电脑 聚合关系 机房内有电脑
    //可以赋初值,因为在机房创建完毕后,电脑应该是摆放在机房内的,是摆放好的
    public Computer computer = new Computer(1);

    //机房---学生 依赖关系 欢迎学生来使用,学生使用机房里面的电脑,机房
    // 创建好了打开门欢迎学生进来,我认为机房比学生大,所以把这个方法写在机房里
    public void welcomeStudent(Student student) {
        //欢迎学生进入机房,这个学生是有名字的,但名字私有,所以在Student中写一个get方法获取
//        System.out.println("欢迎学生进入机房");
        //在MachineRoom中,想要获取Student中的私有名字,就要在Student中设置get方法,
        // 然后在调用Student中的get方法,进行获取
        String studentName = student.getName();
        System.out.println("欢迎:" + studentName + "进入机房");
        //学生进入机房后,使用电脑useComputer,也就是说是学生在做事
        student.useComputer(computer);//学生使用的电脑正好和机房摆放的电脑是同一台computer
    }

}
package contain.Demo01;

public class Test {

    public static void main(String[] args) {
        //创建机房
        MachineRoom machineRoom = new MachineRoom();
        //创建学生对象
        Student student = new Student("五花肉");
        //欢迎学生进入机房,welcomeStudent是机房的方法
        machineRoom.welcomeStudent(student);
    }
    /*
    * 欢迎学生进入机房,welcomeStudent是机房的方法
    * 学生使用电脑,useComputer是学生的方法
    * 电脑被打开、使用、关闭是电脑的方法
    * */


}

(1)扩展:机房内放置5太电脑,让学生进入机房选择一台关闭的电脑使用

(1)扩展:机房内放置5太电脑,让学生进入机房选择一台关闭的电脑使用 思考:如果你想让机房中放一堆,只能是数组(用于存放5台电脑 Computer[]) (2)扩展:(学生也有9个,陆续进入机房) 思考:学生关不关电脑取决于它的素质

package contain.Demo01;

public class Computer {//①
    // 属性--描述自己状态
    // 开/关闭,可以赋初值,电脑一开始咱们默认它关闭的状态
    private boolean used = false;//true开着的 false关闭
    //属性--编号,方便查找
    public int number;

    //设置构造方法
    public Computer(){}
    public Computer(int number) { //给定电脑编号
        this.number = number;
    }

    //方法:电脑被学生打开、关闭、使用
    public void beOpen() {
        this.used = true; //状态切换成开着的
        System.out.println(this.number + "号电脑被打开啦!");
    }
    public void beClose() {
        this.used = false; //状态切换成关闭
        System.out.println(this.number + "号电脑被关闭啦!");
    }
    public void beUsing() {
        this.used = false; //状态切换成关闭
        System.out.println(this.number + "号电脑正在被使用中!");
    }

    //设计两个方法 获取电脑的编号 和 电脑的状态
    public int getNumber() {//获取电脑的编号
        return  this.number;
    }
    public boolean getUsed() {//获取电脑的状态
        return  this.used;
    }
}
package contain.Demo01;

public class Student {//②
    //属性--学生名字
    private String name;
    //属性--学生素质 0~9的整数 0~2素质好 3~9素质不好
    private int RP = (int)(Math.random()*10);//获取随机数人品好不好都看缘分

    //构造方法
    public Student() {}
    public Student(String name) {
        this.name = name;
    }

    //学生---电脑 依赖关系  学生使用电脑
    public void useComputer(Computer computer) {
        System.out.println("学生" + this.name +"开始使用电脑了");
        //学生用电脑,电脑怎么用的?当然是被打开了(调用电脑中的方法)
        computer.beOpen();  //打开
        computer.beUsing(); //使用
        if (this.RP < 2) { //0~2人品好
            computer.beClose(); //关闭
        } else {
            System.out.println(this.name + "RP有问题 没关电脑");
        }
    }

    //在MachineRoom中,想要获取Student中的私有名字,就要在Student中设置get方法进行获取
    public String getName() {
        return name;
    }

}
package contain.Demo01;

public class MachineRoom { //③
    //机房---电脑 聚合关系 机房内有电脑
    //可以赋初值,因为在机房创建完毕后,电脑应该是摆放在机房内的,是摆放好的
    //如果机房中有5台电脑 Computer[] 电脑数组
    public Computer[] computers = new Computer[5];
    //设计一个程序块:用来给电脑数组进行初始化(赋值),程序块是在每一次创建
    // 当前对象调用构造方法之前程序块都会执行一次(用构造器实现也可以)
    {
        for (int i = 0; i < computers.length; i++) {
            //Computer(i+1)给电脑进行编号
            computers[i] = new Computer(i+1);
        }
    }

    //机房---学生 依赖关系 欢迎学生来使用,学生使用机房里面的电脑,机房
    // 创建好了打开门欢迎学生进来,我认为机房比学生大,所以把这个方法写在机房里
    public void welcomeStudent(Student student) {
        //欢迎学生进入机房,这个学生是有名字的,但名字私有,所以在Student中写一个get方法获取
//        System.out.println("欢迎学生进入机房");
        //在MachineRoom中,想要获取Student中的私有名字,就要在Student中设置get方法,
        // 然后在调用Student中的get方法,进行获取
        String studentName = student.getName();
        System.out.println("---------欢迎:" + studentName + "进入机房--------");
        //学生进入机房后,使用电脑useComputer,也就是说是学生在做事,然后选择一台关闭的电脑进行使用
        for (int i = 0; i < computers.length; i++) {//学生进入机房后,选择一台状态为关闭的电脑
            //找寻一台电脑 获取它的状态
            boolean computersState = computers[i].getUsed();//computers这个数组中的每一个元素都代表一个有编号的电脑
            if (!computersState) {//表示电脑是关闭状态就开始使用
                System.out.println(studentName + "找到了电脑是关闭状态的");
                student.useComputer(computers[i]);
                break;//找到了之后,就不继续找了
            }
        }

    }

}
package contain.Demo01;

public class Test {
    /*
    * 扩展:机房内放置5太电脑,让学生进入机房选择一台关闭的电脑使用
    * 思考:如果你想让机房中放一堆,只能是数组(用于存放5台电脑 Computer[])
    * 扩展:(学生也有9个,陆续进入机房)
    * 思考:学生关不关电脑取决于它的素质
    * */
    public static void main(String[] args) {
        //创建机房
        MachineRoom machineRoom = new MachineRoom();
    /*
        machineRoom.init();给电脑进行遍历,但是我不想在这里面调用,太麻烦了,有什么是一开始创建对象
        就带着这个电脑的呢?
        1. 构造器(用构造器实现也可以)
        2. 在构造方法之前还有一个程序块的东西,如果你想让在创建对象的同时就执行你可以创建构造器,如果
           你想在创建对象之前就执行就使用程序块(你想你是先有一些电脑之后,才去创建的机房)
        所以你把构造器删掉,再把public void init()删掉变成程序块就行了
    */
        Student student = new Student("五花肉");
        //欢迎学生进入机房,welcomeStudent是机房的方法
        machineRoom.welcomeStudent(student);

        //表示陆陆续续的进入
        Student student1 = new Student("张三");
        machineRoom.welcomeStudent(student1);
        Student student2 = new Student("王五");
        machineRoom.welcomeStudent(student2);
        Student student3 = new Student("赵四");
        machineRoom.welcomeStudent(student3);
        Student student4 = new Student("铁蛋");
        machineRoom.welcomeStudent(student4);

    }


}

11.2 模拟一个警车 小汽车 测速器之间的关系

package contain.Demo02;

public class Test {
    /*
    * 模拟一个警车 小汽车 测速器之间的关系
    *   测速器测量小汽车的速度   100米5秒钟
    *   如果小汽车超速 警车追击
    *   如果警车追击成功 输出追击时间
    *   如果警车追不上 输出追不上啦
    * 1.分析:
    *   分析有具体的几个类(警车 小汽车 测速器)
    * 2.类和类的关系:
    *    警车---小汽车 依赖关系 警车追小汽车 一个类的方法中使用到了另一个类的对象
    *    警车---测速器 依赖关系 测速器发现小汽车超速警车追击 一个类的方法中使用到了另一个类的对象
    *    小汽车--测速器 依赖关系 测速器测量小汽车
    * 3.分别描述每一个具体类的信息
    *   小汽车   属性---速度
    *   警车    属性---速度   方法---追车(小汽车)
    *   测速器  属性--标准时间  方法--测量汽车(小汽车)
    * 4.测试  创建对象 做事情
    * */
    public static void main(String[] args) {
        Car car = new Car(100); //小汽车速度
        Velometer velometer = new Velometer();
        velometer.measure(car);
    }
}
package contain.Demo02;

public class Car {

    //属性 小汽车自己的速度
    private int speed;

    //构造方法
    public Car() {}
    public Car(int speed) {
        this.speed = speed;
    }

    //提供一个获取小汽车的速度
    public int getSpeed() {
        return speed;
    }

}
package contain.Demo02;

public class PolicCar {

    //属性--警车自己的速度
    private int speed;
    //构造器
    public PolicCar() {}
    public PolicCar(int speed) {
        this.speed = speed;
    }

    //警车追击小汽车 依赖关系
    public void chase(Car car) {
        //获取小汽车速度
        int carSpeed = car.getSpeed();
        //比较两车的速度
        if (this.speed > carSpeed) {//可以追到
            System.out.println("警车开始追击!");
            int time = 100/(this.speed - carSpeed);
            //try表示测试一下catch表示捕获异常
            try {
                Thread.sleep(3000);//编译时异常
            }catch (Exception e) {
                e.printStackTrace(); //捕获异常
            }
            System.out.println("经过" + time +"秒追到了");
        } else {//追不到了
            System.out.println("小汽车炮的太快了,追不到了");
        }
    }
}
package contain.Demo02;

public class Velometer {
    //属性---测速器规定好的标准时间
    private int standardTime;
    //构造器
    public Velometer(){}
    public Velometer(int standardTime) {
        this.standardTime = standardTime;
    }

    //测速器 测量 小汽车速度 依赖
    public void measure(Car car) {
        //获取小汽车的速度
        int carSpeed = car.getSpeed();//但你不给速度的时候,他会默认找Car的无参数构造方法,然后Car没有设定默认速度,所以会报错
        //计算小汽车运行时间
        int carTime = 100/carSpeed;
        //比较
        if (this.standardTime <= carTime) {//说明小汽车时间长,跑得慢,没超速
            System.out.println("速度正常,请保持安全速度行驶");
        } else { //超速了
            System.out.println("经测量,小汽车超速了,警车可以追击");
            //需要一个警车对象做事情---追车
            //测速器发现小汽车超速、通知警车做事(应该这样才合理 观察者设计模式)
            PolicCar pc = new PolicCar(80);//警车速度

            pc.chase(car);
        }
    }
}

十二、修饰符(权限修饰符 + 封装 + final)

12.1 类中的四个成员

类中的四个成员:

属性 ---- 静态描述类的特征(变量 存值 存数据)name

方法 ---- 动态描述类的行为(做事情)eat

构造方法 ---- 作用是构造当前类的对象(做事情)

程序块{}(可省略) ---- 创建对象后,每运行一次对象,就执行一次

12.2 修饰符

(1)权限修饰符和特征修饰符都包含什么?

权限修饰符
public 公共的
protected 受保护的
默认不写 默认的
private 私有的
特征修饰符
final 最终的 或 不可改变的
static 静态的
abstract 抽象的
native 本地的
transient 瞬时的(短暂的 ----- 序列化 )
synchronized 同步的 线程相关的知识
volatile 不稳定的

(2)权限修饰符

1、权限修饰符能修饰什么?

(1)权限修饰符可以用来修饰 类本身 和 类中的成员(除程序块)

(2)权限修饰符用来修饰类的时候只有两个可以用(public和默认不写)

(3)权限修饰符都可以来修饰类中其他成员(除程序块)

2、权限修饰符范文范围?

PUBLIC 公共的 本类 同包 子类(当前项目中任意类的位置只要有对象都可以访问)
protected 保护的 本类 同包 子类(通过子类对象在子类范围内部访问父类,在子类的main中就访问不到)
默认不写 默认的 本类 同步
private 私有的 本类Java基础课的中下基础课02_第3张图片

12.3 封装

1、Java面向对象之封装

(1)封装:将一些数组或执行过程,进行一个包装

(2)目的:保护这些数据 或 执行过程的安全,不可以在类以外的部分直接访问,需要给属性的操作提供一些方式(方法)set和get方法

(3)方法本身就算是封装,封装了执行的过程,保护过程的安全,隐藏了执行细节,增强复用性

(4)好多的方法 和 属性 包装起来了----> 就是类

2、对属性本身的封装:

(1)属性私有(封装在类中)

(2)提供操作属性相应的方式(共有的方式)

(3)建议大家属性不要共有的-----太不安全了,最好用private,如:private int age -----> setAge getAge,通过get set获取和设置

12.4 特征修饰符之final最终的,不可变的

1、修饰变量:

(1)一旦变量被存储了一个值,如用final修饰后,则不让在改变-----相当于常量了(值没法改动)

2、注意变量类型是基本类型还是引用类型:

(1)如果修饰的变量是基本类型:则变量内的值不让更改-----常量;

(2)如果修饰的变量是引用数据类型:则变量内的地址引用不让更改---对象是唯一的了

3、修饰属性:

(1)全局变量,存储在堆内存的对象内一个空间

(2)属性如果没有赋值,有默认值存在的,但是一旦属性用final修饰后

(3)属性用final修饰后,必须给属性赋初值,否则编译报错

4、属性特点与修饰变量一致:注意变量类型是基本类型还是引用类型:

(1)如果修饰的变量是基本类型:则变量内的值不让更改-----相当于常量;

(2)如果修饰的变量是引用数据类型:则变量内的地址引用不让更改---对象是唯一的了(如果修饰的是value[10]数组,那么数组的地址value不能改变,但是数组内的元素value[i]可以改变)

5、修饰方法:方法是最终的方法,不可更改

(1)子类继承父类的方法,将父类的方法重写(覆盖)

(2)final修饰的方法,要求不可以被子类重写(覆盖)

6、修饰类本身:类是最终的不可更改的

(1)(太监类,就是没有子类)此类不可以被其他子类继承

(2)通常都是一些定义好的工具类 (如:Scanner类、Math类)

package test;

public class TestFinal {
    public static void main(String[] args) {
    /*
        final int a;//声明变量 内存开辟栈内存空间
        a = 1;//赋值 常量区取得一个常量 赋值一份存入a空间内
        a = 10;//会报错,因为final存储一旦开辟空间存入值,就无法改变,数组也是一样的
    */
        TestFinal tFinal = new TestFinal();
        //下面两条可以,因为你执行方法是临时开辟的空间,用完之后会销毁。
        tFinal.testNum(1);
        tFinal.testNum(10);
    }
    //在方法内部是不能再次给临时变量赋值的,因为你上面tFinal.testNum(1);已经给临时变量赋值了,就不能在赋值了
    public void testNum(final int a) {
//        a = 10;
    }
    //在方法内部是可以给数组内元素再次赋值的,因为数组的话,你传递的是引用数组类型
    public void testNum02(final int[] a) {
        a[0] = 10;
        //new不行,因为你不能给数组提供新的地址,但是数组里面的元素是可以改的
//        a = new int[];
    }
}

十三、修饰符(static + 静态常量应该场景)

13.1静态的static(特征修饰符)

1、static可以修饰什么?

(1)可以修饰:修饰属性、修饰方法、修饰块、修饰类(内部类)

2、static特点:

(1)静态元素在类加载时就初始化了,创建的非常早,此时没有创建对象(可以只加载类,不创建对象)

(2)静态元素存储在静态元素区中,每一个类有一个自己的区域(如:Person这个类有自己的静态区,在新创建一个类Pig类,它也有自己的静态区),与别的类不冲突

(3)静态元素只能加载一次(无论是通过类Person来找静态变量,还是通过对象new Person()来找静态变量,最后找到的都是相同的一份静态空间);

(4)这一份静态区域怎么用?:全部类对象及其类本身共享(如:如果new了两个Person()对象,这两个对象和这个类本身找静态变量是同一份静态空间)

(5)由于静态元素区加载的时候,有可能没有创建对象,所以可以通过类名字直接访问静态变量(如:Person.age,age是static变量)

(6)可以理解为静态元素不属于任何一个对象,静态变量是属于类的

(7)我们的内存管理机制:栈内存创建开始用完及时回收,堆内存通过GC回收,静态元素区GC无法管理(可以粗暴的认为常驻内存)

3、静态元素的关系:

(1)非静态成员(堆内存的对象里,有很多份)中可以访问静态成员(存在一个单独的区域,静态区,就一份)

(2)静态成员中可以访问静态成员(都存在静态区,如:静态方法访问静态变量)

(3)静态成员中不可以访问非静态成员(从个数上来说,静态成员访问非静态成员,如果非静态成员有两个相同的名字这时,静态成员无法确定到底是哪个比如两个对象中name;一个出发访问一堆相同名字的东西,说不清)(静态元素属于类,非静态成员属于对象自己)

(4)静态元素中不可以出现this 或 super关键字的(因为静态元素属于类)Java基础课的中下基础课02_第4张图片

package static_;

public class Person {
    public String name;
    public static int age; //斜体了

    static {
        System.out.println("我是静态块");
    }

    public Person() {
        System.out.println("我是Person的构造方法");
    }
    //test方法是个非静态方法,存在堆内存,非静态成员中可以访问静态成员
    public void test() {
        System.out.println("我是普通方法" + Person.age);
    }
    //静态方法和静态块都在静态区域
    public static void testStatic() {
        System.out.println("我是静态方法");
    }

    public static void main(String[] args) {
        //加载类模板
        Person person01 = new Person();
        person01.name = "红烧肉";
        person01.age = 18;
        Person person02 = new Person();
        person02.name = "五花肉";
        person02.age = 16;
        //输出
        System.out.println("p1的名字:" + person01.name + " p1的年龄:" + person01.age);//age=16
        System.out.println("p2的名字:" + person01.name + " p2的年龄:" + person02.age);//age=16
        /*
            本来是两个不同的空间(new了两次,两个空间),为什么年龄会一样?
            因为第二个对象使用的age是第一个对象的age,咱们new了两个空间,但是输出同样的age,所以第一个对象的age,也就是说在同一个类中静态空间是同一个
              和第二个对象的age不在对象中,因为static,所以它是有单独的一个专门放置static的空间
        */
    }

}

13.2 静态常量的应用场景

(1)小任务:按照买书人的身份做相应的折扣

1、按照买书人的身份做相应的折扣

(1)书店内部人员 管理员 5折

(2)书店的VIP VIP会员 8折

(3)普通路人 普通 全价

2、静态常量的应用场景:增强程序的可读性,static final 属性 = 0;

package bookstore;

/**书店类*/
public class BookStore {

    private static final int BOOKSTROE_ADMIN = 0;
    private static final int BOOKSTROE_VIP = 1;
    private static final int BOOKSTROE_NORMAL = 2;

    //描述一个书店买书打折的计算方法
    public void buyBook(float price, int identity) { //买书方法
        /*
            为了正确代码的可读性,我们如果把case 0: 换为一眼就理解的意思,但是还不能让其内容改变,这时就利用到了final,
            但是(属性需要对象点才能用)final需要经过对象.才能用(如果这个admin属性不在这个类中,在其他类,还需要创建
            一个对象,然后对象.属性才可以调用,太麻烦),所以用到了静态属性(直接通过类名字,类.静态属性)
        */
        switch (identity) {
            case BookStore.BOOKSTROE_ADMIN: //管理员
                System.out.println("尊敬的书店管理员,您购买的图书应付款" + price*0.5);
                break;
            case BookStore.BOOKSTROE_VIP: //VIP
                System.out.println("尊敬的VIP客户,您购买的图书应付款" + price*0.8);
                break;
            case BookStore.BOOKSTROE_NORMAL: //普通用户
                System.out.println("尊敬的普通客户,您购买的图书应付款" + price);
                break;
            default:
                System.out.println("尊敬的!检查不到您的信息,无法进行购买");
        }
    }
}
package bookstore;

import java.util.Scanner;

public class TestMain {
    public static void main(String[] args) {
        BookStore bookStore = new BookStore();
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入图书金额");
        float price = scanner.nextFloat();
        System.out.println("请出示您的身份");
        int identity = scanner.nextInt();
        bookStore.buyBook(price, identity);
    }
}

十四、修饰符之应用(单例模式)

14.1 设计模式

1、设计模式:

(1)设计模式不是知识点

(2)设计模式是一种设计经验的总结

(3)设计模式用来解决某些场景下的某一类问题------> 通用的解决方案

(4)有了设计模式之后,可以让代码更容易被理解,确保了复用性、可靠性、可扩展性

2、设计模式分为三类:(一共32种)

(1)创建型模式(5种)-----用于解决对象创建的过程(单例模式、工厂方法模式、抽象工程模式、创造者模式、原型模式)

(2)结构型模式(7种)-----把类或对象通过某种形式结合在一起,构成某种复杂或合理的结构(适配器模式、修饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式)

(3)行为型模式(11种)-----用来解决类或对象之间的交互,更合理的优化类或对象之间的关系(观察者模式、策略模式、模板模式、责任链模式、解析器模式、迭代子模式、命令模式、状体模式、备忘录、访问者模式、中介者模式)

14.3 单例模式(Singleton)

(1)单例模式应用在哪?

1、单例模式:一个类只能创建一个对象,有效的减少内存占用空间

(1)假如设计一个系统-----百度,百度里面有---->搜索引擎,那这个搜索引擎实际上就是百度的一个方法;

(2)首先public class BaiDu(){ public void 搜索(String keyword){ } },那么创建完搜索引擎的方法之后,是不能使用的,如果你想使用,先去创建一个BadiDu的对象,就是new BaiDu();然后通过----对象.搜索方法,才可以使用;

(3)假如同一时间有很多很多很多人在使用这个系统,那么这么多人使用,也就是说要创建很多个对象,那么这个对象会在哪呢?会在百度的服务器中,因此这样就会很耗费内存空间,导致服务器不够使用

(4)那么创建对象的目的是什么?其实就是为了调用搜索引擎的这个方法;那么对象在哪产生的?是在堆内存中产生的,方法是在栈内存中是个临时的空间,那么几千万人执行的是临时空间,执行完毕后就销毁了;那么对象空间,如果一直不回收,就会一直存在堆空间;

(5)那么对象我能不能指创建一次?如果只创建一个,那么堆内存中就只开辟一个空间,调用一次方法,就临时执行一次,然后销毁;这个就是单例模式体现的价值

(2)对象的加载:

1、饿汉式(立即加载) :

(1)对象启动时就加载了,不会产生对象没有就拿来使用的问题(不会产生空指针异常),但是启动项目加载的对象过多,有些还没有使用,产生服务器承载压力的问题

(2)咱们下面写的就是这种饿汉式(private static SingleTon single = new SingleTon() ),他是给静态属性直接赋值了,直接初始化了,上来就加载了

2、懒汉式(延迟加载) :

(1)对象什么时候用到了,才会加载,可能会由于没有操作好,导致异常(很严谨),启动项目时候只有需要的加载,不需要的还没有创建,不会浪费空间

3、生命周期托管(单例对象别人帮我们处理):对象加载过程交给别人

(3)单例模式的实现:

1、私有的构造方法

2、私有的静态的当前类对象作为属性;private static SingleTon single = new SingleTon();

3、公有的静态的方法返回当前类对象;public static SingleTon getSingleTon() { return single; }

(4)单例模式的形成

1、通过设计,让这个类只能创建一个对象

2、每一个类都有默认的无参构造方式

3、每次通过 new SingleTon() 创建对象,就会调用构造方法,一调用就需要创建一个新的对象,肯定不能保证一个

4、对象,为什么能new的出来呢?

(1)是因为我在调用构造方法,即使我们不创建构造方法,每一个类也有默认的构造方法

5、为什么能调到构造方法呢?

(1)是因为构造方法是public共有的,在外面(其他类)可以随意访问,所以我们要从源头开始解决,就是不能让他们随便调用构造方法

6、如何让其他类不能随意创建对象?

(1)让构造方法变成私有的,当变为私有的,其他类(外面的)就不能访问了,其他类也不能new了,这个时候只有本类可以访问,但是我们要的是创建一个对象,并不是一个都没有,还要创建一个,当且只能创建一个对象(单例对象)

(2)单例对象,不是一个都没有,所以需要在本类中的某个成员位置上创建唯一的一个对象(每个类都有四个成员:属性、方法、构造方法、块),所以我们得知需要在类中的成员中写一行new SingleTon(),在类的成员中创建一个对象。

7、那么只创建一个对象,这一个对象应该创建在哪?

前提:每个类都有四个成员:属性、方法、构造方法、块,所以我们得知需要在类中的成员中写一行new SingleTon(),在类的成员中创建一个对象。

(1)构造方法:不行----私有的

(2)块:不行----没有返回值,创建了对象也无法给别人使用;在块中创建对象,要让其他类使用,就必须返回这个对象

(3)方法:不行----不符合只能创建一个对象;有返回值,但是你调用一次方法就new一次对象,你在调用一次就在new一个,保证不了唯一,还是没有实现单例模式

(4)属性:在当前类中存在一个私有的静态属性,这个属性的类型就是当前类的类型;

8、提供一个获取单个对象的方法给用户,返回值:将对象返回回去

package singleton;

public class SingleTon {
    //1.让构造方法变成私有的---保证外面不能随便创建对象,
    private SingleTon() {};

    //2.在当前类中存在一个私有的静态属性,这个属性是当前类类型的
    /*
        public SingleTon single = new SingleTon();
        不能这么创建,因为空间内部所有的成员都加载完毕后,这个对象才算创建成功
        堆内存机制:先加载new Singleton这个对象,然后加载这个对象中的属性和方法,但是等到加载到属性的时候,single又被
        赋予了一个他自己的一个对象(相当于:套娃),所以报错栈内存溢出,StackOverflowError(开辟空间的时候,调用的是构
        造方法,构造方法在栈内存的临时空间)(开辟空间的时候就会自动调用构造方法)所以他是一直在调用构造方法
        所以如果想让他产生一次,而不是这种套娃的形式,就用到了static,为了保证唯一性
        为什么是静态?是因为确保唯一性,为什么放在属性里面?因为前三个不行
    */
    //饿汉式
    private static SingleTon single = new SingleTon(); //这个静态变量single永远指向这个new SingleTon对象

    //3.提供一个获取单个对象的方法给用户,加这个方法就是为了:让这个getSingleTon方法,在初始化的时候,就直接加载,然后我们
    //  通过类名直接访问
    public static SingleTon getSingleTon() { //static不用创建对象,就能调用方法,他是通过类名字
        return single; //将对象返回
    }

    //下面是懒汉式,上面是饿汉式
    private static SingleTon single02;
    public static SingleTon getSingleTon02() {
        if (single02 == null) {
            single02 = new SingleTon(); //延迟加载的方式 懒汉式
        }
        return single02;
    }

}
//饿汉式
public class SingleTon {
	private SingleTon() {};
 	private static SingleTon single = new SingleTon(); 
 	public static SingleTon getSingleTon() { 
        return single; 
    }
}
//懒汉式
public class SingleTon {
	private static SingleTon single02;
    public static SingleTon getSingleTon02() {
        if (single02 == null) {
            single02 = new SingleTon(); //延迟加载的方式 懒汉式
        }
        return single02;
    }
}
package singleton;

public class TestMain {
    public static void main(String[] args) {
//        SingleTon singleTon = new SingleTon();
        //在创建对象时发生改变,以前是我自己创建对象之后调用的,现在就通过类名字去访问属性
        SingleTon s01 = SingleTon.getSingleTon(); //我们不操作属性,操作的是方法
        SingleTon s02 = SingleTon.getSingleTon();
        System.out.println(s01==s02);//true   ==比较的是地址
        System.out.println(s01.equals(s02));// Object类 默认也是比地址
    }
}

十五、类的加载顺序 + 抽象类 +接口

15.1 类的加载顺序

1、加载类的过程-----先出静态后出非静态,先出父后出子

2、注意类加载和内存机制是不一样的,不要搞混了!

Java基础课的中下基础课02_第5张图片

Java基础课的中下基础课02_第6张图片

package class_load;

public class Test {
    public static void main(String[] args) {
        /*
        加载类的过程---先出静态后出非静态,先出父后出子
            Animal类加载好了之后,Animal静态空间立即加载,然后在回头加载子类Person
            ,Person类加载完后,立即加载Person静态空间
            new Person过程:
            1.加载父类
            2.父类会产生自己的静态空间(属性、方法、块、执行块),然后会去执行输出静态块中的代码
            3.加载子类
            4.子类会产生自己的静态空间(属性、方法、块、执行块),然后去执行静态块
            第四步完成后才算类加载完成,然后去加载对象
            5.开辟对象空间
            6.加载父类的非静态成员(属性、方法、块、构造方法)
            7.执行块,执行构造方法
            8.加载子类的非静态成员(属性、方法、块、构造方法)
            9.执行块,执行子类构造方法
            10.将对象空间的地址引用给变量存储
        */
        //
        new Person();

    }
}
package class_load;

public class Animal {
    public String test = "AnmimalField";
    public static String testStatic = "AnimalStaticField";

    //构造方法
    public Animal() {
        System.out.println("我是animal中默认无参数的构造方法");
    }

    {
        this.test();
        System.out.println("我是animal中的普通程序块" + test);
    }

    static {
        Animal.testStatic();
        System.out.println("我是animal中的静态代码块" + testStatic);
    }

    public void test() {
        System.out.println("我是animal类中的普通方法");
    }
    public static void testStatic() {
        System.out.println("我是animal类中的静态方法");
    }
}
package class_load;

public class Person extends Animal{
    public String test = "PersonField";
    public static String testStatic = "PersonStaticField";

    //构造方法
    public Person() {
        System.out.println("我是Person中默认无参数的构造方法");
    }

    {
        this.testPerson();
        System.out.println("我是Person中的普通程序块" + test);
    }

    static {
        Person.testStatic();
        System.out.println("我是Person中的静态代码块" + testStatic);
    }

    public void testPerson() {
        System.out.println("我是Person类中的普通方法");
    }
    public static void testStatic() {
        System.out.println("我是Person类中的静态方法");
    }
}

15.2 特征修饰符之native本地的

(1)java源代码中看到的native实际上是底层的一下执行内存的操作,有别调用其他的编程语言C++、C所以他是不让咱们看见源代码的;这里就不过多介绍了。

15.3 特征修饰符之abstract抽象类

1、abstract抽象的--------很不具体,没有具体的执行过程,只是个概念

2、abstract抽象的可以修饰什么?

(1)修饰方法:用abstract修饰符修饰的方法,只有方法的结构

修饰符 特征修饰符 返回值类型 方法名() ;如:pubic abstract void eat();),没有方法执行体(没有{})叫做抽象方法,当然注意native修饰的方法虽然也没有方法体,但是它不是抽象方法,只是执行的过程是其他语言写的,看不见;

(2)修饰类:用abstract修饰符修饰的类,叫做抽象类,如:public abstract class Person{}

3、修饰后的特点:

(1)抽象类中必须有抽象方法么?不是必须含有抽象方法,只是通常会有

(2)抽象方法必须放在抽象类中嘛?目前来看必须方法抽象类中或接口中,普通类是不允许含有抽象方法的

4、研究一下什么叫抽象类,抽象类有什么特点?(通常用来描述事物,还不是很具体)

(1)类中的成员:抽象类和一般类的区别就在于它可以写抽象方法,一般类不可以

(2)属性:可以含有一般的属性,也可以含有private、static、final等等(和一般的类没什么区别)

(3)方法:可以含有一般的属性,也可以含有private、static、final等等。注意抽象类中允许含有抽象方法(只有方法结构, 没有方法执行体)

(4)块:可以含有一般的程序块,也可以含有static程序块

(5)构造方法:可以含有构造方法,包括重载

5、类如何使用:

(1)抽象类含有构造方法,但是我们不能通过调用构造方法直接创建对象;

(2)一般类想创建对象可以直接new一个对象,然后默认去调用无参构造方法,但是抽象类不能直接new来创建对象,抽象类只能通过子类继承来做事情

(3)为什么不让我们调用呢?

因为你抽象类中有一个抽象方法(只有结构体,没有方法体,没有方法体就没法干活,我们可以当他是个残次品,所以没法用这个残次品干事情)(父类是抽象类)

(4)既然不能调用为什么还有呢?

因为必须通过子类来继承做事情,但是子类在创建对象的同时会产生父类的对象空间,来存储东西,所以子类会间接调用父类的构造方法,所以必须有(父类是抽象类)

6、类和类的关系:

(1)抽象类能直接单继承抽象类? ------可以

(2)抽象类能直接单继承具体类?-------可以,就是不怎么合理,一般在开发的时候都是通过抽象的描述一件事情,在上升到具体

(3)具体类能直接单继承抽象类?-------不可以,(将子类中的抽象方法具体化,就是需要让具体类将抽象的方法重写,添加具体执行过程,否则该子类也变成抽象类 )

7、抽象类中能不能没有抽象方法?能不能全部都是具体成员?------可以,但是如果没有抽象方法,就和一般类一样了,就没必要写抽象 类。

8、抽象类中能不能没有具体成员?能不能全部都是抽象方法?------可以,抽象类抽象到极致了,就发生了一个质的变化,它叫做接口了,接口可以理解为抽象类抽象到极致了,还是一个类的结构,只是名字变了,不要class修饰了,改用interface修饰了

15.4 接口

1、什么是接口?

(1)接口也是一个类的结构,只不过,用interface修饰了,替换原有的class了

2、类中的成员

(1)属性:不能含有一般属性(public、protected、默认的、私有的),只能含有公有的静态的常量 public static final(final需要赋初值)

(2)方法:不能含有一般方法,只能含有公有的抽象方法(1.8版本以后可以用default来修饰具体方法)

(3)块:不能含有一般程序块,也不能含有static块(快本身就是具体的,接口中不让具体的)

(4)构造方法:不能含有构造方法

3、如何使用?

(1) 接口没有构造方法,不能创建对象,只能通过子类多实现(implements)来做事

public class A implements B,C,D{

}

4、接口与接口,接口与类之间的关系

(1)接口不能继承其他的类(抽象类、具体类都不行)

(2)抽象类------直接多实现------接口,可以

(3)具体类------直接多实现------接口,不可以(必须将接口中的抽象方法具体化,否则该子类(具体类)自己变成抽象类)

(4)接口----多-继承------接口,可以直接多实现Java基础课的中下基础课02_第7张图片

十六、LinkedBox封装

16.1 回顾前面的ArrayBox封装

1、设计一个类:ArrayBox,目的是:数组有些地方不是很好,如果频繁添加或删除元素,但是数组长度是固定的,就无法实现,所以创建一个ArrayBox进行代替。

2、实际上ArrayBox是底层代码,我们后期学到框架了,就可以直接用了,知道ArrayBox的原理,可以用的更灵活。

(1)设计add方法,用来将用户给定的element存起来。参数是需要存起来的元素element,返回值是否存储成功boolean

(2)设计get方法,用来获取给定位置的元素。参数是索引位置index,返回值是取得的元素

(3)设计remove方法,用来删除给定位置的元素。参数是索引位置index,返回值是删除掉的那个元素

(4)设计一个自己的属性,用来存放真实数据的,属性数组名为elementData

(5)设计一个自己的属性,用来记录数组内存储的有效元素个数,属性名size

(6)用户想存元素,首先调用的就是add方法,用add方法将element存入到elementData数组中,于是我们首先就要知道elementData这个数组空间是否够用(①调用ensureCapacityInternal方法判断),④如果够用就直接将新来的元素存入数组中

(7)设计一个自己的方法ensureCapacityInternal,用于确保数组的内部容量是否够用。参数是最小容量长度minCapacity,无返回值。在ensureCapacityInternal方法中判断最小容量minCapacity长度是否比原数组elementData空间(长度)大(minCapacity - elementData.length > 0),如果大就计算需要扩多大的长度(②调用grow方法计算需要扩多大的长度

(8)设计grow方法,用来计算扩大后的新数组长度。参数是最小容量minCapacity长度。在grow方法中将原数组长度进行扩容到1.5倍变为新的数组的长度newCapacity,然后比较新数组的长度是否比所需数组的长度小(newCapacity - minCapacity < 0),如果比所需数组的长度小,就直接按照所需数组的长度作为新数组长度newCapacity = minCapacity;最后(③调用copyOf方法,并将copyOf返回的新数组给原数组elementData进行存储)创建新数组,将原数组中的所有元素移入新数组中

(9)设计copyOf方法,负责将原数组中的元素移入新数组中。参数是原数组和新数组的长度,返回创建的新数组。首先按照提供的新数组长度创建一个新数组,然后将原数组中的元素按照位置移入新数组中,再将移入后的新数组返回

(10)先看大写 一~八 ,在看 1~4 ,再看①

(11)能存就能取,get方法中,检查给定的index是否合法

(12)设计一个自己的rangeCheck方法,检查给定index是否合法,参数是index。如果index小于零或给定的范围大于等于有效元素就错误,然后自定义一个异常(⑤调用异常信息),⑥如果无异常找到index对应的位置的元素将其返回

(13)设计BoxIndexOutOfBoundException类,用于定义的异常,让BoxIndexOutOfBoundException类继承RuntimeException,然后设计构造方法来调用父类的异常

(14)能存就能取能删,remove方法中,检查index(⑦调用rangecheck方法检查),⑧如果合法,将index位置的元素保留起来oldValue,并将旧数据返回return oldValue,返回以后中间想删除一个元素,⑨通过用循环将后面的值往前移,然后将后面多余的数去掉size-1,从index位置开始至size-1结束,后面元素一次往前覆盖⑩然后将最后的元素删除,让size减少一个记录

  1. 设计getSize方法,用来获取size有效的个数 ,因为size是私有的

  2. 设计一个构造方法ArrayBox(),用户自己设置数组长度也行

  3. 设计一个构造方法ArrayBox(int capacity),如果不设置,咱们就自动给定一个默认数组的长度

  4. 设计一个静态常量DEFAULT_CAPACITY,用来自动给定的默认数组长度

最终简介版:

  1. 设计add方法,参数是想要存入的元素,返回值boolean是告诉用户是否成功

    1. 首先确保elementData空间是否够用

      1. 设计ensureCapacityInternal方法,参数是最小容量,返回值void

      2. 如果不够用直接计算需要扩多大容量的长度

        1. 设计grow方法,参数是最小容量

        2. 将长度扩大到原来的1.5倍,比较1.5倍是否大于你所需的长度

        3. 如果大于就直接按照所需长度,就要所需的长度

        4. 如果小于就将原数组的元素依次移入新数组

          1. 设计copyOf方法,参数是原数组,和新数组的长度,返回值是新数组

            1. 按照新数组长度开辟一个新的数组,用遍历将原数组中元素移入新数组中,并返回

    2. 如果够用就直接将元素存入到elementData数组中,并返回true

  2. 设计get方法,参数index索引位置,返回值获得到的元素

    1. 检查index范围是否合法

      1. 设计rangeCheck方法,参数是索引的位置,用于检测给定的index是否合法

      2. 如果不合法就报错

    2. 合法,将index位置的元素从数组中取出,并返回

  3. 设计remove方法,参数是索引位置,返回值是删除后的数组

    1. 检查index是否合法

      1. 设计rangeCheck方法,参数是索引的位置,用于检测给定的index是否合法

      2. 如果不合法就报错

    2. 合法,获得index位置上的元素-----保存起来

    3. 从index到size-1的位置,将后面元素逐一前移覆盖

    4. 最后有效的那个元素删掉了,所以--size

    5. 保留起来的旧数组返回

  4. 设计getSize方法,用于获得size有效元素的个数

public class ArrayBox {
    //四、设定一个自己的属性,用来存放真实数据
    private int[] elementData;
    //五、设定一个自己的属性,用来记录数组内存储的有效元素个数
    private int size;
    //十二、设计一个静态常量,用来自动给定的默认数组长度
    private static final int DEFAULT_CAPACITY = 10;
​
    //十一、构造方法重载
    //默认给定的数组长度
    public ArrayBox() {
        elementData = new int[DEFAULT_CAPACITY];
    }
    //用户自己创建的
    public ArrayBox(int capacity) {
        elementData = new int[capacity];
    }
​
    //六02、数组得内部容量是否够,参数是最小容量长度
    private void ensureCapacityInternal(int minCapacity) {
        if (minCapacity - elementData.length > 0) {
            //七01、如果大就计算需要扩多大的长度
            this.grow(minCapacity);//②八布后调用,参数是最小容量的长度
        }
    }
    //七02、计算扩多大的长度,也就是新数组的长度,参数是最小容量的长度(也是所需数组的长度)
    public void grow(int minCapacity) {
        //1.设置旧数组的长度,用于扩大1.5倍
        int oldArray = elementData.length;
        //2.将长度扩大到原数组长度的1.5倍,变为新数组的长度
        int newCapacity = oldArray + (oldArray << 1);
        //3.比较新数组的长度(原数组1.5倍后的数组)是否比所需的长度大
        if (newCapacity - minCapacity > 0) {
            //4、如果小就直接按照所需数组的长度作为新数组的长度
            newCapacity = minCapacity;
        }
        //八01、用新数组的长度给新数组定义空间,将原数组中的所有元素移入新数组中
        elementData = this.copyOf(elementData, newCapacity);//③将返回的新数组返回
    }
    //八02、创建新数组,并将原数组中的所有元素移入到新数组中,参数是原数组和新数组的长度
    public int[] copyOf(int[] oldArray, int newCapacity) {
        //1.按照提供的新数组长度创建一个新数组
        int[] newArray = new int[newCapacity];
        //2.将原数组中的元素按照位置一次移入新数组中
        for (int i = 0; i < oldArray.length; i++) {
            newArray[i] = oldArray[i];
        }
        //3.将移入后的新数组返回
        return newArray;
    }
    //九02、检查给定index是否合法,不合法调用异常
    private void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            //1.定义异常来说明问题
            throw new BoxIndexOutOfBoundException("Index=" + index + " size=" + size);
        }
    }
​
​
    /*======================================================*/
    //一、用来将用户给定的element存起来,参数是需要存起来的元素,返回值是存储成功
    public boolean add(int element) {
        //六01、用户首先进入add,将元素存入到elementData数组中,首先确保elementData空间是否够
        this.ensureCapacityInternal(size + 1);//①八布之后开始调用,参数为有效元素 + 1
        //④上面一行代码如果执行完毕,就证明elementData的数组肯定够用,那就直接将新来的element元素存入数组中,并让size多记录一个
        elementData[size++] = element;
        //返回true告诉用户存储元素成功
        return true;
​
    }
    //二、用来获取给定位置的元素,参数是索引位置,返回值是取得的元素
    public int get(int index) {
        //九01、检查给定index是否合法
        this.rangeCheck(index);//⑤调用异常信息
        //⑥如果上面代码可以执行,证明index合法
        //则找到index 对应位置的元素,将其返回
        return elementData[index];
    }
    //三、用来删除给定位置的元素,参数是索引位置,返回值是删掉的那个元素
    public int remove(int index) {
        //⑦检查index
        this.rangeCheck(index);
        //⑧上述代码能执行,证明index合法
        //找到index位置的元素保留起来,留给用户
        int oldValue = elementData[index];
        //1 2 3 4 5 6 size=6
        // ------> 1 2 3 4 6 6 elementData[5]=0 size-1
        // ------> 1 2 4 5 6 0
        //⑨用循环将后面的值往前移,然后将后面多余的数去掉size-1
        for (int i = index; i < size-1; i++) {//从index位置开始至size-1结束,后面元素一次往前覆盖
            elementData[i] = elementData[i + 1];
        }
        //⑩手动将最后的元素删除,让size减少一个记录
        elementData[--size] = 0;
        //⑦将旧数据返回
        return oldValue;
    }
    //十、用来获取size有效的个数
    public int getSize() {
        return size;
    }
​
}
public class BoxIndexOutOfBoundException extends RuntimeException{
​
​
    public BoxIndexOutOfBoundException(){}
    public BoxIndexOutOfBoundException(String msg) {
        super(msg);
    }
}

(1)为什么写ArrayBox,它的优点和缺点

1、数组的优点和缺点:

(1)优点:利用数组存储一组元素,长度固定,好处在于创建后不会浪费内存

(2)缺点:长度不变,如果添加删除时,个数的改变很麻烦

2、ArrayBox优点和缺点:

(1)优点:长度看似可以改变,在于添加删除出时不需要纠结长度变化的问题,适合遍历

(2)缺点:插入和删除是很麻烦的,效率低,需要不断的移入元素的位置进行覆盖

16.2 LinkedBox封装

1、LinkedBox优点和缺点:

(1)优点:长度看似可以改变,解决了插入和删除效率低的问题(很适合)

(2)缺点:不适合遍历

2、为什么有ArrayBox还要设计一个LinkedBox?

(1)我们为了更新ArrayBox,然后创建一个新的LinkedBox,为了方便用户使用,需要让这两个Box的方法...等等都一样,这样用户在使用第二个Box时才更加方便上手。这时就引出了接口来进行定义规则。

3、接口:----定义规则让方法名一样,为了让用户更方便了解。

package test;
//接口定义规则
public interface Box {
​
    public boolean add(int element);
​
    public int get(int index);
​
    public int remove(int index);
​
    public int getSize();
}

4、LinkedBox:

(1)采用双向链表,这种结构虽然地址不连续,但是可以通过一个一个的结构找到下一个,所以可以存数据,无论添加还是删除只需要动三根线;但是不便于遍历,因为每一个地址不连续,遍历需要一个一个找很慢。遍历最好用ArrayList;

(2)链表结构:双向链表:A<---> B <--->C <--->D

①会发现每一个结构都是一样的。从红线开始看,每一个对象的下都指向下一个对象的上,也就是说每一个对象的尾节点都存储这下一个对象的地址。(绿线也是,每一个对象的首节点都存储这上一个对象的地址)Java基础课的中下基础课02_第8张图片

(3)如果我们想实现双向链表的结构,就先要创建一个与双向链表结构一样的对象,每一个对象我们称之为节点

(4)需要定义一个自己的结构,创建一个节点类Node,有三个自己的属性,1. 上一个node对象的地址 2. 存当前的数据 3.下一个node对象的地址

有了这个结构以后,这个Node以后,你每一次new一个这个的对象,跟咱们上面的图是一样的

package test;
​
public class Node {
​
    public Node prev; //上一个node对象的地址
    public int item;  //存当前的数据
    public Node next; //下一个node对象的地址
​
    //为了赋值方便创建构造方法
    public Node(){}
    public Node(Node prev, int item, Node next) {
        this.prev = prev;
        this.item = item;
        this.next = next;
    }
}

(5)如果想存数据必须知道头节点和尾节点,在LinkedBox创建三个自己的属性,记录头尾节点、有效元素个数、尾节点;

①有了记录用户该怎么用?是不是应该创建一个对象,然后调用相关方法进行存等一系列操作,所以我们是不是在存之前应该将element存入一个新的Node对象里,然后在添加至链表尾端,我肯定找别人帮我做linkLast

②设计一个A同学,负责将元素添加在新的node里,挂在链表的尾端linkLast方法,A同学需要数据才能进行添加;

  1. 如果想存的话,往哪存?肯定是挂在尾端,那么挂在尾端,先知道尾端在哪?所以先获取链表的尾节点

  2. 尾巴找到之后,就想把数据挂在尾节点,那么挂数据,是不是应该先将数据包装起来,那么包装?怎么包装?创建一个新的Node对象(新节点)将数据包装起来

  3. 这个新节点挂在尾巴上,那么这个新节点的首节点一定是上一个节点的尾节点

  4. 如果连上以后,这个新节点是不是就变成尾节点了,尾节点为null,因为是新创建的节点;为空是为了等待下一个节点的连接,所以要将新节点设置为尾节点,用于等待下一个节点的连接

  5. 但是想一个问题,就是有可能你上面创建的node对象前面没有节点,也就是说有可能你这新节点就是第一个节点,所以要做一个严谨的判断Java基础课的中下基础课02_第9张图片

package test;
​
import util.BoxIndexOutOfBoundException;
​
public class LinkedBox implements Box {
    //如果想存数据必须知道头节点和尾节点
    private Node first; //记录首节点
    private Node last;  //记录尾节点
    private int size;   //记录有效元素的个数
​
    //设计一个A同学,负责将元素添加在新的node里,挂在链表的尾端
    private void linkLast(int element) {
        //1.如果想挂在尾部,就先找到那个尾节点,获取链表的尾节点
        Node l = last;
        //2.找到尾部之后,就要创建一个新的node对象(新节点),将新数据包装起来,然后把这个数据挂上
        //3.下----上数据下,这样才能连接,这个新节点挂在尾巴上,那么这个新节点的首节点一定是上一个节点的尾节点
        Node newNode = new Node(l, element, null);
         //4.尾节点为null,因为是新创建的节点,为空是为了等待下一个节点的连接,所以要将新节点设置为尾节点,用于等待下一个节点的连接
        last = newNode;
        //5.有可能你上面创建的node对象前面没有节点,也就是说有可能你这新节点就是第一个节点,所以严谨的判断
        if (l == null) { //如果原来的尾节点为null,证明这个链表没有使用过,如果没有使用过就说明我们创建的新节点就是第一个节点,也就是上面属性定义的首节点
            first = newNode;
        } else { //如果不是空,证明原来用过,刚才已经将新节点连接在尾节点last之后了new Node(l, element, null);,如果原来用过,由于双向链表,你光连接后边肯定不行,所以原来尾节点的下一个节点next存储着这个新节点newNode的对象行连接
            //因为双链表,所以新节点的前一个就是尾节点,如果原来有过尾节点的化
            //那么尾节点的下一个就是新节点,就都连这了,双链表你连我,我也得连你
            l.next = newNode;
        }
        //完事之后,让有效元素个数增加一个
        size++;
    }
    //设计B同学,负责检查index
    private void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            throw new BoxIndexOutOfBoundException("Index=" + index + " size=" + size);
        }
    }
    //设计C同学,负责找寻给定的index位置的node对象
    private Node node(int index) {
        Node targetNode;//用来存储找到的当前那个目标
        //判断index范围是在前半部分 还是在后半部分
        if (index < (size >> 1)) {//从前往后找寻比较块
            targetNode = first;
            for (int i = 0; i < index; i++) {
                targetNode = targetNode.next;
            }
        } else { //从后往前找
            targetNode = last;
            for (int i = size - 1; i > index; i--) {//size是个数,索引号正好少一个
                targetNode = targetNode.prev;
            }
        }
        return targetNode;
    }
    //设计D,负责将给定的node节点对象删除,并且保留数据
    private int unlink(Node targetNode) {
        //获取当前node的item信息
        int oldValue = targetNode.item;
        //当前的前一个
        Node prev = targetNode.prev;
        //当前的下一个
        Node next = targetNode.next;
        //删除节点对象
        if (prev == null) {//当前node就是一个
            first = next;
        } else {
            prev.next = next;
            targetNode.prev = null;
        }
        if (next == null) {//当前node就是最后一个
            last = prev;
        } else  {
            next.prev = prev;
            targetNode.next = null;
        }
        //让有效元素减少一个
        size--;
        return oldValue;
    }
​
​
​
//==================================================================
    //数据结构
    @Override
    public boolean add(int element) {
        //将element存入一个新的Node对象里,添加至链表的尾端,设计A同学
        this.linkLast(element);
        //告知添加成功
        return true;
    }
​
    @Override
    public int get(int index) {
        //B检查index是否合法
        this.rangeCheck(index);
        //C找寻index对应位置的那个node对象,将对象中的数据取出来
        Node targetNode = this.node(index);
        //返回找到的node对象内的数据
        return targetNode.item;
    }
​
    @Override
    public int remove(int index) {
        //B检查范围是否合法
        this.rangeCheck(index);
        //找到index对应的node
        Node targetNode = this.node(index);
        //设计D负责删除当前的目标节点,同时让它帮我们返回oldValue
        int oldValue = this.unlink(targetNode);
        return oldValue;
    }
​
    @Override
    public int getSize() {
        return size;
    }
}
package util;
​
public class BoxIndexOutOfBoundException extends RuntimeException{
​
    public BoxIndexOutOfBoundException(){}
    public BoxIndexOutOfBoundException(String msg) {
        super(msg);
    }
​
}

十七、 多态

17.1 多态

1、java面向对象的四大特性: 继承、封装、多态、(抽象);

2、多态 :

(1)同一个对象,体现出来的多种不同形态(身份),将一种行为表现出不同的效果;

(2)想要实现多态的效果,需要先有继承关系

3、多态的向上转型:Person p = new Teacher() ;父类类型 引用名 = new 子类类型()

(1)父类类型的引用指向子类的对象

(2)该引用只能调用父类中定义的属性和方法

(3)执行结果:如果调用属性,执行父类的;如果调用方法,看子类是否重写

(4)如果子类中将父类的方法重写,那么调用方法后执行的结果是子类重写之后的那个结果

(5)如果父类与子类有同名的属性,调用父类的属性

(6)如果父类与子类有同名的方法,执行子类重写之后的方法

(7)如果想要调用子类中独有的成员,将身份还原回去才可以,需要强制类型转化,引出向下转型(造型)

4、多态的向下转型:Teacher t = (Teacher)p;子类类型 引用名 = (子类类型)父类引用;

(1)只能强转父类的引用,不能强转父类的对象

(2)要求父类的引用必须指向的是当前目标类型的对象

(3)可以调用子类类型中的所有成员

(4)若需要转换的类型与真实对象的类型不匹配,会产生一个运行时异常ClassCastException

(5)为了避免转型时产生的问题,可以利用instanceof进行判断比较,比较前面对象和后面类型是否匹配

5、注意:不是一个分支上的不行瞎造型

(1)不能将Student造成Teacher,不能将Teacher造成Pig,不能将Person造成Pig

(2)要满足对象属于(instanceof)这个类的子类,就可以Java基础课的中下基础课02_第10张图片

你可能感兴趣的:(Java基础,java,开发语言,intellij-idea)