需求:
完成以下功能
实体类:学生类(成员变量有id, 姓名,年龄,性别,成绩)
需要使用数组保存学生信息: Student[] allStus
需要完成的方法
1. 根据学生的ID,找到对应的学生对象
2. 完成方法,添加新学生
3. 完成方法,删除指定ID的学生
4. 完成方法,展示数组中所有的学生信息
5. 根据学生成绩,完成降序排序
包名规范:
包结构的使用是为了在开发中让代码结构更加明确,更好管理,会接触到MVC设计模式(MVC ==> Module Viewer Controller)
目前学生管理系统需要的包【目前功能所需】
实体类:所有的实体类都会在一个包下
管理类:需要一个管理类来管理学生操作(核心)需要一个包
主方法类:主方法
测试类:测试功能,养成习惯,对于代码中的功能,写一个测试一个,今天会用到@Test
包名
com.qfedu.student.system.
– | entity 实体类包
– | manager 管理类包
– | mainproject 主方法所在包
– | testsystem 测试类
package com.qfedu.student.system.entity;
/**
* 学生实体类
* @author Mr_SunJH
*
*/
public class Student {
private int id;
private String name;
private int age;
private char gender;
private int score;
public Student() {}
public Student(int id, String name, int age, char gender, int score) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
/**
* 使用System.out.println打印展示Student类对象时
* 是直接调用toString方法,展示该方法返回String字符串内容
*/
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender + ", score=" + score
+ "]";
}
}
管理类功能:
数据的保存
明确使用的是一个Student类的数据:Student[] allStus;
作为成员变量,还是静态成员变量问题:
(1)成员变量:使用一个成员变量数组来保存对应的数据,所有的内容都依赖于类对象的操作来完成,而且每一个管理类对象中保存的数据都是不一样的。目前的不一样,是为了更好的复用性,后面的数据一样,是为了统一性。【选用成员变量】
(2)静态成员变量:不管有多少个类对象,当前数据有且只有一份。存在复用问题,因为当前管理类,在后期功能是考虑复用的,不管是数据的存储方式,数据的处理方式,都是需要来考虑满足多种情况,多种方式。
数据处理:CRUD 增删改查
因为当前管理类内成员变量是一个数组,当前构造方法需要对于保存学生信息的数组进行初始化操作
(1)传入参数是一个学生数组【不选】
(2)传入参数是一个数组容量 √
分析:
(1)传入一个数组,操作性、安全性都是存在一定的隐患,操作性较差,用户需要提供真实管理数据的空间,繁琐,引用指向有可能导致数据丢失
(2)传入一个数组容量,用户操作的自由度更高,方便,快捷,省心省事
要求传入的参数是一个数组容量,有没有要求???
(1)int
(2)非负
(3)int类型最大值
【补充】Java中数组容量是在int范围以内,要求数组容量不得超出int范围 Integer.MAX_VALUE - 8(为什么-8 后面解释)
构造方法这里提供两种
(1)无参数构造方法:用户不用指定容量,我们给予用户一个初始化容量使用
(2)有参数构造方法:用户指定底层数组容量,要求数据在合理范围以内
package com.qfedu.student.system.manager;
import com.qfedu.student.system.entity.Student;
/**
* 学生管理类
* @author Mr_SunJH
*
*/
public class StudentManager {
/*
* 私有化保存学生信息的数组,对外不能公开,有且只针对与当前管理类使用
*/
private Student[] allStus = null;
// DEFAULT_CAPACITY 默认容量,这里是一个带有名字的常量
private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/*
* 无参数构造方法,但是需要创建底层保存学生数据的Student数组,因为当前数组
* 不存在,指向为null
*
* 默认初始化容量为10,10是一个常量数据,操作阅读不太方便
*/
public StudentManager() {
allStus = new Student[DEFAULT_CAPACITY];
}
/**
* 用户指定初始化容量,但是要求初始化容量是在合理范围内,不能小于0,
* 不能大于数组的最大值MAX_ARRAY_SIZE
* @param initCapacity 用户指定的初始化容量
*/
public StudentManager(int initCapacity) {
if (initCapacity < 0 || initCapacity > MAX_ARRAY_SIZE) {
System.out.println("Input Parameter is Invalid!");
/* 异常抛出!!!补充该知识点,暂时留下一个System.exit(0) 退出程序 */
System.exit(0);
}
allStus = new Student[initCapacity];
}
}
分析:
权限修饰符:
public √
private 如果私有化,类外无法使用,不能操作数据
是否需要static修饰
不需要
保存学生信息的数组是static修饰还是非static修饰?非static修饰
如果当前方法使用static修饰,是没有办法操作类内的成员变量
返回值类型:boolean类型。添加成功返回true,添加失败返回false
方法名:add 添加
形式参数列表:Student student
方法声明
public boolean add(Student student)
采用尾插法数据存入,需要一个计数器:int类型数据
如果作为局部变量:方法运行结束,不存在,无法保存数据
如果作为成员变量:可以保证每一个StudentManager对象中存储的数据是独立的
如果作为静态成员变量:静态成员变量和类对象无关,而且独此一份,每一个StudentManager对象中保存的数据个数都是独立的,是不合理的
/**
* 当前方法是添加学生类对象到StudentManager中,保存到底层的Student类型数组
*
* @param stu Student类对象
* @return 添加成功返回true,失败返回false
*/
public boolean add(Student stu) {
allStus[size] = stu;
size += 1;
return true;
}
目前存在问题
(1)好像没有错误情况处理
需要异常处理来辅助完成错误或者异常处理
(2)容量不够怎么办?
扩容!!!
考虑:①数组的容量在创建之后是无法修改的;
②重新创建一个数组!新数组要求比原本的数组容量大;
③从原数据数组迁移数据到新数组中;
④重新赋值成员变量allStus保存的空间地址指向新数组。
流程:
方法分析:
(1)权限修饰符
public :类外可以随便使用,数组容量不受控制,我们期望的是在数组添加元素的时候容量不足的情况下,才会调用当前grow方法,public不合适
private : 核心操作,主要是在类内的使用,类外不能调用当前方法
(2)是否需要static修饰
因为需要操作类内的底层数组,数组非static修饰,不需要使用static
(3)返回值类型:void 当前方法不需要返回值
(4)方法名:grow
(5)形式参数列表
要求最小容量
为了保证add操作在合理的容量范围以内操作成功,这里要求通过add方法告知grow方法,最小要求容量扩容到多少?
需要考虑一些极端情况:
①添加多个,超出了grow增加的范围
②原数组容量为1,扩容之后不够用
方法声明
private void grow(int minCapacity);
/**
* 类内私有化方法,用于在添加元素过程中,出现当前底层数组容量不足的情况下
* 对底层数组进行扩容操作,满足使用要求
* @param minCapacity 添加操作要求的最小容量
*/
private void grow(int minCapacity) {
// 1. 获取元数组容量
int oldCapacity = allStus.length;
// 2. 计算得到新数组容量,新数组容量大约是原数组容量的1.5倍
int newCapacity = oldCapacity + oldCapacity / 2;
// 3. 判断新数组容量是否满足最小容量要求
if (minCapacity > newCapacity) {
newCapacity = minCapacity;
}
// 4. 判断当前容量是否超出了MAX_ARRAY_SIZE
if (newCapacity > MAX_ARRAY_SIZE) {
// 这里需要一个异常处理,目前我们采用程序退出
System.exit(0);
}
// 5. 创建新数组
Student[] temp = new Student[newCapacity];
// 6. 数据拷贝
for (int i = 0; i < oldCapacity; i++) {
temp[i] = allStus[i];
}
// 7. 使用 allStus 保存新数组首地址
allStus = temp;
}
方法分析
(1)权限修饰符
public √
private【不合适】
(2)是否需要 static
不需要
(3)返回值类型
boolean类型 删除成功返回true,删除失败返回false
(4)方法名:remove 移除
(5)形式参数列表:int id
方法声明
public boolean remove(int id)
流程
/**
* 根据用户指定的id号删除对应学生类对象
* @param id 指定学生id
* @return 删除成功返回true,删除失败返回false
*/
public boolean remove(int id) {
int index = -1;
// 遍历数组的终止条件为size,有效元素个数
for (int i = 0; i < size; i++) {
if (id == allStus[i].getId()) {
index = i;
break;
}
}
/*
* 以上代码循环结束,如果index的值为-1,说明没有找到对应的元素
* 当前方法无法进行删除操作,返回false
*/
if (-1 == index) {
System.out.println("Not Found!");
return false;
}
/**
* 如果 index 不是 -1 表示找到了对应需要删除的元素,进行删除操作
*
* 假设元数组容量为10,有效元素个数为10,删除下表为5的元素
* 数组[5] = 数组[6];
* 数组[6] = 数组[7];
* 数组[7] = 数组[8];
* 数组[8] = 数组[9];
* 数组[9] = null;
*
* 数组[i] = 数组[i + 1];
*/
for (int i = index; i < size; i++) {
allStus[i] = allStus[i + 1];
}
// 原本最后一个有效元素位置赋值为null
allStus[size - 1] = null;
size -= 1;
return true;
}
方法分析
(1)权限修饰符
public √
private
(2)是否需要static修饰:不需要
(3)返回值类型:Student 学生类对象
(4)方法名 get 获取
(5)形式参数列表:int id
方法声明
public Student get(int id)
/**
* 根据指定的ID获取对应的Student类对象
*
* @param id 指定的ID号
* @return 返回对应的Student类对象, 如果没有找到,返回null
*/
public Student get(int id) {
int index = findIndexById(id);
return index > -1 ? allStus[index] : null;
}
方法分析
(1)权限修饰符
public
private √ 给内部其他方法使用的一个功能,不需要对外
(2)是否需要static修饰:不需要
(3)返回值类型:int 类型, 需要的是一个下标
(4)方法名:findIndexById
(5)形式参数列表 int id
方法声明
public int findIndexById(int id)
/**
* 类内私有化方法,用于根据指定ID获取对应的下标位置,提供给其他方法使用
*
* @param id 指定ID号
* @return 返回值是对应的下标位置,返回值大于等于0 表示找到对应元素,返回-1没有找到
*/
private int findIndexById(int id) {
int index = -1;
for (int i = 0; i < size; i++) {
if (id == allStus[i].getId()) {
index = i;
break;
}
}
return index;
}
需求:
真实修改数组中保存的学生对象数据
需要我们找到对应学生对象,修改其中指定的数据
方法分析:
(1)权限修饰符:public
(2)是否需要static修饰:不需要
(3)返回值类型:
① Student
② boolean:监测方法运行状态,如果出现问题,返回false
(4)方法名:modify
(5)形式参数列表:int id
方法声明:
public boolean modify(int id)
【static补充】
①static修饰成员变量的共享性和持久性
②static修饰静态代码块的加载行和初始化操作
③static修饰静态成员方法操作的便捷性和独立性
/**
* 根据用户指定的ID号,修改对应学生的信息
*
* @param id 用户指定的ID号
* @return 方法运行成功返回true,如果修改无法完成返回false
*/
public boolean modify(int id) {
// 通过类内其他成员方法获取对应ID的学生类对象
Student stu = get(id);
// 没有对应的学生类对象
if (null == stu) {
System.out.println("Not Found");
return false;
}
// 类似于点菜系统,需要完成修改操作
Scanner sc = new Scanner(System.in);
int choose = 0;
while (choose != 5) {
System.out.println("ID: " + stu.getId());
System.out.println("Name: " + stu.getName());
System.out.println("Age: " + stu.getAge());
System.out.println("Gender: " + stu.getGender());
System.out.println("Score: " + stu.getScore());
System.out.println("1. 修改学生姓名");
System.out.println("2. 修改学生年龄");
System.out.println("3. 修改学生性别");
System.out.println("4. 修改学生成绩");
System.out.println("5. 退出");
choose = sc.nextInt();
// 1\n ==> 1 程序中剩余\n
sc.nextLine();
switch (choose) {
case 1:
System.out.println("请输入学生的姓名:");
String name = sc.nextLine();
stu.setName(name);
break;
case 2:
System.out.println("请输入学生的年龄:");
int age = sc.nextInt();
stu.setAge(age);
break;
case 3:
System.out.println("请输入学生的性别:");
char gender = sc.nextLine().charAt(0);
stu.setGender(gender);
break;
case 4:
System.out.println("请输入学生的成绩:");
int score = sc.nextInt();
stu.setScore(score);
break;
case 5:
System.out.println("保存退出");
break;
default:
System.out.println("选择错误,请重新选择");
break;
}
}
return true;
}
需求:
根据学生的成绩,降序排序
方法分析
(1)权限修饰符:public
(2)是否需要static修饰?不需要
(3)返回值类型:void
(4)方法名:scoreSortDesc
(5)形式参数列表:不需要参数
方法声明
public void scoreSortDesc();
/**
* 按照成绩降序排序的算法
*/
public void scoreSortDesc() {
/*
* 排序算法操作不能在原数据数组中进行直接操作,需要另外准备
* 一个用于排序的临时数组,这里就涉及到一个数据拷贝过程。
*/
Student[] sortTemp = new Student[size];
for (int i = 0; i < sortTemp.length; i++) {
sortTemp[i] = allStus[i];
}
for (int i = 0; i < sortTemp.length - 1; i++) {
// 找出极值
int index = i;
for (int j = i + 1; j < sortTemp.length; j++) {
// 按照成绩降序排序
if (sortTemp[index].getScore() > sortTemp[j].getScore()) {
index = j;
}
}
// 交换位置
if (index != i) {
Student stu = sortTemp[index];
sortTemp[index] = sortTemp[i];
sortTemp[i] = stu;
}
}
for (int i = 0; i < sortTemp.length; i++) {
System.out.println(sortTemp[i]);
}
}
package com.qfedu.student.system.mainproject;
import java.util.Scanner;
import com.qfedu.student.system.entity.Student;
import com.qfedu.student.system.manager.StudentManager;
public class MainProject {
public static void main(String[] args) {
StudentManager stm = new StudentManager();
for (int i = 0; i < 15; i++) {
Student student = new Student();
student.setId(i + 1);
student.setName("测试" + i);
student.setGender(Math.random() > 0.5 ? '男' : '女');
// 随机数
student.setAge((int) (Math.random() * 100));
student.setScore((int) (Math.random() * 100));
stm.add(student);
}
Scanner sc = new Scanner(System.in);
int choose = 0;
int id = 0;
while (choose != 7) {
System.out.println("1. 添加新学生");
System.out.println("2. 删除指定ID的学生");
System.out.println("3. 修改指定ID的学生");
System.out.println("4. 查询指定ID的学生");
System.out.println("5. 查询所有学生");
System.out.println("6. 按照成绩降序排序");
System.out.println("7. 退出");
choose = sc.nextInt();
sc.nextLine();
switch (choose) {
case 1:
/*
* 大家自己完成提供给用户一些输入提示,完成添加操作
*/
stm.add(new Student(100, "骚磊", 16, '男', 119));
break;
case 2:
System.out.println("请输入需要删除学生的ID号");
id = sc.nextInt();
stm.remove(id);
break;
case 3:
System.out.println("请输入需要修改信息的学生ID号");
id = sc.nextInt();
stm.modify(id);
break;
case 4:
System.out.println("请输入需要查询信息的学生ID号");
id = sc.nextInt();
Student student = stm.get(id);
if (null == student) {
System.out.println("查无此人");
} else {
System.out.println(student);
}
break;
case 5:
stm.showAllStudents();
break;
case 6:
stm.scoreSortDesc();
break;
case 7:
System.out.println("程序退出...");
break;
default:
System.out.println("选择错误");
break;
}
}
}
}
数据持久化保存
IO操作文件内容,String字符串解析
Sout界面分离
除了viewer层,其他任何代码中不能出现任何一个sout
排序算法优化
可以让排序方法能够根据用户指定条件呢进行排序操作
自定义异常添加
优化代码中错误提示和处理过程
数据根据条件过滤展示
根据用户指定条件,过滤展示对应的数据