校园信息管理 Day04(终)通过Java特性完成代码的优化
1案例驱动
1.1学习方式
- 根据项目来驱动学习,找出项目存在的问题,通过新的知识点进行解决问题
- 代码配合文字说明,一步一步慢慢走
- 开发原则;对扩展内容开放,对修改内容关闭
1.2项目名称
校园信息管理系统
1.3思想介绍
分类思想
1.3.1概述
- 分工协作,专人做专事
1.3.2目的
- 解决重复代码过多的冗余,提高代码的复用性
- 解决业务逻辑聚集紧密导致的可读性差,提高代码的可读性
- 解决代码可维护性差,提高代码的维护性
1.3.3方法
- 创建多个类来存储不同的对象,来模拟现实的系统运作
- Student类 标准学生类,封装键盘录入的学生信息(id , name , age , birthday)
- StudentDao类 Dao : (Data Access Object 缩写) 用于访问存储数据的数组或集合
- StudentService类 用来进行业务逻辑的处理(例如: 判断录入的id是否存在)
- StudentController类 和用户打交道(接收用户需求,采集用户信息,打印数据到控制台)
2系统介绍
2.1需求介绍
- 添加学生: 键盘录入学生信息(id,name,age,birthday)
使用数组存储学生信息,要求学生的id不能重复 - 删除学生: 键盘录入要删除学生的id值,将该学生从数组中移除,如果录入的id在数组中不存在,需要重新录入
- 修改学生: 键盘录入要修改学生的id值和修改后的学生信息将数组中该学生的信息修改,如果录入的id在数组中不存在,需要重新录入
- 查询学生: 将数组中存储的所有学生的信息输出到控制台
2.2项目实现过程
-
各个类的现实映射
-
StudentController:客服接待,用于与用户的直接接触,接受各种数据,反馈接受结果
-
StudentService:业务员,与数据库的桥梁,进行业务逻辑的判断,
-
StudnetDeo:库管,进行数据的管理
-
Student:学生类
-
-
数据库的技术选型:数组—复习数组的知识点,需要自己写方法
-
注意书写格式:包:小驼峰;类:大驼峰
-
entry为程序主入口,创建完的包和类如下
2.2.1 菜单的搭建
- 主界面的菜单
- 学生界面的菜单
- 老师界面的菜(先空着)
- 菜单实现步骤:
- 在主输出语句完成主界面的编写,
- 获取用户的选择Scanner的键盘如入
- 根据用户的选择进行对应的操作——switch语句
- 菜单实现步骤:
- 代码实现
- 学生类:
package domain;
public class Student {
// 学号,姓名,年龄,生日 四个属性
private String sid;
private String name;
private String age;
private String birthday;
//使用快捷键 alt+ins 进行空参,有参,get,set方法的构造
public Student(String sid, String name, String age, String birthday) {
this.sid = sid;
this.name = name;
this.age = age;
this.birthday = birthday;
}
public Student() {
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
- 主方法的实现
package entry;
import controller.StudentController;
import java.util.Scanner;
public class Entry {
//创造主方法
public static void main(String[] args) {
//键盘录入创建对象
Scanner sc = new Scanner(System.in);
//对其进行 while (true) 死循环,只有输入3 退出系统
while (true) {
System.out.println("--------欢迎来到校园信息管理系统--------");
System.out.println("请输入您的选择: 1.学生管理 2.老师管理 3.退出");
//键盘录入
String choice = sc.next();
// 对choic 进行swith判断
switch (choice) {
case "1":
//模拟学生管理功能
System.out.println("1.学生管理 ");
break;
case "2":
//模拟老师管理功能
System.out.println("2.老师管理 ");
break;
case "3":
//直接退出jvm虚拟机
System.exit(0);
default:
System.out.println("输入错误");
break;
}
}
}
}
- 随后我们去添加学生界面的设计,我们则需要在主方法的case1 后面进行操作
- 思想 ;公司做大做强了。我要招聘一个前台客服来进行与用户的直接交流,所以创造一个StundetController对象来处理
//创造一个业务员
StudentController studentController=new StudentController();
//业务员中的start方法,进入学生管理的主界面
//因为是不存在的方法,用alt+entry进行快速建造
studentController.start();
- 代码与主界面差不多 ,只是跳出循环的时候不能直接跳出虚拟机,需要命名循环跳出循环,此处是studentLoop
package controller;
import java.util.Scanner;
public class StudentController {
public void start() {
//键盘录入创建对象
Scanner sc = new Scanner(System.in);
studentLoop:
while (true) {
System.out.println("--------欢迎来到 <学生> 管理系统--------");
System.out.println("请输入您的选择: 1.添加学生 2.删除学生 3.修改学生 4.查看学生 5.退出");
String choic = sc.next();
switch (choic) {
case "1":
System.out.println("添加");
break;
case "2":
System.out.println("删除");
break;
case "3":
System.out.println("修改");
break;
case "4":
System.out.println("查询");
break;
case "5":
System.out.println("感谢使用,再见");
break studentLoop;
default:
System.out.println("您的输入有误, 请重新输入");
break;
}
}
}
}
- 页面已经设计完成
2.2.2学生各个功能实现步骤
2.2.2.1添加功能
-
步骤
- 1.用户输入信息
- 2.StudentController客服接受数据,并进行封装,之后信息传递给StudentService业务员,并进行业务逻辑判断(id是否存在,此处先不进行),若可以添加,则返回添加成功,若不能添加,则返回添加失败
- 3.StudentService业务员收到数据但无权对数据进行直接的添加,需要传给StudentDao尽心后续操作
- 4.StudentDao需要先创建数组,此处长度为5,随后将把从StudentDao中接收到的数据进行添加,是否添加成功返回Boolean给StudentService
- 5.StudentService接受到的boolean返回给StudentController,StudentController根据返回的boolean返回结果
-
代码实现:
-
1.在case1之后创建添加学生的方法:
addStudent();
-
2.进行键盘录入,信息的提示输入,数据的封装
private void addStudent() {
//键盘录入创建对象
Scanner sc = new Scanner(System.in);
//提示输入信息
System.out.println("请输入姓名学号");
String sid = sc.next();
System.out.println("请输入姓名");
String name = sc.next();
System.out.println("请输入年龄");
String age = sc.next();
System.out.println("请输入生日");
String birthday = sc.next();
//封装数据
//创造一个学生
Student student=new Student();
student.setSid(sid);
student.setName(name);
student.setAge(age);
student.setBirthday(birthday);
}
- 3.把数据给业务员,要知晓能否储存,(雇佣一个业务员)返回类型为boolean,如果返回为true则可以储存,返回为false,则储存失败
//创建一个业务员
StudentService studentService = new StudentService();
//把数据给业务员,我只要结果
Boolean result = studentService.addstudent(student);
//根据结果result的值进行判断
if (result) {
System.out.println("输入成功");
}else {
System.out.println("输入失败");
}
- 4.补充业务员addstudent方法(alt+entry),因暂时没有逻辑判断,只起到一个桥梁的作用
public Boolean addstudent(Student student) {
//创建一个库管对象
StudentDao studentDao = new StudentDao();
//调用库管的添加方法,并接受
boolean result = studentDao.addstudent(student);
//把接收到的值返回给业务员
return result;
}
- 5.在添加之前,我们应该先创建存储对象——数组,因为数组存放的都是学生 ,所以数组类型为学生,数组进来了我们如何判断能不能存储呢?
- 现在我们要实现两个功能1:判断是否能存储2:如果能存储还要把数据存进去
- 思路:只要数组里还有空数据,则能存,所以需要遍历数组
- 要存数据,则需要知道哪一个位置是空的,所以需要一个索引值
- 还要返回是否存进去了,所以我们要根据这个索引值,来返回Boolean值
- 所以我们定义一个index为-1,假设都满了存不了
- 以下是代码实现
public class StudentDao {
Student[] stus = new Student[5];
public boolean addstudent(Student student) {
//定义索引值为-1,假设不存在
int index = -1;
//遍历数组
for (int i = 0; i < stus.length; i++) {
//如果找到了一个空位
if (stus[i] == null) {
//把i赋值给index;确定索引位
index = i;
//一找到就要跳出循环
break;
}
}
//不用写else 没有就 index的值就是-1
if ( index==-1) {
return false;
} else {
//进行存储
stus[index]=student;
return true;
}
}
}
2.2.2.2 添加功能的优化
- 优化目的,判断添加的学号是否已经存在于组中
- 操作步骤
- 客服接收到学生学号信息,交给业务员进行判断
- 业务员则需要库管提供的数据进行判断
- 判断的结果返回给客服,客服则根据这个结果反馈id的正确性
- 代码实现
- 1.客服接收到学生学号信息
在客服接收学号的时候创建一个死循环
while (true) {
System.out.println("请输入姓名学号");
String sid = sc.next();
//把学号传给业务员,调用业务员的方法
boolean flag = studentService.isExist(sid);
//根据业务员返回的值来进行判断
if (flag) {//存在
System.out.println("请重新输入");
} else {//不存在
System.out.println("输入成功");
break;
}
}
需要注意
1:由于studentService(业务员)的创建对象在下面,要上提此代码
· StudentService studentService = new StudentService();
2:由于添加了死循环,限制了sid学号的范围,下面封装学生数据的sid报错
student.setSid(sid);
解决方案:提高sid的级别,先定义,在赋值
String sid ;
while (true) {
System.out.println("请输入姓名学号");
sid = sc.next();
//把学号传给业务员,调用业务员的方法
boolean flag = studentService.isExist(sid);
//根据业务员返回的值来进行判断
if (flag) {//存在
System.out.println("请重新输入");
} else {//不存在
System.out.println("输入成功");
break;
}
}
- 2.编写业务员的业务逻辑
- 1接收库管返回的数组,同理也要用到库管,所以要提升库管等级
- 2根据数组判断sid是否存在,需要遍历数组,因为不需要赋值,所以无需像day01那样定义index
- 3.返回结果.
String sid;
while (true) {
System.out.println("请输入姓名学号");
sid = sc.next();
//把学号传给业务员,调用业务员的方法
boolean flag = studentService.isExist(sid);
//根据业务员返回的值来进行判断
if (flag) {//存在
System.out.println("请重新输入");
} else {//不存在
break;
}
}
- 3补充库管的返回代码
public Student[] retunAllStundet() {
//返回即可
return stus;
}
- 4测试发现无作业----寻找原因
- 我们每次调用StudentDao这个类时都会新建一个数组,都会新在内存中开辟一块新的空间
- 而我们则需要把数据都存储在一个数组中
- 解决方法:使用static关键字
- 把数组变为静态类
- 特点
- 随着类的加载而加载
- 被类的所有对象共享
- 可以通过类名调用,也可以通过对象名调用
public static Student[] stus = new Student[5];
//数组的创建更换为这个就行
2.2.2.3查看学生
- 需求:客服接收到指令,返回学生信息,若没有信息,则返回无成员
- 思路:客服接收,业余员调用库管给的数据,返回给客服
- 代码实现:
- 1客服接收
添加方法:findAllStudent();
创造方法 - 任务–需要向业余员要去返回数组:空数组null 或者真实数组
- 任务二-根据业余员的返回值,来进行人机交流
- 1客服接收
public void findAllStudent() {
//返回数组-----
//数组有两种情况 1 有数据正常返回 2 没有数据 返回值为null
Student[] stus = studentService.findAllStundent();
if (stus == null) {//判断数组里有没有数据
System.out.println("无信息,请先添加");
return;//结束方法
}
System.out.println("学号\t姓名\t年龄\t生日");
for (int i = 0; i < stus.length; i++) {
Student stu = stus[i];
if (stu != null) {
stu = stus[i];
System.out.println(stu.getSid() + "\t" + stu.getName() + "\t" + stu.getAge() + "\t" + stu.getBirthday());
}
}
}
- 2业务员逻辑判断
- 任务:1调取数据库的数组,2判断数组是不是空数组,并且返回给业务员
//查询学生
public Student[] findAllStundent() {
//向库管要数据
Student[] stu = studentDao.retunAllStundet();
//假设没有数据
boolean flag = false;
//遍历stu
for (int i = 0; i < stu.length; i++) {
//接收学生对象
Student student = stu[i];
if (student != null) {//找到了一个不为零的值,说明数组里面有数据源
flag = true;
break;//一个足以,跳出循环
}
}
//根据flag的值来进行返回数组的确定
if (flag) {//存在
return stu;//返回数组
} else {
return null;//没有 ,返回null
}
}
- 3库管的方法上文已经写过
//返回数组
public Student[] retunAllStundet() {
//返回即可
return stus;
}
}
2.2.2.4删除代码的实现
- 需求:用户输入要删除的id,进行删除,若没有要删除的则提示没有要删除的id,并重新输入,若数组没有数据,则提示先添加数据
- 思路
- 客服先要知晓数组里面有没有数据,但无需自己去做,让业务员返回给我
- 把要删除的id给业余员,业务员把此id给库管,由库管来删除(此处需要定义一个方法,早出输入id所在的索引,随后服织物null,则可以进行删除)
- 删除成功与否有业务员返回给用户
- 代码实现
- 1客服删除方法的添加
delayStundet();
- 2.删除方法的实现
- 1客服删除方法的添加
// 删除学生
public void delayStundet() {
//返回数组-----
//数组有两种情况 1 有数据进行删除 2 没有数据 返回值为null
Student[] stus = studentService.findAllStundent();
if (stus == null) {//判断数组里有没有数据
System.out.println("无信息,请先添加");
return;//结束方法
}
//数组不是null,进行删除操作
String sid;
while (true) {
System.out.println("请输入要删除的id");
sid = sc.next();
//把id给业务员,但业务员也无权修改数据库的信息
boolean flag = studentService.isExist(sid);
if (!flag) {//id不存在,则重新输入
System.out.println("id不存在,请重新输入");
} else {
break;
}
}
studentService.delayStundet(sid);
System.out.println("删除成功");
}
- 3业务员进行传递
public void delayStundet(String sid) {
studentDao.delayStundent(sid);
}
- 4,查询索引的方法,在库管中哦
//由于我们要查找sid所在的索引,先定义一个方法
public int findIndex(String sid) {
//假设不存在
int index = -1;
//遍历数组
for (int i = 0; i < stus.length; i++) {
//接收学生,但可能是null,需要进行判断
Student stu = stus[i];
if (stu != null && stu.getSid().equals(sid)) {//如果相同,则表示存在
index = i;
break;//跳出循环
}
}
return index;
}
- 5库管进行删除操作,
public void delayStundent(String sid) {
//调用方法,查找索引
int index = findIndex(sid);
stus[index] = null;
}
2.2.2.5修改功能的实现
- 需求 :根据学生id,来修改其他三个属性,没有id发出提示,数组没数据也发出对应提示
- 思路:与删除基本类似,直接代码实现
- 代码实现
- 1客服修改方法的添加
changeStudent();
- 2客服修改方法的添加
- 先进行数组是否为空的判断
- 循环要修改的id,
- 录入新的学生对象
- 把新的学生对象和学号传给业务员
- 业务员再给数组
- 由数组进行修改
- 1客服修改方法的添加
//返回数组-----
//数组有两种情况 1 有数据进行修改 2 没有数据 返回值为null
Student[] stus = studentService.findAllStundent();
if (stus == null) {//判断数组里有没有数据
System.out.println("无信息,请先添加");
return;//结束方法
}
String sid;
while (true) {
System.out.println("请输入要修改的id");
sid = sc.next();
//把id给业务员,但业务员也无权修改数据库的信息
boolean flag = studentService.isExist(sid);
if (!flag) {//id不存在,则重新输入
System.out.println("id不存在,请重新输入");
} else {
break;
}
}
//录入
System.out.println("请输入姓名");
String name = sc.next();
System.out.println("请输入年龄");
String age = sc.next();
System.out.println("请输入生日");
String birthday = sc.next();
//封装数据
//创造一个学生
Student student = new Student();
student.setSid(sid);
student.setName(name);
student.setAge(age);
student.setBirthday(birthday);
// //创建一个业务员
// StudentService studentService = new StudentService();
//把数据给业务员,进行数据的修改
studentService.changeStudent(sid,student);
System.out.println("修改成功");
}
- 3.业务员的传递
public void changeStudent(String sid, Student student) {
studentDao.changeStundetn(sid, student);
}
- 4.库管修改学生
public void changeStundetn(String sid, Student student) {
int index = findIndex(sid);
stus[index] = student;
}
到此学生系统的建设已经完毕
接下来要针对代码的重复等部分进行修改
2.2.2.6学生功能的优化——抽取成方法
- studentController中都需要进行输入学号的操作,对其进行方法的抽取,并微改输入语句,快捷键:ctrl+alt+m
- 这里要注意 添加方法中的判断语句是
if (flag) {//存在
System.out.println("请重新输入");
} else {//不存在
break;
}
与其相反,不能抽取为一个方法
//输入id
private String getString() {
String sid;
while (true) {
System.out.println("请输入id");
sid = sc.next();
//把id给业务员,但业务员也无权修改数据库的信息
boolean flag = studentService.isExist(sid);
if (!flag) {//id不存在,则重新输入
System.out.println("id不存在,请重新输入");
} else {
break;
}
}
return sid;
}
- studentController中添加学生和修改都需要进行学生封装的操作,对其进行方法的抽取
//封装学生
public Student getStudent(String sid) {
System.out.println("请输入姓名");
String name = sc.next();
System.out.println("请输入年龄");
String age = sc.next();
System.out.println("请输入生日");
String birthday = sc.next();
//封装数据
//创造一个学生
Student student = new Student();
student.setSid(sid);
student.setName(name);
student.setAge(age);
student.setBirthday(birthday);
return student;
}
2.2.3老师环境的搭建
- domain中的学生类直接复制粘贴到domain,随后改名
- 新建类。完成框架的建设
2.2.4系统的优化
2.2.4.1集成改进:优化原理:继承
- 把老师类和学生类向上提取成一个Person类,同时删除老师类和学生类中所有代码,然后idea自动生成构造方法
package domain;
public class Teacher extends Person {
public Teacher(String sid, String name, String age, String birthday) {
super(sid, name, age, birthday);
}
public Teacher() {
}
}
package domain;
public class Student extends Person{
public Student(String sid, String name, String age, String birthday) {
super(sid, name, age, birthday);
}
public Student() {
}
}
- 在封装学生的时候,我们是调用无参构造的方法,我们也可以调用有参构造的方法,由于我们的开发原则:对扩展内容开放,对修改内容关闭。我们新建一个OtherStudentController,并与StudentController一同继承于BaseStudentController,并且把entry中的StudentController换成OtherStudentController
- 删除子类与父类相同的代码逻辑,保留子类独有的代码逻辑
+父类更改要重写的方法,然后return
// 父类封装学生
public Student getStudent(String sid) {
return null;
}
OtherStudentController的封装学生方法
private Scanner sc = new Scanner(System.in);
//封装学生
public Student getStudent(String sid) {
System.out.println("请输入姓名");
String name = sc.next();
System.out.println("请输入年龄");
String age = sc.next();
System.out.println("请输入生日");
String birthday = sc.next();
//封装数据
//创造一个学生
Student student = new Student(sid,name,age,birthday);
return student;
}
StudentController的封装学生方法
public class StudentController extends BaseStudentController{
private Scanner sc = new Scanner(System.in);
//封装学生
public Student getStudent(String sid) {
System.out.println("请输入姓名");
String name = sc.next();
System.out.println("请输入年龄");
String age = sc.next();
System.out.println("请输入生日");
String birthday = sc.next();
//封装数据
//创造一个学生
Student student = new Student();
student.setSid(sid);
student.setName(name);
student.setAge(age);
student.setBirthday(birthday);
return student;
}
}
2.2.4.2抽象类改进:优化原理:抽象,finall
- 将BaseStudnet中封装学生的方法变成一个抽象方法,不改变的加上finall关键字,整个类变成抽象类
package controller;
import domain.Student;
import service.StudentService;
import java.util.Scanner;
public abstract class BaseStudentController {
// 创建一个业务员
private StudentService studentService = new StudentService();
//键盘录入创建对象
private Scanner sc = new Scanner(System.in);
public final void start() {
lo:
while (true) {
System.out.println("--------欢迎来到 <学生> 管理系统--------");
System.out.println("请输入您的选择: 1.添加学生 2.删除学生 3.修改学生 4.查看学生 5.退出");
String choic = sc.next();
switch (choic) {
case "1":
// System.out.println("添加");
//添加学生的方法
addStudent();
break;
case "2":
// System.out.println("删除");
delayStundet();
break;
case "3":
// System.out.println("修改");
changeStudent();
break;
case "4":
// System.out.println("查询");
//查询
findAllStudent();
break;
case "5":
System.out.println("感谢使用,再见");
break lo;
default:
System.out.println("您的输入有误, 请重新输入");
break;
}
}
}
//修改学生
private final void changeStudent() {
//返回数组-----
//数组有两种情况 1 有数据进行修改 2 没有数据 返回值为null
Student[] stus = studentService.findAllStundent();
if (stus == null) {//判断数组里有没有数据
System.out.println("无信息,请先添加");
return;//结束方法
}
//输入要修改的id
String sid = getString();
//封装学生
Student student = getStudent(sid);
// //创建一个业务员
// StudentService studentService = new StudentService();
//把数据给业务员,进行数据的修改
studentService.changeStudent(sid, student);
System.out.println("修改成功");
}
// 删除学生
public final void delayStundet() {
//返回数组-----
//数组有两种情况 1 有数据进行删除 2 没有数据 返回值为null
Student[] stus = studentService.findAllStundent();
if (stus == null) {//判断数组里有没有数据
System.out.println("无信息,请先添加");
return;//结束方法
}
//数组不是null,进行删除操作
//输入要删除的id
String sid = getString();
studentService.delayStundet(sid);
System.out.println("删除成功");
}
//查询学生
public final void findAllStudent() {
//返回数组-----
//数组有两种情况 1 有数据正常返回 2 没有数据 返回值为null
Student[] stus = studentService.findAllStundent();
if (stus == null) {//判断数组里有没有数据
System.out.println("无信息,请先添加");
return;//结束方法
}
System.out.println("学号\t姓名\t年龄\t生日");
for (int i = 0; i < stus.length; i++) {
Student stu = stus[i];
if (stu != null) {
stu = stus[i];
System.out.println(stu.getSid() + "\t" + stu.getName() + "\t" + stu.getAge() + "\t" + stu.getBirthday());
}
}
}
//添加学生
public final void addStudent() {
//提示输入信息
String sid;
while (true) {
System.out.println("请输入姓名学号");
sid = sc.next();
//把学号传给业务员,调用业务员的方法
boolean flag = studentService.isExist(sid);
//根据业务员返回的值来进行判断
if (flag) {//存在
System.out.println("请重新输入");
} else {//不存在
break;
}
}
//封装学生
Student student = getStudent(sid);
// //创建一个业务员
// StudentService studentService = new StudentService();
//把数据给业务员,我只要结果
boolean result = studentService.addstudent(student);
//根据结果result的值进行判断
if (result) {
System.out.println("输入成功");
} else {
System.out.println("输入失败");
}
}
//输入id
public String getString() {
String sid;
while (true) {
System.out.println("请输入id");
sid = sc.next();
//把id给业务员,但业务员也无权修改数据库的信息
boolean flag = studentService.isExist(sid);
if (!flag) {//id不存在,则重新输入
System.out.println("id不存在,请重新输入");
} else {
break;
}
}
return sid;
}
//封装学生
public abstract Student getStudent(String sid);
}
2.2.4.3代码块改进:优化原理:静态代码块
- 需求:初始化学生数据
- 步骤:在StudentDao中创建静态代码块,初始化学生的数据
- 代码如下
static {
//创建两个学生
Student student = new Student("1", "1", "1", "1");
Student student2 = new Student("2", "2", "2", "2");
//进行存储
stus[0]=student;
stus[1]=student2;
}
2.2.4.4集合改进:优化原理:集合
- 数组存储不方便,使用集合,但根据开发原则,不直接进行修改,创建一个新的类来实现
- 使用数组容器的弊端
- 容器长度是固定的,不能根据添加功能自动增长
- 没有提供用于赠删改查的方法
- 优化步骤
- 创建新的StudentDao类,OtherStudentDao
- 创建ArrayList集合容器对象
- OtherStudentDao中的方法声明,需要跟StudentDao保持一致
注意:如果不一致,StudentService中的代码就需要进行修改 - 完善方法(添加、删除、修改、查看)
- 替换StudentService中的Dao对象
- 代码实现
package dao;
import domain.Student;
import java.util.ArrayList;
public class OtherStudentDao {
static {
//创建两个学生
Student student = new Student("1", "1", "1", "1");
Student student2 = new Student("2", "2", "2", "2");
//进行存储
stus.add(student);
stus.add(student2);
}
public boolean addstudent(Student student) {
//一定有的位置,直接添加
stus.add(student);
return true;
}
//返回数组-----由于之后全是数组操作,所以要返回数组
public Student[] retunAllStundet() {
//返回数组
Student[] retunStus=new Student[stus.size()];
for (int i = 0; i < stus.size(); i++) {
retunStus[i]=stus.get(i);
}
return retunStus;
}
// 删除学号
public void delayStundent(String sid) {
//调用方法,查找索引
int index = findIndex(sid);
stus.remove(index);
}
//由于我们要查找sid所在的索引,先定义一个方法
public int findIndex(String sid) {
//假设不存在
int index = -1;
//遍历数组
for (int i = 0; i < stus.size(); i++) {
//接收学生,但可能是null,需要进行判断
Student stu = stus.get(i);
if (stu != null && stu.getSid().equals(sid)) {//如果相同,则表示存在
index = i;
break;//跳出循环
}
}
return index;
}
public void changeStundetn(String sid, Student student) {
int index = findIndex(sid);
stus.set(index,student);
}
}
2.2.4.5集合进行公共类的抽取:优化原理:继承,抽象
- 将方法向上抽取,抽取出一个父类 ( BaseStudentDao )
- 方法的功能实现在父类中无法给出具体明确,定义为抽象方法
- 让两个类分别继承 BaseStudentDao ,重写内部抽象方法
- 代码实现
package dao;
import domain.Student;
import java.util.ArrayList;
public abstract class BaseStudentDao {
public abstract boolean addstudent(Student student) ;
//返回数组-----由于之后全是数组操作,所以要返回数组
public abstract Student[] retunAllStundet();
// 删除学号
public abstract void delayStundent(String sid) ;
//由于我们要查找sid所在的索引,先定义一个方法
public abstract int findIndex(String sid);
public abstract void changeStundetn(String sid, Student student) ;
}
public class OtherStudentDao extends BaseStudentDao
public class StudentDao extends BaseStudentDao
2.2.4.6接口改进:优化原理:接口
- 实现步骤
- 将 BaseStudentDao 改进为一个接口
- 让 StudentDao 和 OtherStudentDao 去实现这个接口
- 代码实现
public abstract interface BaseStudentDao
public class OtherStudentDao implements BaseStudentDao
public class StudentDao implements BaseStudentDao
2.2.4.7 多态改进:优化原理:多态
- 引入,如今我们可以使用集合,也可以使用数组,那我们业务员StudntService中
调用库管也有两个方法
private StudentDao studentDao = new StudentDao();
和
private OtherStudentDao studentDao = new OtherStudentDao();
因为studentDao 和StudntService之间的耦合性太强,要解开耦合性,我们需要引入第三方变量
- 实现步骤
- 创建factory包,创建 StudentDaoFactory(工厂类)
- 提供 static 修改的 getStudentDao 方法,该方法用于创建StudentDao对象并返回
OtherStudentDao:通过静态访问的方式,固定右半语句
package factory;
import dao.OtherStudentDao;
import dao.StudentDao;
public class StudentDaoFactory {
public static OtherStudentDao getStudentDao() {
return new OtherStudentDao();
}
}
private OtherStudentDao studentDao = StudentDaoFactory.getStudentDao();
StudentDao:
package factory;
import dao.OtherStudentDao;
import dao.StudentDao;
public class StudentDaoFactory {
public static StudentDao getStudentDao() {
return new StudentDao();
}
}
private StudentDao studentDao = StudentDaoFactory.getStudentDao();
此时发现还是要改变StudntService的语句
- 此时引入多态
将工厂类中返回值类型改为父类的BaseStudentDao,这样既可以返回OtherStudentDao,也可以返回StudentDao,而无论返回什么,只要我用父类BaseStudentDao去接收,都能接受
public class StudentDaoFactory {
public static BaseStudentDao getStudentDao() {
return new OtherStudentDao();
// return new StudentDao();
}
}
BaseStudentDao类型 的接收
private BaseStudentDao studentDao = StudentDaoFactory.getStudentDao();
这应该是最后一个优化了
3知识点的引入
3.1继承
3.1.1引入
- 根据上面的案例,老师类和学生类有相同的属性,相同的方法放,具有共性,向上抽取,关键字extend,特点 :子类可以直接使用父类中非私有的成员
3.1.2优点
- 提高代码的复用性,维护性,并且让类与类产生了联系,是多态的前提
3.1.3缺点
- 继承是侵入性的,降低代码的灵活性:子类必须拥有父类非私有的属性和方法
增强了代码的耦合性
3.1.4使用场景
- 1 有共性 2:产生了is a 的关系 :例子 我是帅哥 ,你也是帅哥 ,提取帅哥为父类,
3.1.5继承特点
- 只支持单继承和多层继承(爷祖)
3.1.6继承的成员变量访问特点
- 就近原则
public class one {
public static void main(String[] args) {
zi zi = new zi();
zi.mothed1();
zi.mothed2();
zi.mothed3();
}
}
//fu类
class fu {
int a = 10;
}
//子类
class zi extends fu {
int a = 20;
public void mothed1() {
System.out.println(a+"就近原则");
}
public void mothed2() {
int a =30;
System.out.println(this.a+"this关键字返回本类成员变量");
System.out.println(a+"局部变量");
}
public void mothed3() {
System.out.println(super.a+"super返回父类");
}
}
3.1.6.1 this and super
- this&super关键字:
- this:代表本类对象的引用
- super:代表父类存储空间的标识(可以理解为父类对象引用)
- this和super的使用分别
- 成员变量:
- this.成员变量 - 访问本类成员变量
- super.成员变量 - 访问父类成员变量
- 成员方法:
- this.成员方法 - 访问本类成员方法
- super.成员方法 - 访问父类成员方法
- 成员变量:
- 构造方法:
- this(…) - 访问本类构造方法
- super(…) - 访问父类构造方法
3.1.7继承的成员方法访问特点
- 先去子类找 ,没有再去父类找
public class Two {
public static void main(String[] args) {
zi2 zi = new zi2();
zi.mothed2();
zi.mothed3();
}
}
//fu类
class fu2 {
//fu类方法
public void mothed() {
System.out.println("fu2类方法");
}
}
//子类
class zi2 extends fu2 {
//子类与父类同名方法
public void mothed() {
System.out.println("zi2类方法");
}
//子类中的方法通过super调用父类同名方法
public void mothed2() {
super.mothed();
}
// 默认有个this. -- 调用子类方法
public void mothed3() {
mothed();
}
}
3.1.8方法的重写
- 1、方法重写概念
- 子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)
- 2、方法重写的应用场景
- 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
//需求 升级手机功能
public class Three {
public static void main(String[] args) {
ipear2 i =new ipear2();
i.smallBlack();
}
}
//ipear1类-----一代
class ipear1 {
//打电话
public void call(String name) {
System.out.println("给" + name + "打电话");
}
//只会说英文
public void smallBlack() {
System.out.println("speak English");
}
}
//二代
class ipear2 extends ipear1 {
//进行重写
public void smallBlack() {
//调用父类的方法
super.smallBlack();
//添加自己的方法
System.out.println("speak Chinese");
}
}
3.1.8.1方法重写的注意事项
- 私有方法不能被重写(父类私有成员子类是不能继承的)
- 子类方法访问权限不能低于父类的访问权限
- 静态方法不能被重写,如果子类也有相同的方法,并不是重写的父类的方法,子类只是将父类的方法隐藏起来了
3.1.9权限修饰符
3.1.10继承中构造方法的访问特点(idea自动生成)
注意:子类中所有的构造方法默认都会访问父类中无参的构造方法
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()
问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
1. 通过使用super关键字去显示的调用父类的带参构造方法
2. 子类通过this去调用本类的其他构造方法,本类其他构造方法再通过super去手动调用父类的带参的构造方法
注意: this(…)super(…) 必须放在构造方法的第一行有效语句,并且二者不能共存
注意:super 会在堆内存开辟自己的存储空间来存放父类成员
3.2抽象类
3.2.1概述
- 抽象方法:父类中的某些方法不能明确具体实现方式,就需要定义成抽象方法,子类要重写父类中的抽象方法(完成方法体内容的编写)
- 抽象类:一个类中存在抽象方法,则该类必须声明为抽象类
- 如何定义抽象类和抽象方法
abstract
关键字
//抽象类的定义
public abstract class 类名 {}
//抽象方法的定义
public abstract void eat();
3.2.2抽象类的注意事项
-
抽象类中可以没有抽象方法,有抽象方法的类一定是抽象类
-
抽象类不能实例化(实例化就是创建对象)
-
抽象类可以有构造方法
- 但是无法创建对象
- 构造方法主要用于初始化自己成员变量,以供子类使用
-
抽象类的子类
- 要么重写父类所有的抽象方法(推荐)
- 要么自己也是一个抽象类(工作中可能会用到)
3.2.3模板设计模式
- 把抽象类整体就可以看做成一个模板,模板中不能决定的东西定义成抽象方法
让使用模板的类(继承模板抽象类的子类)去重写抽象方法实现需求,下面是一个写作文的模板
package Day02;
public abstract class Composition {
//模板
public void write() {
System.out.println("《我的dady》");
body();
System.out.println("哈哈哈");
}
//抽象主题方法
public abstract void body();
}
//创建子类重写抽象方法
class Tom extends Composition {
@Override
public void body() {
System.out.println("俺不会写作文");
}
}
//创建子类对象,调用父类方法
class test {
public static void main(String[] args) {
Tom tom=new Tom();
tom.write();
}
}
3.2.4 finall关键字
- 根据模板设计思想,模板的骨架最好不被重写,
- finall-最终
- 被其修饰的方法无法被子类重写
- 被其修饰的类不能拥有子类
- 被其修饰的变量不能再被赋值:基本数据类型–值 ;引用数据类型:地址值
- 如果要修饰成员变量,要注意初始化时机:
1.在定义final
修饰的变量(常量)的时候, 直接赋值(推荐)2. 在构造方法结束之前(白话:就是在构造方法中), 完成赋值
package com.itheima.mfinal;
public class TestFinal {
/*
final修饰变量:
基本数据类型变量: 其值不能被更改
引用数据类型变量: 地址值不能被更改, 但是可以修改对象的属性值
*/
public static void main(String[] args) {
// 常量的命名规范: 如果是一个单词, 所有字母大写, 如果是多个单词, 所有字母大写, 但是中间需要使用_分隔
final int A = 10;//A变量的值是不能被修改
// a = 10;
final int MAX = 10;
final int MAX_VALUE = 20;
final Student stu = new Student();
stu.setName("张三");
stu.setName("李四");
// stu = new Student();
}
}
class Student {
// final修饰成员变量 初始化时机
// 1. 在创建的时候, 直接给值
// 2. 在构造方法结束之前, 完成赋值
final int a = 10;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.3代码块
- 引出:系统调试不方便
- 目的:每当程序启动完毕以后,系统就初始化一部分学生数据
- 分类:局部代码块,构造代码块,静态代码块
3.3.1 局部代码块
- 位置:方法中定义
- 作用:限定变量的生命周期,及早释放,提高内存利用率
- 示例代码
public class Test {
/*
局部代码块
位置:方法中定义
作用:限定变量的生命周期,及早释放,提高内存利用率
*/
public static void main(String[] args) {
{
int a = 10;
System.out.println(a);
}
// System.out.println(a);
}
}
3.3.2 构造代码块
- 位置:类中方法外定义
- 特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
- 作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
- 示例代码
public class Test {
/*
构造代码块:
位置:类中方法外定义
特点:每次构造方法执行的时,都会执行该代码块中的代码,并且在构造方法执行前执行
作用:将多个构造方法中相同的代码,抽取到构造代码块中,提高代码的复用性
*/
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(10);
}
}
class Student {
{
System.out.println("好好学习");
}
public Student(){
System.out.println("空参数构造方法");
}
public Student(int a){
System.out.println("带参数构造方法...........");
}
}
3.3.3 静态代码块
- 位置:类中方法外定义
- 特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
- 作用:在类加载的时候做一些数据初始化的操作
- 示例代码
public class Test {
/*
静态代码块:
位置:类中方法外定义
特点:需要通过static关键字修饰,随着类的加载而加载,并且只执行一次
作用:在类加载的时候做一些数据初始化的操作
*/
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person(10);
}
}
class Person {
static {
System.out.println("我是静态代码块, 我执行了");
}
public Person(){
System.out.println("我是Person类的空参数构造方法");
}
public Person(int a){
System.out.println("我是Person类的带...........参数构造方法");
}
}
3.4接口
3.4.1接口的引出
- 根据2.2.4.5的改进,出现了一个全是抽象方法的类,
- 接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。
3.4.2接口的意义
- 用来定义规范
- 用来做功能的拓展
3.4.3接口的特点
- 接口用关键字interface修饰
public interface 接口名 {}
- 类实现接口用implements表示
public class 类名 implements 接口名 {}
接口不能实例化,我们可以创建接口的实现类对象使用
- 接口的子类
要么重写接口中的所有抽象方法
要么子类也是抽象类
3.4.4接口中成员特点
- 成员变量
只能是常量
默认修饰符:public static final - 构造方法
没有,因为接口主要是扩展功能的,而没有具体存在 - 成员方法
只能是抽象方法
默认修饰符:public abstract
关于接口中的方法,JDK8和JDK9中有一些新特性- JDK8:用default可以定义非抽象方法,用static定义静态方法,静态方法通过类名 . 的方式调用,注意default和static只能有一个
- JDK9:允许接口中定义private私有的方法,(private和static会吗,默认带上default,反正就是不用写default)
3.4.4类与接口的关系
- 类与类的关系
继承关系,只能单继承,但是可以多层继承 - 类与接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口(亲爹更亲,类和接口有一样的方法,执行类中的方法) - 接口与接口的关系
继承关系,可以单继承,也可以多继承(如果继承的接口有重名且方法体不一样的方法,需要重写该方法)
3.5多态
3.5.1多态的概念
同一个对象,在不同时刻表现出来的不同形态
3.5.2多态的前提
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
3.5.3多态中成员访问的特点
-
成员访问特点
-
成员变量
编译看父类,运行看父类
-
成员方法
编译看父类,运行看子类,父类中要有方法,不然会编译失败,运行则运行子类中重写的方法
-
代码展示
-
class Fu {
int num = 10;
public void method(){
System.out.println("Fu.. method");
}
}
class Zi extends Fu {
int num = 20;
public void method(){
System.out.println("Zi.. method");
}
}
public class Test2Polymorpic {
/*
多态的成员访问特点:
成员变量: 编译看左边 (父类), 运行看左边 (父类)
成员方法: 编译看左边 (父类), 运行看右边 (子类)
*/
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);
f.method();
}
}
3.5.4多态的好处与弊端
- 好处
提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作 - 弊端
不能使用子类的特有成员,因为要先编译,如果父类中没有调用的方法,编译会失败
3.5.5多态的转型及其风险
-
向上转型
父类引用指向子类对象就是向上转型
-
向下转型
格式:子类型 对象名 = (子类型)父类引用;
3.5.5多态的风险(解决向下转型的风险)
如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
- 解决方案
- 关键字
instanceof - 使用格式
变量名 instanceof 类型
通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果
- 关键字
//父类 动物
abstract class Animal {
public abstract void eat();
}
//狗继承了动物类 ,除了吃还能看家
class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void watchHome(){
System.out.println("看家");
}
}
// 猫也继承了动物
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
//测试类
public class Test4Polymorpic {
public static void main(String[] args) {
//创建了一条狗,调用方法
useAnimal(new Dog());
//创建了一只猫,调用方法
useAnimal(new Cat());
}
public static void useAnimal(Animal a){
// Animal a = new Dog();
// Animal a = new Cat();
//这个方法可以调用父类动物所有子类对象
a.eat();
//a.watchHome();
// Dog dog = (Dog) a;
// dog.watchHome(); // ClassCastException 类型转换异常
// 判断a变量记录的类型, 是否是Dog
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
}
}
总结
- 差不多就是根据java语言思想做的小项目,模拟现实生活的处理方式,通过继承,抽象,接口,多态等特性来优化,维护代码。
- 吾初学java,此当成笔记来记录,若有错误望包涵;若有建议望提出;若有小用望点赞哦,码字不易,小金叹气。
- 最后附上今天看到的一句话,很多失败不是因为能力有限,而是因为努力没有到底
- 同时也祝看到最后的小伙伴身体健康,万事如意