目录
九、类的关系之继承
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 多态
1、is-a (泛化) 继承 实现
2、has-a (包含)组合 聚合 关联;
(1)组合:整体和部分的关系,就好比人和心脏的关系,整体要出现都出现,要灭亡都灭亡 ,不可以分割
(2)聚合:整体和部分的关系,就好比汽车和车轮子,创建时有可能是分开的,汽车没轮子就跑不了
(3)关联:整体和部分的关系,就好比人有电脑,是因为后来因为某种原因形成在一起的,可以分割
(4)从Java程序来描述包含的关系,就是一个类的对象当做另一个类的属性来存储的
3、use-a (依赖)依赖
(1)一个类的方法中使用到了另一个类的对象,方法内部new,方法传递参数
(2)不是整体和部分关系,某件事情产生了关系,临时组合在一起,这件事情一旦做完,关系即刻解散
(1)子类继承父类,通过一个关键字 enxtends
(2)子类的对象可以调用父类中的(public、protected)属性和方法,当做自己的来使用
(3)构造方法严格意义来说,不算做子类继承过来的,只是单纯的在子类调用构造方法时默认调用父类的构造方法
(4)子类可以添加自己独有的属性和方法的
(5)为什么有重写?子类从父类中继承过来的方法不能满足子类需求,可以在子类中重写(覆盖)父类的方法,更多指的是内容
(6)每一个类都有继承类,如果不写extends关键字,默认继承Object,如果写了extends则继承后面那个父类
(7)Object类非常重要,是一个引用类型的父类(都是直接或间接的继承Object),Object没有父类
(8)Java中继承是单个存在的(单继承)每一个类只能由一个继承类(在extends关键字后面只能写一个类);可以通过传递的方式实现多继承的效果,后续还会有多实现
方法重写OVERRIDE | 方法重载OVERLOAD | |
---|---|---|
类 | 产生两个继承关系的类,子类重写父类的方法 | 一个类中的一组方法 |
权限 | 子类可以大于等于父类 | 无要求 |
特征 | ①父类方法是final,子类不能重写 ②是static,子类不存在 ③abstract,子类必须重写 (子类是具体必须重写,否则子类是抽象类,可以不重写) | 无要求 |
返回值 | 子类可以<=父类 | 无要求 |
名字 | 子类与父类一致 | 一个类中的好多方法名必须一致 |
参数 | 子类与父类一致 | 每一个方法的参数必须不一致(个数、类型、顺序) |
OBJECT类中的方法 | 解释 |
---|---|
hashCode() | 将对象在内存中的地址经过计算得到一个int整数 |
equals() | 1、用来比较两个对象的内容,Object默认效果是== 2、==可以比较基本类型(比较值)可以比较引用类型(比较地址) 3、equals方法是Object类中继承过来的方法,默认效果是比较地址,如果想要改变规则,可以进行方法重写,重写后比较存储的值 |
toString() | 打印输出时将对象变化string字符串 |
getClass() | 获取对象对应类的类映射(反射) |
wait() | 让线程进入挂起等待状态,还存在方法重载 |
notify() | 让线程唤醒 |
notifyAll() | 唤醒所有 |
finalize() | 权限修饰符是protected,在对象被GC回收的时候,默认调用执行的方法 |
clone() | 权限修饰符是protected,为了克隆对象的 |
(1)this和super都是指代词,代替的是对象
(2)this代替的是当前执行方法时的那个对象,不一定是当前类的
(3)super代替的是当前执行方法时的对象的父类对象,空间内部那个
(4)this和super都能调用一般属性 和 一般方法
(5)this和super可以放置在类成员的任意位置(属性 方法 构造 块),注意调用一般方法的时候可以来回互相调用,执行可能产生问题(StackOverflowError)
(6)this和super可以调用构造方法(但是必须放在构造器的第一行),构造方法之间不能来回互相调用(编译就不好用了),但是this和super在构造方法中调用另一个类的构造方法不能同时出现在第一行
在我们类的第一行出现package关键字,如果package和import同时出现,先写package后写import,package只能有一个,impact是可以多个引入的
从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,"黑色"));
}
}
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);
}
}
/*
* 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太电脑,让学生进入机房选择一台关闭的电脑使用 思考:如果你想让机房中放一堆,只能是数组(用于存放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);
}
}
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);
}
}
}
类中的四个成员:
①属性 ---- 静态描述类的特征(变量 存值 存数据)name
②方法 ---- 动态描述类的行为(做事情)eat
③构造方法 ---- 作用是构造当前类的对象(做事情)
④程序块{}(可省略) ---- 创建对象后,每运行一次对象,就执行一次
权限修饰符 | |
---|---|
public | 公共的 |
protected | 受保护的 |
默认不写 | 默认的 |
private | 私有的 |
特征修饰符 | |
final | 最终的 或 不可改变的 |
static | 静态的 |
abstract | 抽象的 |
native | 本地的 |
transient | 瞬时的(短暂的 ----- 序列化 ) |
synchronized | 同步的 线程相关的知识 |
volatile | 不稳定的 |
1、权限修饰符能修饰什么?
(1)权限修饰符可以用来修饰 类本身 和 类中的成员(除程序块)
(2)权限修饰符用来修饰类的时候只有两个可以用(public和默认不写)
(3)权限修饰符都可以来修饰类中其他成员(除程序块)
2、权限修饰符范文范围?
PUBLIC 公共的 本类 同包 子类(当前项目中任意类的位置只要有对象都可以访问) |
---|
protected 保护的 本类 同包 子类(通过子类对象在子类范围内部访问父类,在子类的main中就访问不到) |
默认不写 默认的 本类 同步 |
private 私有的 本类 |
1、Java面向对象之封装
(1)封装:将一些数组或执行过程,进行一个包装
(2)目的:保护这些数据 或 执行过程的安全,不可以在类以外的部分直接访问,需要给属性的操作提供一些方式(方法)set和get方法
(3)方法本身就算是封装,封装了执行的过程,保护过程的安全,隐藏了执行细节,增强复用性
(4)好多的方法 和 属性 包装起来了----> 就是类
2、对属性本身的封装:
(1)属性私有(封装在类中)
(2)提供操作属性相应的方式(共有的方式)
(3)建议大家属性不要共有的-----太不安全了,最好用private,如:private int age -----> setAge getAge,通过get set获取和设置
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[];
}
}
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关键字的(因为静态元素属于类)
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的空间
*/
}
}
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);
}
}
1、设计模式:
(1)设计模式不是知识点
(2)设计模式是一种设计经验的总结
(3)设计模式用来解决某些场景下的某一类问题------> 通用的解决方案
(4)有了设计模式之后,可以让代码更容易被理解,确保了复用性、可靠性、可扩展性
2、设计模式分为三类:(一共32种)
(1)创建型模式(5种)-----用于解决对象创建的过程(单例模式、工厂方法模式、抽象工程模式、创造者模式、原型模式)
(2)结构型模式(7种)-----把类或对象通过某种形式结合在一起,构成某种复杂或合理的结构(适配器模式、修饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式)
(3)行为型模式(11种)-----用来解决类或对象之间的交互,更合理的优化类或对象之间的关系(观察者模式、策略模式、模板模式、责任链模式、解析器模式、迭代子模式、命令模式、状体模式、备忘录、访问者模式、中介者模式)
1、单例模式:一个类只能创建一个对象,有效的减少内存占用空间
(1)假如设计一个系统-----百度,百度里面有---->搜索引擎,那这个搜索引擎实际上就是百度的一个方法;
(2)首先public class BaiDu(){ public void 搜索(String keyword){ } },那么创建完搜索引擎的方法之后,是不能使用的,如果你想使用,先去创建一个BadiDu的对象,就是new BaiDu();然后通过----对象.搜索方法,才可以使用;
(3)假如同一时间有很多很多很多人在使用这个系统,那么这么多人使用,也就是说要创建很多个对象,那么这个对象会在哪呢?会在百度的服务器中,因此这样就会很耗费内存空间,导致服务器不够使用
(4)那么创建对象的目的是什么?其实就是为了调用搜索引擎的这个方法;那么对象在哪产生的?是在堆内存中产生的,方法是在栈内存中是个临时的空间,那么几千万人执行的是临时空间,执行完毕后就销毁了;那么对象空间,如果一直不回收,就会一直存在堆空间;
(5)那么对象我能不能指创建一次?如果只创建一个,那么堆内存中就只开辟一个空间,调用一次方法,就临时执行一次,然后销毁;这个就是单例模式体现的价值
1、饿汉式(立即加载) :
(1)对象启动时就加载了,不会产生对象没有就拿来使用的问题(不会产生空指针异常),但是启动项目加载的对象过多,有些还没有使用,产生服务器承载压力的问题
(2)咱们下面写的就是这种饿汉式(private static SingleTon single = new SingleTon() ),他是给静态属性直接赋值了,直接初始化了,上来就加载了
2、懒汉式(延迟加载) :
(1)对象什么时候用到了,才会加载,可能会由于没有操作好,导致异常(很严谨),启动项目时候只有需要的加载,不需要的还没有创建,不会浪费空间
3、生命周期托管(单例对象别人帮我们处理):对象加载过程交给别人
1、私有的构造方法
2、私有的静态的当前类对象作为属性;private static SingleTon single = new SingleTon();
3、公有的静态的方法返回当前类对象;public static SingleTon getSingleTon() { return single; }
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类 默认也是比地址
}
}
1、加载类的过程-----先出静态后出非静态,先出父后出子
2、注意类加载和内存机制是不一样的,不要搞混了!
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类中的静态方法");
}
}
(1)java源代码中看到的native实际上是底层的一下执行内存的操作,有别调用其他的编程语言C++、C所以他是不让咱们看见源代码的;这里就不过多介绍了。
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修饰了
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)具体类------直接多实现------接口,不可以(必须将接口中的抽象方法具体化,否则该子类(具体类)自己变成抽象类)
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减少一个记录
设计getSize方法,用来获取size有效的个数 ,因为size是私有的
设计一个构造方法ArrayBox(),用户自己设置数组长度也行
设计一个构造方法ArrayBox(int capacity),如果不设置,咱们就自动给定一个默认数组的长度
设计一个静态常量DEFAULT_CAPACITY,用来自动给定的默认数组长度
最终简介版:
设计add方法,参数是想要存入的元素,返回值boolean是告诉用户是否成功
首先确保elementData空间是否够用
设计ensureCapacityInternal方法,参数是最小容量,返回值void
如果不够用直接计算需要扩多大容量的长度
设计grow方法,参数是最小容量
将长度扩大到原来的1.5倍,比较1.5倍是否大于你所需的长度
如果大于就直接按照所需长度,就要所需的长度
如果小于就将原数组的元素依次移入新数组
设计copyOf方法,参数是原数组,和新数组的长度,返回值是新数组
按照新数组长度开辟一个新的数组,用遍历将原数组中元素移入新数组中,并返回
如果够用就直接将元素存入到elementData数组中,并返回true
设计get方法,参数index索引位置,返回值获得到的元素
检查index范围是否合法
设计rangeCheck方法,参数是索引的位置,用于检测给定的index是否合法
如果不合法就报错
合法,将index位置的元素从数组中取出,并返回
设计remove方法,参数是索引位置,返回值是删除后的数组
检查index是否合法
设计rangeCheck方法,参数是索引的位置,用于检测给定的index是否合法
如果不合法就报错
合法,获得index位置上的元素-----保存起来
从index到size-1的位置,将后面元素逐一前移覆盖
最后有效的那个元素删掉了,所以--size
保留起来的旧数组返回
设计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、数组的优点和缺点:
(1)优点:利用数组存储一组元素,长度固定,好处在于创建后不会浪费内存
(2)缺点:长度不变,如果添加删除时,个数的改变很麻烦
2、ArrayBox优点和缺点:
(1)优点:长度看似可以改变,在于添加删除出时不需要纠结长度变化的问题,适合遍历
(2)缺点:插入和删除是很麻烦的,效率低,需要不断的移入元素的位置进行覆盖
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
①会发现每一个结构都是一样的。从红线开始看,每一个对象的下都指向下一个对象的上,也就是说每一个对象的尾节点都存储这下一个对象的地址。(绿线也是,每一个对象的首节点都存储这上一个对象的地址)
(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同学需要数据才能进行添加;
如果想存的话,往哪存?肯定是挂在尾端,那么挂在尾端,先知道尾端在哪?所以先获取链表的尾节点
尾巴找到之后,就想把数据挂在尾节点,那么挂数据,是不是应该先将数据包装起来,那么包装?怎么包装?创建一个新的Node对象(新节点)将数据包装起来
这个新节点挂在尾巴上,那么这个新节点的首节点一定是上一个节点的尾节点
如果连上以后,这个新节点是不是就变成尾节点了,尾节点为null,因为是新创建的节点;为空是为了等待下一个节点的连接,所以要将新节点设置为尾节点,用于等待下一个节点的连接
但是想一个问题,就是有可能你上面创建的node对象前面没有节点,也就是说有可能你这新节点就是第一个节点,所以要做一个严谨的判断
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);
}
}
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