目录
1.6 RuntimeExcepion与Exception的区别
一,面向对象基础
1,面向对象概述
1.1 基本概念
- 面向对象(Object Oriented)是软件开发方法。
- 面向对象的概念和应用已超越了程序设计和软件开发,是一种对现 实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
- 面向对象是相对于面向过程来讲的,指的是把 相关的数据和方法组织为一个整体 来看待,从更高的层次来进行系 统建模,更贴近事物的自然运行模式。
面向过程到面向对象思想层面的转变:
- 面向过程关注的是执行的过程,面向对象关注的是具备功能的对象。
- 面向过程到面向对象,是程序员思想上 从执行者到指挥者的转变。
1.2 举个例子
我们描述一个生活的场景:
场景:当我们独自生活时, 我们经常纠结一日三餐怎么吃。
- 面向过程: 每天亲力亲为: 买菜 - 做饭 - 吃饭 - 洗碗 的过程。
- 面向对象: 招聘一个保姆,每天等吃即可。
场景升级: 假设你是一个富豪, 拥有一座占地3000亩地的庄园 ,不再是只关注吃饭问题 , 还有花草树木修剪,泳池维 护清洗,卫生打扫,洗衣做饭。。。。。。
- 面向过程: 此处省略看着就累的N字。
- 面向对象: 招聘一个管家, 然后让管家招聘 园丁、泳池维护工、保姆等等。
结论:
- 从上述的栗子中, 我们发现面向过程,我们需要关注很繁琐的过程 。
- 而面向对象不用关注具体的细节,更关注的是统筹架构的问题。
- 其实我们进行大型应用开发时, 就如上述的例子一样, 如果我们写程序只关注过程的话, 代码量达到一定 层次以后, 就很难再编写下去了。
1.3 三大思想
面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP
- OOA:面向对象分析(Object Oriented Analysis)
- OOD:面向对象设计(Object Oriented Design)
- OOP:面向对象程序(Object Oriented Programming
1.4 三大特征
封装性:所有的内容对外部不可见
- 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问,常见的实现方式就是:getter、setter。
继承性:将其他的功能继承下来继续发展
- 继承是类与类的一种关系,子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用。
多态性:方法的重载本身就是一个多态性的体现(Java中的多态主要指引用多态和方法多态。)
- 引用多态是指:父类引用可以指向本类对象,也可指向子类对象。引用多态的强大主要体现在调用属性、方法时,可以根据引用具体指向的对象去调用,例如:子类中重写了父类方法。
- 方法多态:子类中可以重写父类的方法,在调用方法时根据引用指向的子类对象决定调用哪个具体的方法。方法多态的强大主要体现在可以根据调用时参数的不同,而自主匹配调用的方法,例如:重载。
2,类与对象的关系
类表示一个共性的产物,是一个综合的特征,而对象,是一个个性的产物,是一个个体的特征。 (类似生活中的图纸与实物的概念。)
类必须通过对象才可以使用,对象的所有操作都在类中定义。
类由属性和方法组成:
- 属性:就相当于人的一个个的特征 ;
- 方法:就相当于人的一个个的行为,例如:说话、吃饭、唱歌、睡觉;
3,类与对象的创建
3.1 类的创建
1,类的定义格式:
class 类名称{ 成员属性 成员方法 }
2,注意事项
- 类必须编写在.java文件中。
- 一个.java文件中, 可以存在N个类, 但是只能存在一个public修饰的类。
- .java文件的文件名称 必须 与public修饰的类名 完全一致;
3,属性与方法定义格式
属性定义格式:
- 数据类型 属性名;
- 数据类型 属性名 = 初始化值;
方法定义格式:
权限修饰符 返回值类型 方法名(形式参数列表){ //方法体 return 返回值; }
3.2 对象的创建
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
- 类名称 对象名称 = new 类名称() ;
如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式:
- 访问类中的属性: 对象.属性 ;
- 调用类中的方法: 对象.方法(实际参数列表) ;
3.3 具体实例
package com.kaikeba.demo2;
/**
* class 类名{
* 成员属性;
* 成员方法;
* }
*
* 类必须编写在.java文件中。
* 一个.java文件中, 可以存在N个类, 但是只能存在一个public修饰的类。
* .java文件的文件名称 必须 与public修饰的类名 完全一致;
*
*/
public class Demo {
public static void main(String[] args) {
//创建对象的格式:
//类名 对象名 = new 类名();
Person p = new Person();
//对象属性赋值
//格式: 对象名.属性名=值;
p.name = "张三";
p.age = 18;
p.sex = '男';
p.say();
int s = p.sum(100, 200);
System.out.println(s);
}
}
/**
* 类就像是图纸
*/
class Person{
//属性 - 特征
String name;
int age;
char sex;
//方法 - 行为
/**
* 定义格式:
* 返回值类型 方法名称(形式参数列表){
* 方法体
* return 返回值;
* }
*
* 调用格式:
* 对象名.方法名称(实际参数列表);
*/
void say() {
System.out.println("自我介绍:我是"+name+" , 我的年龄:"+age+",我的性别:"+sex);
}
int sum(int x,int y) {
int z = x+y;
return z;
}
/*
void xxx() {
if(true) {
return; // 这里的return表示方法的结束
}
System.out.println("哈哈");
}
*/
}
4,对象创建内存
4.1 栈
1,栈(Stack): Java中一个线程一个栈区。每一个栈中的元素都是私有的,不被其他栈所访问。 栈有后进先出的特点,栈中的数据大小与生存期都是确定的,缺乏灵活性,但是,存取速度比堆要快,仅次于CPU中的高存器...Java栈的区域很小 , 大概2m左右 , 特点是存取的速度特别快 ;
2,存储速度快的原因:
- 栈内存, 通过 '栈指针' 来创建空间与释放空间 !
- 指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 !
- 这种方式速度特别快 , 仅次于PC寄存器 !
3,但是这种移动的方式, 必须要明确移动的大小与范围 , 明确大小与范围是为了方便指针的移动 , 这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序 的灵活性 ~ 。所以我们把更大部分的数据 存储到了堆内存中
4,栈中存储的是:
基本数据类型的数据(比如局部变量) 以及 引用数据类型的引用(比如数组的引用名,对象的引用名)!
例如:
- int a =10;
- Person p = new Person();
- 10存储在栈内存中 , 第二句代码创建的对象的引用(p)存在栈内存中
4.2 堆
存放的是类的对象(new出来的东西,成员变量) . (大小不固定)
- Java是一个纯面向对象语言, 限制了对象的创建方式: 所有类的对象都是通过new关键字创建
- new关键字, 是指告诉JVM , 需要明确的去创建一个新的对象 , 去开辟一块新的堆内存空间:
- 堆内存与栈内存不同, 优点在于我们创建对象时 , 不必关注堆内存中需要开辟多少存储空间 , 也不需要关注内存占用 时长 !
- 堆内存中内存的释放是由GC(垃圾回收器)完成的
垃圾回收器 回收堆内存的规则:
- 当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收
4.3 方法区
存放的是
- 类信息
- 静态的变量
- 常量
- 成员方法
方法区中包含了一个特殊的区域 ( 常量池 )(存储的是使用static修饰的成员)
4.4 PC寄存器
PC寄存器保存的是 当前正在执行的 JVM指令的 地址 !
在Java程序中, 每个线程启动时, 都会创建一个PC寄存器 !
4.5 本地方法栈
保存本地(native)方法的地址 !
4.6 实例过程1
1,分配内存,并且修改属性的值;
2,创建新的对象b2,指向旧对象b1,并且更新属性name的值
3,程序执行完毕,引用数据从栈中弹出,栈内存中不存在此对象的引用时,等待垃圾回收器回收;
4.7 实例过程2
1,创建两个新对象
2,引用数据b2指向b1,b2原先指向的对象被当作垃圾释放;
5,构造方法
5.1 概述
回顾对象的创建
- Person p = new Person(); 在右侧Person后面出现的小括号, 其实就是在调用构造方法 !
作用:
- 用于对象初始化。
执行时机:
- 在创建对象时,自动调用
特点:
- 所有的Java类中都会至少存在一个构造方法
- 如果一个类中没有明确的编写构造方法, 则编译器会自动生成一个无参的构造方法, 构造方法中没有任何的代 码!
- 如果自行编写了任意一个构造器, 则编译器不会再自动生成无参的构造方法
5.2 定义格式
定义的格式:
与普通方法基本相同, 区别在于: 方法名称必须与类名相同, 没有返回值类型的声明 !
案例:
public class Demo3{ public static void main(String[] args){ Person p = new Person(); p = new Person(); p = new Person(); p = new Person(); } } class Person{ public Person(){ System.out.println("对象创建时,此方法调用"); } }
5.3 构造方法设计
建议自定义无参构造方法,不要对编译器形成依赖,避免错误发生。
当类中有非常量成员变量时,建议提供两个版本的构造方法,一个是无参构造方法,一个是全属性做参数的构造方法。
当类中所有成员变量都是常量或者没有成员变量时,建议不提供任何版本的构造。
6,方法的重载
6.2 普通方法的重载
方法名称相同, 参数类型或参数长度不同, 可以完成方法的重载 !
方法的重载与返回值无关! 方法的重载 ,可以让我们在不同的需求下, 通过传递不同的参数调用方法来完成具体的功能
package com.kaikeba.demo2; public class Demo5 { public static void main(String[] args) { Math m = new Math(); int num = m.sum(100, 500); System.out.println(num); double num2 = m.sum(10.5, 20.6); System.out.println(num2); } } // 命名规范 见名知意 class Math{ /** * 一个类中定义的方法, 是允许重载 (相同的方法名称) * * 1、方法名称相同 * 2、参数列表长度 或 参数列表类型 或 (参数类型顺序不同) * * 注意: 与返回值类型无关 * */ int sum(int x,int y) { int z = x+y; return z; } double sum(double x,double y) { double z = x+y; return z; } double sum(int x,double y) { double z = x+y; return z; } double sum(double y,int x) { double z = x+y; return z; } }
6.2 构造方法的重载
一个类, 可以存在多个构造方法 :
- 参数列表的长度或类型不同即可完成构造方法的重载 ~
- 构造方法的重载 ,可以让我们在不同的创建对象的需求下, 调用不同的方法来完成对象的初始化!
package com.kaikeba.demo2; public class Demo6 { public static void main(String[] args) { Person3 p = new Person3("张三",18); p.say(); Person3 p2 = new Person3("李四"); p2.say(); } } class Person3{ Person3(String name2,int age2){ name = name2; age = age2; } Person3(String name2){ name = name2; } String name; int age; void say() { System.out.println("自我介绍: 姓名:"+name+", 年龄:"+age); } }
7,匿名对象
没有对象名称的对象 就是匿名对象。 (没有对象名、引用名,所以所以栈中无相关记录,故只能使用一次)
- 匿名对象只能使用一次,因为没有任何的对象引用,所以将称为垃圾,等待被G·C回收。
- 只使用一次的对象可以通过匿名对象的方式完成,这一点在以后的开发中将经常使用到。
package com.kaikeba.demo2; public class Demo7 { /** * 匿名 : 没有名字(没有对象名 没有引用名,所以栈中无相关记录,故只能使用一次) */ public static void main(String[] args) { int num = new Math2().sum(100, 200); // 没有给对象名称 而是直接新建对象并使用方法 System.out.println(num); } } class Math2{ int sum(int x,int y) { return x+y; } }
每次new都是在堆中创建了一个新的内存区:
二,面向对象进阶
1,封装
1.1 概述:
- 封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
- 保护成员属性,不让类以外的程序直接访问和修改;
1.2 封装原则:
- 隐藏对象的属性和实现细节,仅对外公开访问方法,并且控制访问级别
1.3 举例如下:
1,观察如下代码:
class Person{ private String name ; // 表示姓名 private int age ; // 表示年龄 void tell(){ System.out.println("姓名:" + name + ";年龄:" + age) ; } }; public class Demo{ public static void main(String args[]){ Person per = new Person() ; per.name = "张三" ; per.age = -30 ; per.tell() ; } };
2,可以发现,per.age = -30;出现逻辑错误(年龄不可能为负数);
3,在开发中, 为了避免出现逻辑错误, 我们建议对所有属性进行封装,并为其提供setter及getter方法进行设置和取得 操作。
Eclipse中对setter及getter方法创建提供了两种快捷方式:
方法一:
方法二:
4,修改后的代码如下:(private修饰属性)
class Person{ private String name ; // 表示姓名 private int age ; // 表示年龄 void tell(){ System.out.println("姓名:" + getName() + ";年龄:" + getAge()) ; } public void setName(String str){ name = str ; } public void setAge(int a){ if(a>0&&a<150) age = a ; else{ System.out.println("年龄设置不合理,自动设置为1"); age = 1; } } public String getName(){ return name ; } public int getAge(){ return age ; } }; public class Demo10{ public static void main(String args[]){ Person per = new Person() ; per.setName("张三") ; per.setAge(-30) ; per.tell() ; } };
修改年龄属性时需要调用函数, 因此可以通过函数来控制输入的参数是否合理,并给出相应提示,大大的降低出错概率:
2,this关键字
2.1 概念
在Java基础中,this关键字是一个最重要的概念。使用this关键字可以完成以下的操作:
- 调用类中的属性 ;
- 调用类中的方法或构造方法 (在一个构造方法中,调用另一一个构造方法时, 调用的代码必须编写在构造方法的第一 行。因为在调用有参构造方法之前,对象尚未创建完毕,这时使用其他语句会报错);
- 表示当前对象;
2.2 举例
1,声明全参构造函数时,如果传递的参数和属性名称一致,就会出现问题:
2,解决方法:this.属性名
3,此外,还可以通过this在无参构造方法中调用有参构造方法
4,注意:在一个构造方法中,调用另一一个构造方法时, 调用的代码必须编写在构造方法的第一 行;
3,静态static
3.1 概述
static表示“静态”的意思,可以用来修饰成员变量和成员方法(后续还会学习 静态代码块 和 静态内部类)。
static的主要作用在于创建独立于具体对象的域变量或者方法
简单理解:
- 被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访 问;
- 并且不会因为对象的多次创建 而在内存中建立多份数据;
3.2 重点
静态成员 在类加载时加载并初始化。
无论一个类存在多少个对象 , 静态的属性, 永远在内存中只有一份( 可以理解为所有对象公用 )
在访问时: 静态不能访问非静态 , 非静态可以访问静态 !(静态表示对象未创建也可调用,而非静态必须等到对象创建完毕。所以使用静态时,对象可能还未创建,因此不能访问非静态)
3.3 举例
1,假设员工有属性:姓名、公司。声明若干个对象时,原先的做法如下:
2,当公司迁址,那么每个对象中的地址属性都需要进行修改(时间开销大)。此外公司地址都是相同的,不同对象重复的创建地址变量,有浪费空间的嫌疑(空间开销大);
当前内存状况如下:
3,解决方法:static修饰
使用static修饰的变量,存放在方法区中:
4,static修饰的变量,可以理解为类的属性,直接通过类名来调用,所以在创建对象之前就可以调用
4,包
4.1 介绍
1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
2、包如同文件夹一样,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名 加以区别。因此,包可以避免名字冲突。
3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
4.2 使用规则
1,包中java文件的定义:
- 在.java文件的首部, 必须编写类所属哪个包,
- 格式: package 包名;
2,包的定义:
- 通常由多个单词组成, 所有单词的字母小写, 单词与单词之间使用.隔开 ,一般命名为“com.公司名.项目 名.模块名....”。
3,规范由来:
- 由于Java面向对象的特性,每名Java开发人员都可以编写属于自己的Java Package,为了保障每个Java Package命名的唯一性,在最新的Java编程规范中,要求开发人员在自己定义的包名前加上唯一的前缀。由于互联网上 的域名称是不会重复的,所以多数开发人员采用自己公司在互联网上的域名称作为自己程序包的唯一前缀。例如: com.java.xxx
4.3 实例1
1,创建包
2,找到包对应的文件夹
3,可以看出:包实际上就是文件夹
4.4 实例2
1,如何实用工具包?
当所需的工具类不在当前文件夹中时,需要通过import进行导包;
如果不想通过import导包的话,也可以通过使用类全名的方法实现相同的功能;
2,什么情况不需要导包?
- 一些常用的系统包,比如String类型,包含在java.lang包中;
- 使用当前包中的类;
5,权限修饰符
6,代码块
6.1 代码块分类
普通代码块
- 在执行的流程中 出现的 代码块, 我们称其为普通代码块。
构造代码块
- 在类中的成员代码块, 我们称其为构造代码块, 在每次对象创建时执行, 执行在构造方法之前。
静态代码块
- 在类中使用static修饰的成员代码块, 我们称其为静态代码块, 在类加载时执行。 每次程序启动到关闭 ,只会 执行一次的代码块。
同步代码块
- 在后续多线程技术中学习。
面试题:构造方法 与 构造代码块 以及 静态代码块的执行顺序: 静态代码块 --> 构造代码块 --> 构造方法
6.2 构造代码块
1,随着对象的每次创建,执行一次。 且执行在构造方法之前:
2,为什么需要构造代码块呢?
与构造方法不同,构造方法不一定会被执行(构造方法可能会重载,这样使用无参构造方法就不会调用一参或两参的构造方法);
所以当多个构造方法需要同一种操作时,可以使用代码块(可以有多个);
6.3 静态代码块
在代码块前加static;
静态代码块,随着类的加载 (第一次使用) ,静态代码块执行。
因为类只加载一次,所以静态代码块只执行一次。
举例如下:
7,main方法详解
main()方法一直写到了今天:
public static void main(String args[])
以上的各个参数的含义如下:
- · public:表示公共的内容,可以被所有操作所调用
- · static:表示方法是静态的,可以由类名称直接调用。java StaticDemo09
- · void:表示没有任何的返回值操作
- · main:系统规定好的方法名称。如果main写错了或没有,会报错:NoSuchMethodError: main
- · String[] args:字符串数组,接收参数的
public class StaticDemo10{ public static void main(String args[]){ for(int i=0;i<args.length;i++){ System.out.println(args[i]) ; } }
所有的参数在执行类的时候以空格进行分割。
java StaticDemo10 1 2 3 4 5 6 7
但是,如果现在我要输入的是以下几种参数“hello world”、“hello vince”、“hello mjw”。
因为以空格分割,所以以上的三组参数会当做六组参数输入,那么此时如果要想完成有空格的内容输入,则参数需 要使用“"”括起来。
java StaticDemo10 "hello world" "hello vince" "hello mjw"
三,面向对象高级
1,继承格式
1.1 概念
继承是java面向对象编程技术的一 块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
1.2 格式
class 父类 {
}
class 子类 extends 父类 {
}
1.3 继承的限制
Java中只有单继承,多重继承(C想继承B和A,可以C继承B,B继承A),没有多继承(同时继承多个父类。因为不同的父类可能有相同的特征时,如何选择继承谁的)
2,子类实例化内存分析
1,假设现在有一个父类Person,子类Student继承父类Person。当创建Student对象时Student s = new Student(),便在栈中创建引用,在堆中创建Student空间:
2,但实际上,并不是直接打开Student空间,而是先查看其有哪些父(这里是Person类)。于是先创建Person对象,创建完毕后再创建Student,并添加super变量/属性;
3,super中存放的值就是Person对象的内存地址,所以可以通过super操作Person类;
4,假设setName函数和name属性都在Person类中。当调用s.setName("张三");时,先找Student有没有setName的方法,如果没有就在super中找(这样可以规定方法使用的优先级,相当于重写),如果没有,就在Person中的super找,以此类推。所以 s.setName("张三") 《=》s.super.setName("张三")。所以最终是在Person中修改了name的值;
5,继承时只能继承共有的比如用public,protected修饰的属性和方法,default和private修饰的无法直接使用。
3,super详解
super:
通过super,可以访问父类构造方法
- 调用super构造方法的代码,必须写在子类构造方法的第一行。(类似于this中相关的用法)
通过super,可以访问父类的属性
通过super,可以访问父类的方法
4,重写,重写与重载的区别
4.1 重写(Override)
重写(override) 规则:(子父类之间)
1,参数列表必须完全 与被重写方法的相同:
2,返回类型必须完全与被重写方法的返回类型相同:
3,访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重与该方法就不能声明为protected.
4,父类的成员方法只能披它的子类重写。(如果两个类之间没有关系,因为没有继承,所以不算重写)
5,声明为static和privete的方法不能被重写,但是能够被再次声明。(静态static的方法与对象无关。私有private不能被继承,所以无法重写)
4.2 重载(Overload)
见本文 一,6
4.3 区别
1,发生的位置:
- 重载:一个类中
- 重写:子父类中
2,参数列表限制
- 重载:必须不同
- 重写:必须相同
3,返回值类型
- 重载:与返回值类型无关
- 重写:返回值类型必须一致
4,访问权限
- 重载:与访问权限无关
- 重写:子的方法权限不能小于父的方法权限
5,异常处理
- 重载:与异常无关
- 重写:异常范围可以更小,但是不能抛出新的异常
5,final关键字
5.1 final用于修饰属性、变量。
- 变量成为常量,无法再次进行赋值
- final修饰的局部变量(在方法内部),只能赋值一 次(可以先声明后赋值)
- final修饰的是成员属性,必须在声明时赋值。
- 全局常量( public static final )。
补充:常量的命名规范
- 由1个或多个单词组成,单词与单词之 间必须使用下划线隔开,单词中所有字母大写。比如SQL_INSERT
5.2 final用于修饰类
final修饰的类,不能被继承。
5.3 final用于修饰方法
final修饰的方法,不能被子类重写。
6,抽象类
6.1 概念
抽象类必须使用abstract class声明
一个抽象类中可以没有抽象方法。抽象方法必须写在抽象类或者接口中。
格式:
abstract class 类名{ // 抽象类 }
比如,在设计父类Person时,有一个方法用来描述说话方式,然而子类可能有很多比如学生、护士、企业家等等,他们对这个方法的需求并不统一,所以父类不知如何设计(无法确定的部分)
6.2 抽象方法
只声明而未实现的方法称为抽象方法(未实现指的是:没有“{}”方法体),抽象方法必须使用abstract关 键字声明。
格式:
abstract class 类名{ // 抽象类 public abstract void 方法名() ; // 抽象方法,只声明而未实现 }
6.3 不能被实例化
类相当于一个图纸,当类中有不确定的部分时,便不能依据图纸做出相应的实例,也就是不能被实例化。
在抽象类的使用中有几个原则:
- · 抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键字new完成。
- · 一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须重写抽象类中的全部抽象方法。
6.4 实例
编程规范:建议一个.java文件只编写一个类,且类通过public修饰。
1,测试类Demo,抽象类Person;
2,编写子类Student
3,编写子类Nurse
4,编写测试方法:
5,抽象类的好处
假设现在有一个大型项目,当项目中部分模块未明确功能时,如果因此而暂停项目的开展进程,将会非常低效,所以将不确定的部分编写为抽象类(抽象类也不常用,后续中的接口应用更加普遍)
6.5 抽象类常见问题
1、 抽象类能否使用final声明?
- 不能,因为final属修饰的类是不能有子类的 , 而抽象类必须有子类才有意义,所以不能。
2、 抽象类能否有构造方法?
- 能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法。(创建子类对象时,是先创建父类的对象,然后再创建子类对象,并添加super属性)
比如在抽象类中添加无参构造方法:
执行测试类:
6.6 抽象类与普通类的区别
1、抽象类必须用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法)。 默认缺省为 public
2、抽象类不可以使用new关键字创建对象, 但是在子类创建对象时, 抽象父类也会被JVM实例化。
3、如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为 abstract类
7,接口多态
7.1 接口概念
如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。
格式:
interface 接口名称{ 全局常量 ; 抽象方法 ; }
如果一个接口要想使用,必须依靠子类。 子类(如果不是抽象类的话)要实现接口中的所有抽象方法。
7.2 面向接口编程思想
这种思想是接口是定义(规范,约束)与实现(名实分离的原则)的分离。 (从宏观角度设计好,定义好规范后再设计,某一环节出现问题,直接替换即可)
优点:
- 1、 降低程序的耦合性
- 2、 易于程序的扩展
- 3、 有利于程序的维护
7.3 全局常量和抽象方法的简写
因为接口本身都是由全局常量和抽象方法组成 , 所以接口中的成员定义可以简写:
1、全局常量编写时, 可以省略public、static、final 关键字,例如:
public static final String INFO = "内容" ;
简写后: String INFO = "内容" ;
斜体加粗表示
2、抽象方法编写时, 可以省略 public、abstract 关键字, 例如:
public abstract void print() ;
简写后: void print() ;
7.4 接口的实现
接口因为都是抽象部分, 不存在具体的实现, 所以允许多继承:
格式:
class 子类 implements 父接口1,父接口2...{ }
以上的代码称为接口的实现。那么如果一个类即要实现接口,又要继承抽象类的话,则按照以下的格式编写即可:
class 子类 extends 父类 implements 父接口1,父接口2...{ }
7.5 接口和抽象类的区别
1、抽象类要被子类继承,接口要被类实现。
2、接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
3、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
4、抽象类使用继承来使用, 无法多继承。 接口使用实现来使用, 可以多实现
5、抽象类中可以包含static方法 ,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)
6、接口不能有构造方法,但是抽象类可以有
7.6 多态概念
多态:就是对象的多种表现形式,(多种体现形态)
7.7 多态的体现
对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态 ,对象多态性就从此而来。
ps: 方法的重载 和 重写 也是多态的一种, 不过是方法的多态(相同方法名的多种形态)。
- 重载: 一个类中方法的多态性体现
- 重写: 子父类中方法的多态性体现。
7.8 多态的的使用:对象类型的转换
类似于基本数据类型的转换:
- 向上转型:将子类实例变为父类实例
格式:父类 父类对象 = 子类实例 ;
- 向下转型:将父类实例变为子类实例
格式:子类 子类对象 = (子类)父类实例 ;(类似于强制转化)
例1,父类的对象指向子类的对象,即小范围的数据可用大范围的来表示:
例2,向上转型之后,护士的本质还是护士,学生的本质还是学生,如果用学生的对象指向护士的上转型对象就会出错:
例3,多态的实际应用。
8,instanceof
作用:判断某个对象是否是指定类的实例,则可以使用instanceof关键字
格式:实例化对象 instanceof 类 //此操作返回boolean类型的数据
举例:在方法中有时会提前约定对象所属的类,这时如果不判断类型就会出错
解决方法
9,Object类概述
9.1 概念
Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。
例如我们定义一个类:
public class Person{ }
其实它被使用时 是这样的:
public class Person extends Object{ }
9.2 Object的多态
有了前面的铺垫,可以看出,使用Object可以接收任意的引用数据类型;
举例1,传入String数据类型
10,toString
查看类的某些方法:
方法一:Ctrl+鼠标左键查看类的源码
方法二:查看API文档
10.1 查看文档
搜索Object,找到toString方法
10.2 查看对象的toString形式
直接输出一个对象
查看println函数
查看valueOf函数
10.3 重写toString方法
运行查看
如何区分对象
运行效果
建议:重写所有类的toString方法
10.4 重写toString的快捷键方法
1,Shift+Alt+S
2,选择生成toString
3,勾选作为标识的属性
4,自动生成的toString函数
5,运行效果
11,equals
11.1 概述
建议重写Object中的equals(Object obj)方法,此方法的作用:指示某个其他对象是否“等于”此对象。
Object的equals方法:实现了对象上最具区别的可能等价关系; 也就是说,对于任何非空引用值x和y ,当且仅当 x和y引用同一对象( x == y具有值true )时,此方法返回true 。
equals方法重写时的五个特性:
- 自反性 :对于任何非空的参考值x , x.equals(x)应该返回true 。
- 对称性 :对于任何非空引用值x和y , x.equals(y)应该返回true当且仅当y.equals(x)回报true 。
- 传递性 :对于任何非空引用值x , y和z ,如果x.equals(y)回报true个y.equals(z)回报true ,然后 x.equals(z)应该返回true 。
- 一致性 :对于任何非空引用值x和y ,多次调用x.equals(y)始终返回true或始终返回false ,前提是未修改对象 上的equals比较中使用的信息。
- 非空性 :对于任何非空的参考值x , x.equals(null)应该返回false 。
11.2 举例
1,直接使用==,比较的是内存中的地址,所以只要不是同一个东西,就会返回false
2,直接使用equals,仍然会报错
3,查看equals的实现方式,发现本质上仍是==,因此需要重写
4,重写equals方法
5,简化方法
6,运行结果
7,快捷键:Shift+Alt+S,选择下列选项
12,内部类概述
12.1 概述
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
广泛意义上的内部类一般来说包括这四种:
- 1、成员内部类
- 2、局部内部类
- 3、匿名内部类
- 4、静态内部类
12.2 成员内部类
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式
class Outer { private double x = 0; public Outer(double x) { this.x = x; } class Inner { //内部类 public void say() { System.out.println("x="+x); } } }
特点: 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问 的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
- 外部类.this.成员变量
- 外部类.this.成员方法
12.3 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或 者该作用域内
注意:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
例1,使用介绍
1,比如原先因为不确定一些操作,定义一个接口,这时要一个方法需要使用这个接口(
2,如果正确实现,还需要额外写一个类,显得过域麻烦,这时就可以使用局部内部类
3,简单来说就是参数需要对象,但不知道该new什么时,且只用到一次,可以使用局部内部类。
例2,实际应用场景
1,当给一个窗口添加监听器时,发现需要传入一个参数,参数的类型为接口
2,编写局部内部类
3,修改对应方法即可
12.4 匿名内部类
属于局部内部类的一种。匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一 个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐 式的。
在使用匿名内部类的过程中,我们需要注意如下几点:
- 1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或 者实现一个接口。
- 2、匿名内部类中是不能定义构造函数的。
- 3、匿名内部类中不能存在任何的静态成员变量和静态方法。
- 4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
- 5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 6、只能访问final型的局部变量
为什么只能访问final型局部变量
1,使用局部变量,对变量二次赋值时报错
2,内部类也是一个类,所以编译结束后也会生成一个内部类对应的字节码文件,此时备份了变量的值,如果外部变量发生了改变,就会造成前后不一致的情况
12.5 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。
静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员 变量或者方法.
例1
1,创建静态内部类对象
2,静态内部类实例化
3,它不能使用外部类的非static成员 变量或者方法
13,包装类
13.1 概述
在Java中有一个设计的原则“一切皆对象”,那么这样一来Java中的一些基本的数据类型,就完全不符合于这种设计思 想,因为Java中的八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,引入了八种基本数据类型 的包装类。
以上的八种包装类,可以将基本数据类型按照类的形式进行操作。 但是,以上的八种包装类也是分为两种大的类型的:
- · Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个数字。
- · Object:Character、Boolean都是Object的直接子类。
13.2 优点
1,可以作为对象参数进行传递,方便了基本数据类型与引用数据类型的交互(这里参数直接传10,会自动装箱,变为Integer对象,然后传递)
2,可以有许多方法提供使用
13.3 装箱和拆箱操作
以下以Integer和Float为例进行操作
- 将一个基本数据类型变为包装类,那么这样的操作称为装箱操作。
- 将一个包装类变为一个基本数据类型,这样的操作称为拆箱操作,
因为所有的数值型的包装类都是Number的子类,Number的类中定义了如下的操作方法,以下的全部方法都是进行拆箱的操 作。
装箱操作:
在JDK1.4之前 ,如果要想装箱,直接使用各个包装类的构造方法即可,例如:
在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。例如:
程序实例
13.4 字符串转换
使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用 较多。
在Integer类中提供了以下的操作方法:
- public static int parseInt(String s) :将String变为int型数据
在Float类中提供了以下的操作方法:
- public static float parseFloat(String s) :将String变为Float
在Boolean 类中提供了以下操作方法:
- public static boolean parseBoolean(String s) :将String变为boolean
举例
2,将字符串转换为int
14,可变参数
一个方法中定义完了参数,则在调用的时候必须传入与其一一对应的参数,但是在JDK 1.5之后提供了新的功能,可以根 据需要自动传入任意个数的参数。
返回值类型 方法名称(数据类型…参数名称){ //参数在方法内部 , 以数组的形式来接收 }
注意: 可变参数只能出现在参数列表的最后。
15,递归概述
四,安装IDEA
1,安装IDEA
1,进入官网https://www.jetbrains.com/products.html#type=ide,选择IDEA
2,下载
3,下载完之后安装
4,注册(用邮箱注册,通过邮箱验证)并激活账号。这时账号已经成功激活正版IDEA
5,激活IDEA
2,基本操作
1,登录界面
2,帮助中可以查看快捷键方式(也有鼠标垫中记录中文IDEA快捷键)
3,创建工程
五,异常处理
什么是异常?
异常是在程序中导致程序中断运行的一种指令流。
以上的代码在“int temp = i / j ;”位置处产生了异常,一旦产生异常之后,异常之后的语句将不再执行了,所以现 在的程序并没有正确的执行完毕之后就退出了。
那么,为了保证程序出现异常之后仍然可以正确的执行完毕,所以要采用异常的处理机制。
1,try-catch
1.1 问题引入
1,编写代码如下:
2,运行测试
3,新手误区:
容易想当然:认为所有用户都会按照程序员的思路来操作;
容易“我认为”:用户的需求或设计在别人看来就是合理的,程序员不是产品经理,不要擅自改动;
4,异常产生的过程
所以x/y这里变成了一个new的操作
5,为了避免JVM接收到异常对象,可以选择在 创建异常对象-》虚拟机 过程中拦截,这就引入了处理异常
1.2 处理异常
1,如果要想对异常进行处理,则必须采用标准的处理格式,处理格式语法如下:
try{ // 有可能发生异常的代码段 }catch(异常类型1 对象名1){ // 异常的处理操作 }catch(异常类型2 对象名2){ // 异常的处理操作 } ... finally{ // 异常的统一出口 }
在进行异常的处理之后,在异常的处理格式中还有一个finally语句,那么此语句将作为异常的统一出口,不管是否产生 了异常,最终都要执行此段代码。(finally不执行情况:电脑没电、关机、软件关闭程序从内存中消失、finally之前出现exit。其余情况finally都会执行)
1.3 finally常见面试问题
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
答:finally中的代码会执行
详解:执行流程:
- 1. 先计算返回值, 并将返回值存储起来, 等待返回
- 2. 执行finally代码块
- 3. 将之前存储的返回值, 返回出去;
需注意:
- 1. 返回值是在finally运算之前就确定了,并且缓存了,不管finally对该值做任何的改变,返回的值都不 会改变
- 2. finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或 catch中的值
- 3. 如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出 JVM:System.exit(0);
finally举例1:(即使return,在准备返回值与跳出函数之间,仍会执行finally中的语句)
finally举例2:
由于返回值是引用数据类型,在return之前准备好p(将引用地址进行备份),但是finally中通过引用数据类型的地址,修改了值,所以还是显示的最终结果会更改属性值为28;
finally举例3:
finally举例4:
2,处理过程如下:
- 1、 一旦产生异常,则系统会自动产生一个异常类的实例化对象。
- 2、 那么,此时如果异常发生在try语句,则会自动找到匹配的catch语句执行,如果没有在try语句中,则会将异 常抛出(给到调用的方法).
- 3、 所有的catch根据方法的参数匹配异常类的实例化对象,如果匹配成功,则表示由此catch进行处理
使用异常的效果:
1,不捕获异常:
2,捕获异常
1.4 异常的体系结构
1,异常指的是Exception , Exception类, 在Java中存在一个父类Throwable(可能的抛出) Throwable存在两个子类:
- 1.Error:表示的是错误,是JVM发出的错误操作,只能尽量避免,无法用代码处理。(比如内存过小)
- 2.Exception:一般表示所有程序中的错误,所以一般在程序中将进行try…catch的处理。
2,多异常捕获的注意点:
- 1、 捕获更粗的异常不能放在捕获更细的异常之前。
- 2、 如果为了方便,则可以将所有的异常都使用Exception进行捕获
3,RuntimeException:运行时异常(非受检异常)。参数有可能引发错误时,发出的异常不会有提示,就直接崩溃
1.5 真实的异常处理场景
1,程序异常的用途
2,运行效果
3,最终代码
4,格式2(将异常合并起来,了解即可)
5,格式3(使用范围更大的异常)
1.6 RuntimeExcepion与Exception的区别
注意观察如下方法的源码:
Integer类: public static int parseInt(String text)throws NumberFormatException
此方法抛出了异常, 但是使用时却不需要进行try。。。catch捕获处理,
原因: 因为NumberFormatException并不是Exception的直接子类,而是RuntimeException的子类,只要是 RuntimeException的子类,则表示程序在操作的时候可以不必使用try…catch进行处理,如果有异常发生,则由JVM进 行处理。当然,也可以通过try catch处理。
2,throws
2.1 概念
在程序中异常的基本处理已经掌握了,但是随异常一起的还有一个称为throws关键字,此关键字主要在方法的声明上使 用,表示方法中不处理异常,而交给调用处处理。
格式:
返回值 方法名称()throws Exception{ }
2.2 什么时候用throws/try-catch
如果是因为传入的参数导致异常的发生,则可以通过throws抛出异常。通常是谁调用谁处理;
如果是在此方法中调用时,可以使用try-catch处理异常,并使程序正常运行;
1,观察下面的函数,只有当传入的参数错误时,程序才会出错;
2,这时可以选择,谁调用谁处理的策略;
3,如何处理
3,throw
throw关键字表示在程序中人为的抛出一个异常,因为从异常处理机制来看,所有的异常一旦产生之后,实际上抛出 的就是一个异常类的实例化对象,那么此对象也可以由throw直接抛出。(真正应用的时候,自己造异常还是比较麻烦的,之前加判断也可以出现相同效果。所以用的比较少)
3.1 实例
1,回顾以前编写的函数
2,所以在发生异常时,需要告诉调用函数,发生了什么问题,而不是自己默默处理
3,运行效果
4,自定义异常
编写一个类, 继承Exception,并重写一参构造方法 即可完成自定义受检异常类型。
编写一个类, 继承RuntimeExcepion,并重写一参构造方法 即可完成自定义运行时异常类型。
例如:
class MyException extends Exception{ // 继承Exception,表示一个自定义异常类 public MyException(String msg){ super(msg) ; // 调用Exception中有一个参数的构造 } };
自定义异常可以做很多事情, 例如:
class MyException extends Exception{ public MyException(String msg){ super(msg) ; //在这里给维护人员发短信或邮件, 告知程序出现了BUG。 } };
4.1 非受检异常(运行时异常)
1,设计自定义的运行时异常类
2,抛出自定义的异常
3,运行效果
4.2 受检异常
1,自定义受检异常
2,发现飘红
3,抛出异常(受检查的异常必须明确抛出,运行时异常可以不抛出)
4,测试效果
5,解决方法
6,不解决,继续执行
5,快递-views/快递-完善
此部分内容根据视频顺序截取,仅记录过程,代码存在问题尚未完善。
5.1 任务概述
模块
任务
5.2 编写代码
2.1 views类(view包)
视图就是视图,只负责展示的内容。不包含任何逻辑
1,主菜单menu
2 快递员菜单cMenu
3 用户菜单uMenu
4, 快递录入insert
5,获取快递单号findByNumber
6,打印快递信息printExpress
有问题找产品经理,不要自己猜!
7,更新快递信息update
8,删除快递delete
9,打印所有快递信息printAll
2.2 快递类express(bean包)
流程:
- 设置成员变量-》
- 构造方法(全参/无参)-》
- getter/setter方法-》
- 重写toString方法-》
- 重写equals方法(用于判断两个对象是否相同,需要考虑哪个属性是最本质的特征)
1,属性及构造方法
2,getter/setter方法
3,toString方法
4,equals方法
2.3 快递对象数据操作类ExpressDao(dao包)
1,整体描述可能需要的方法(后面把data数组改成了二维的)
2, 成员变量(修改后的)
3,编写add函数
【补】
4,获得新的不重复的取件码randCode
5,获得取件码对应的快递对象
6,删除快递对象
7,显示所有快递对象
2.4 测试类
1,main函数
2,快递员客户端
3,用户客户端
5.3 完整代码
view类(view层)
package view;
import bean.Express;
import java.util.Scanner;
/**
* 视图层
* 只负责展示视图 不包含其他任何逻辑
*/
public class View {
public Scanner input = new Scanner(System.in);
/**
* 获得用户的角色选择输入,并进入相应的功能
* @return 返回功能码 1:管理原 2:普通用户 0:退出
*/
public int menu(){
System.out.println("根据提示输入功能序号:");
System.out.println("1,管理员");
System.out.println("2,普通用户");
System.out.println("0,退出");
String s = input.nextLine();
int funcNum = -1;
try{
funcNum = Integer.parseInt(s);
}catch (NumberFormatException e){ // 格式异常 递归继续获取功能码
return menu();
}
if(funcNum < 0 || funcNum > 2){ // 功能码不合法
return menu();
}
return funcNum;
}
/*
-----------------------------------------------------------------
*/
/**
* 获得管理员输入的功能码
* @return 管理员输入的合法功能码 1:录入 2:修改 3:删除 4:查看所有 0:退出
*/
public int gMenu(){
System.out.println("根据提示输入功能序号:");
System.out.println("1,快递录入");
System.out.println("2,快递修改");
System.out.println("3,快递删除");
System.out.println("4,查看所有快递");
System.out.println("0,退出");
String s = input.nextLine();
int funcNum = -1;
try{
funcNum = Integer.parseInt(s);
}catch (NumberFormatException e){ // 格式异常 递归继续获取功能码
return gMenu();
}
if(funcNum < 0 || funcNum > 4){ // 功能码不合法
return gMenu();
}
return funcNum;
}
/**
* 1快递员录入信息
* @return 返回包含了快递单号和快递公司的快递对象
*/
public Express insert(){
System.out.println("请根据提示输入快递信息:");
System.out.print("请输入快递单号:");
String number = input.nextLine();
System.out.print("请输入快递公司:");
String company = input.nextLine();
Express e = new Express();
e.setNumber(number);
e.setCompany(company);
return e;
}
/**
* 2修改快递信息
* @param e
*/
public void update(Express e){
System.out.print("请输入新的快递单号:");
String number = input.nextLine();
System.out.print("请输入新的快递公司");
String company = input.nextLine();
e.setNumber(number);
e.setCompany(company);
}
/**
* 3询问是否删除
* @return 给出快递管理员的选择 1:删除 2:取消
*/
public int delete(){
System.out.println("确认是否删除:");
System.out.println("1,确认删除");
System.out.println("2,取消删除");
System.out.println("0,退出");
String s = input.nextLine();
int num = -1;
try {
num = Integer.parseInt(s);
}catch (NumberFormatException e){
return delete();
}
if(num < 0 || num > 2){
return delete();
}
return num;
}
/**
* 4遍历显示所有快递信息
* @param es
*/
public void printAll(Express[][] es){
int count = 0;
for(int i = 0; i < 10; i++){
for(int j = 0;j < 10; j++){
if(es[i][j] != null) {
count++;
System.out.print("第" + (i + 1) + "排," + (j + 1) + "列, ");
printExpress(es[i][j]);
}
}
}
if(count == 0){
System.out.println("暂无快递信息");
}
}
/**
* 提示用户输入快递单号
* @return
*/
public String findByNumber(){
System.out.println("请根据提示输入快递信息:");
System.out.print("请输入需要操作的快递单号:");
String number = input.nextLine();
return number;
}
/**
* 显示快递信息
* @param e
*/
public void printExpress(Express e){
if(e == null){
System.out.println("快递信息不存在");
return;
}
System.out.println("快递信息如下:");
System.out.println("快递公司:" + e.getCompany() + ",快递单号:" + e.getNumber() + ",取件码:" + e.getCode());
}
/*
-----------------------------------------------------------------
*/
/**
* 获得用户输入的取件码(这里简化,只要取件码相同,就算取件成功)
* @return 用户输入的合法功能码(6位)
*/
public int uMenu(){
System.out.println("根据提示进行取件:");
System.out.print("请输入取件码:");
String s = input.nextLine();
int funcNum = -1;
try{
funcNum = Integer.parseInt(s);
}catch (NumberFormatException e){ // 格式异常 递归继续获取功能码
return uMenu();
}
if(funcNum < 100000 || funcNum > 999999){ // 功能码不合法
System.out.println("输入有误,请重试!");
return uMenu();
}
return funcNum;
}
public void expressExist(){
System.out.println("此快递单号已存在,请勿重复存储");
}
public void printCode(Express e) {
System.out.println("新快递的取件码为:" + e.getCode());
}
public void success(){
System.out.println("操作成功!");
}
public void printNull(){
System.out.println("快递不存在,请检查输入");
}
}
ExpressDao类(dao层)
package dao;
import bean.Express;
import java.util.Random;
public class ExpressDao {
Express[][] data = new Express[10][]; // 二维数组表示快递柜
{
for(int i = 0; i < 10; i++){
data[i] = new Express[10];
}
}
private Random random = new Random(); // 用于生成随机数
private int size; // 当前存储的快递数目(便于判断是否还有空位 否则在随机生成取件码时可能陷入死循环)
/**
* 用于存储快递
* @param e
* @return
*/
public boolean add(Express e){
if(this.size >= 100){
return false;
}
// 1,随机生成两个0-9的下标
int x = -1, y = -1;
while (true){
x = random.nextInt(10);
y = random.nextInt(10);
if(data[x][y] == null){
// 此位置未被占用
break;
}
}
// 2,判断取件码是否重复(最简单的 一个个对比)
int code = randomCode(); // 获得没有重复的取件码
e.setCode(code);
data[x][y] = e;
return true;
}
private int randomCode(){
while (true) {
int code = random.nextInt(900000) + 100000; // 范围(000000-899999)+1000000
Express e = findByCode(code);
if(e == null) { // 说明取件码未重复
return code;
}
}
}
/**
* 快递员根据快递单号查询
* @param number
* @return
*/
public Express findByNumber(String number){
Express e = new Express();
e.setNumber(number);
for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
if(e.equals(data[i][j])) { // 注意e确定不为空 但data[i][j]可能为null
return data[i][j];
}
}
}
return null;
}
/**
* 根据取件码查询快递
* @param code 取件码
* @return 查询到结果 查询失败返回null
*/
public Express findByCode(int code){
for(int i = 0; i < 10; i++){ // 这里确定了规格大小
for(int j = 0; j < 10; j++){
if(data[i][j] != null && data[i][j].getCode() == code){
return data[i][j];
}
}
}
return null;
}
/**
* 多余的操作 为了MVC更圆润
* @param oldExpress
* @param newExpress
*/
public void update(Express oldExpress, Express newExpress){
delete(oldExpress);
add(newExpress);
}
public void delete(Express e){
p:for(int i = 0; i < 10; i++){
for(int j = 0; j < 10; j++){
if(e.equals(data[i][j])) { // 注意e确定不为空 但data[i][j]可能为null
data[i][j] = null;
break p;
}
}
}
}
public Express[][] findAll(){
return data;
}
}
Express类(bean层)
package bean;
import java.util.Objects;
/**
*
*/
public class Express {
private String number; // 快递单号
private String company; // 公司
private int code; // 取件码
// 构造方法
public Express(String number, String company, int code) {
this.number = number;
this.company = company;
this.code = code;
}
public Express() {
}
// getter/setter
public String getNumber() {
return number;
}
public String getCompany() {
return company;
}
public int getCode() {
return code;
}
public void setNumber(String number) {
this.number = number;
}
public void setCompany(String company) {
this.company = company;
}
public void setCode(int code) {
this.code = code;
}
// 重写toString 方法
@Override
public String toString() {
return "Express{" +
"number='" + number + '\'' +
", company='" + company + '\'' +
", code=" + code +
'}';
}
// 重写equals方法
/**
* 只要快递单号相同就认为快递相同
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Express express = (Express) o;
return Objects.equals(number, express.number);
}
@Override
public int hashCode() {
return Objects.hash(code);
}
}
main方法(测试)
package main;
import bean.Express;
import dao.ExpressDao;
import view.View;
public class Main {
// 初始化视图对象
private static View v = new View(); // 这样可以跨方法使用
// 初始化dao对象
private static ExpressDao dao = new ExpressDao();
// 1,弹出身份选择菜单
public static void main(String[] args) {
m:while (true){
int menu = v.menu();
switch (menu){
case 0:
break m;
case 1:
gClient();
break;
case 2:
uClient();
break;
}
}
}
private static void uClient() {
// 1,获得取件码
int code = v.uMenu();
// 2,根据取件码取出快递
Express e = dao.findByCode(code);
if(e == null){
v.printNull();
}else {
v.success();
v.printExpress(e);
dao.delete(e);
}
}
private static void gClient() {
while (true){
int menu = v.gMenu();
switch (menu){
case 0:
return;
case 1:{
// 1,提示输入快递信息
Express e = v.insert();
// 2,此快递是否已经存储过
Express e2 = dao.findByNumber(e.getNumber());
// 3,存储快递
if(e2 == null){ // 未存储过
dao.add(e);
v.printCode(e);
}else { // 单号重复
v.expressExist();
}
break;
}
case 2: {// 快递修改
// 1,提示输入快递信息
String number = v.findByNumber();
// 2,查找数据
Express e1 = dao.findByNumber(number);
// 3,打印快递信息
if(e1 == null){
v.printNull();
}else {
v.printExpress(e1);
// 4,提示修改
v.update(e1); // 这里已经将快递的信息修改过了
dao.update(e1, e1); // 这里只是为了强调 删除-修改的过程
v.printExpress(e1);
}
break;
}
case 3: {// 删除
// 1,输入快递单号
String number = v.findByNumber();
// 2,查找快递对象
Express e = dao.findByNumber(number);
if(e == null){
v.printNull();
}else {
v.printExpress(e);
int type = v.delete();
if(type == 1){
dao.delete(e);
}else {
v.success();
}
}
break;
}
case 4:{ // 查看所有
Express[][] data = dao.findAll(); // 从dao层获取数据
v.printAll(data); // 在视图层显示
break;
}
}
}
}
}