站点图标 AI技术聚合

【Java】图书管理系统

图书管理系统

  • 程序要求
  • 程序书写
    • 大体思路
    • 框架实现
      • 存储系统
      • 操作系统
      • 用户
      • 系统运行
      • 问题修复
    • 具体实现
      • 初始化存储系统
      • 操作细节实现
        • 添加书籍
        • 展示书籍
        • 查找书籍
        • 借阅书籍
        • 修改书籍
        • 删除书籍
        • 系统销毁
  • 代码总览

程序要求

书写一个图书管理系统,其中要包含以下功能:借阅书籍、浏览书籍、查找书籍、添加书籍、删除书籍、修改书籍

(书写这个程序主要是为了巩固我们之前学习的基础语法,因此在这个程序书写的过程中我们将尽量运用我们之前学的大部分知识。同时值得一提的是,这篇文章的所有部分都只不过是提供一个思路,并非最优解。因此,如果在阅读过程中有了自己的想法欢迎自己上手去实现)

程序书写

大体思路

我们学类和对象的时候说过,在面向对象编程中,一切皆对象,那么我们能否将这个图书管理系统看作是多个对象交互的结果呢?类比我们之前讲过的洗衣服的例子,那时候我们说现在洗衣服被看作是,衣服,洗衣液,洗衣机这四个对象完成的,那么我们的图书管理系统的交互是不是就可以看作是通过系统完成的。

其中可以进行细分,例如细分为管理员普通用户,并且分别执行不同的操作。系统也可以进行细分,细分为操作系统存储系统,一个用来提供各类操作,另一个用来存储信息。

但是的细分和系统的细分还不太一样,即便划分为了管理员普通用户也是有共通性的,比如都可以查看书籍、查找书籍,但同时也有不同性,比如管理员才可以添加书籍、删除书籍、修改书籍,普通用户才能借阅书籍。因此即便进行了细分,依旧可以看作是一个对象

系统划分为操作系统存储系统后,两者几乎没有共同性,完全可以看作是两个对象

总而言之,我们抽象出了三个对象用户(人)操作系统存储系统,而图书系统交互的实现就可以看作是用户通过操作系统存储系统进行交互

框架实现

首先我们为了存放对象的各种细节,我们先创建三个包,分别为为operate操作系统、stroage存储系统和user用户,另外还需要一个主类存放main方法作为程序的起点

存储系统

既然是存储书,我们肯定要先定义一个书类来存储书存储在这个系统中的各种信息,比如书名、作者、类型和是否被借出,并且这些信息我们不希望直接公开,因此写的时候用private修饰

因此先这样写

public class Book{
    //书的信息:名字 作者 种类 是否被借出
    private String name;
    private String author;
    private String type;
    private boolean isBorrowed;
    
	//下面的getter()和setter()通过IDEA自动生成,借助Alt+Insert快捷键可以看到
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public boolean getIsBorrowed() {
        return isBorrowed;
    }

    public void setIsBorrowed(boolean borrowed) {
        isBorrowed = borrowed;
    }

}

那么就先写到这里,后续遇到其他需要我们继续加代码即可

那么既然要存很多书,那么我们不妨再创建一个类,用于存放多本书,那么就可以借助数组,同时开辟一个变量用来存储有效书籍的数量,用于访问书籍

public class BookList {
    //设定一个默认存储大小
    public static final int DEFAULT_SIZE = 5;

    //书的数量和存储书的数组
    public int bookNum;
    public Book[] bookList = new Book[DEFAULT_SIZE];

}

这里由于之后操作系统中需要经常用到bookListbookNum因此直接将他们公开,故使用public修饰

操作系统

操作也就对应要求中的各个操作:借阅书籍、浏览书籍、查找书籍、添加书籍、删除书籍、修改书籍。我们对应每一个操作创建一个类即可,我们这里也不先实现具体的操作方法,只打印一下对应的操作文字

以添加书籍的类为例

public class Add {
    public void add(){
        System.out.println("添加书籍");
    }
}

这里我们还要多增加一个类,销毁操作的类,当我们退出系统前将数据进行销毁。可能有人觉得这个操作很多余,但是假设这个程序只是一个大程序的一个小分支,小分支的结束并不代表整个程序的结束,那么此时我们如果不进行清理的话就可能会导致内存的泄露(也就是程序占用着内存,但是我们由于操作不当,导致我们无法对这块内存进行操作,那么这块内存就是被无效占用的,这样的情况就被称作内存泄露)。

那么就先写成这样

public class Destroy{
    public void destroy() {
        System.out.println("销毁数据");
    }
}

但这样也不完全行,我们这么多类,难道每进行一次不同的操作都要通过不同的方法吗?

比如下面的两个方法,分别调用删除和添加的方法

public void operate(Add a){
    a.add();
}
public void operate(Delete d){
    d.delete();
}

这明显有点麻烦,并且如果这样的话没有写成类的必要了。那么我们有没有方法去借助多态的思想,来让我们传入不同对象的时候就直接执行不同操作呢?

我们可以通过定义一个Operate接口,接口内部有一个operate()方法

public interface Operate {
    void operate();
}

然后让这些操作都实现这个接口,并且结合它们的不同作用重写operate()

public class Add implements Operate{
    @Override
    public void operate() {
        System.out.println("添加书籍");
    }
}

最后我们就可以将这个接口类型作为函数参数,然后调用operate()方法。这样在未来我们直接通过一个方法就可以执行多个操作,而不是一个类就要一个方法去执行一个操作

public void operate(Operate o){
    o.operate();
}

至于为什么定义成接口而不是抽象类,因为这个Operate接口只用来描述这些类可以执行operate()操作,并没有任何属性,因此没有写成类的必要

用户

用户我们刚刚划分为了有共同点并且也有区别的管理员普通用户,因此我们这里就可以运用多态的思想,先定义一个用户User的抽象类,然后定义Admin类和GeneralUser类继承User

public abstract class User {
    //设置用户名
    private String name;
    
    //打印界面
    public abstract void menu();

    //执行操作
    public abstract void operate();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

首先定义一个用户名name,由于到时候我们只会用在菜单的打印上所以先用private修饰。然后定义两个抽象方法,一个是用于打印界面的,一个是用于调用operate包里面的操作的

然后就是在两个具体类里面继承这个抽象类并重写里面的方法,我们先以Admin类为例

首先先写一个构造方法,将用户名name设定

    public Admin(String name) {
        super.setName(name);
    }

然后是简单的菜单打印menu()方法的书写

    public void menu() {
        System.out.println("***************************************");
        System.out.println("Hello Admin " + super.getName() + " 欢迎来到图书管理系统");
        System.out.println("请问您要执行什么操作");
        System.out.println("1.查看图书");
        System.out.println("2.增加图书");
        System.out.println("3.删除图书");
        System.out.println("4.修改图书");
        System.out.println("5.查找图书");
        System.out.println("0.退出系统");
        System.out.println("***************************************");
    }

那么现在问题就是如何将上面这个界面的选项实现到代码中,这种涉及到具体选项的多分支情况,我们可以考虑使用switch-case语句,那么可以先写成这样

    @Override
    public void operate() {
        Scanner sc = new Scanner(System.in);
        int input = sc.nextInt();
            //清理回车
            sc.nextLine();
            switch (input){
                case 1:
                    new Show().operate();
                    break;
                case 2:
                    new Add().operate();
                    break;
                case 3:
                    new Delete().operate();
                    break;
                case 4:
                    new Modify().operate();
                    break;
                case 5:
                    new Search().operate();
                    break;
                case 0:
                    new Destroy().operate();
                    break;
                default:
                    System.out.println("非法输入,请重新输入");
                    break;
            }
        }

另外也可以考虑使用接口类型的数组,因为虽然接口不能实例化为对象,但是接口类型可以接收所有实现了对应接口的对象,这样可以通过多态的思想大量简化代码

由于这个接口类型的数组,也是两个共有但是内部细节不同的,因此我们也把它放到User里面,然后在实例化GeneralUserAdmin的时候再初始化这个数组

因此我们先给User里面加一个

//存储操作类型的数组
public Operate[] operateList;

然后将Admin的构造方法改为

    //构造方法
    public Admin(String name) {
        super.setName(name);
        operateList = new Operate[]{
                new Destroy(),
                new Show(),
                new Add(),
                new Delete(),
                new Modify(),
                new Search()
        };
    }

然后把在Admin里面的operate()方法改为访问数组元素后调用这个类型内部的operate()方法

    @Override
    public void operate() {
        Scanner sc = new Scanner(System.in);
        int input = sc.nextInt();
        sc.nextLine();
        try {
            operateList[input].operate();
        } catch (IndexOutOfBoundsException e) {
            System.out.println("非法输入,请重新输入");
        }
    }

由于可能会发生数组越界,我们这里使用try-catch语句捕捉这个异常,然后输出提示一下

另外这里不关闭Scanner的原因是,我们如果在一个程序各个类里面中需要多次用到Scanner,即便需要创建多次,但是也只需要关闭一次。那么我们只需要在程序结束前将其关闭即可,多次关闭会导致一些问题发生,后面会在Destroy类里面关闭

GeneralUser类我们就根据需求模仿这Admin类写即可

public class GeneralUser extends User{
    //构造方法
    public GeneralUser(String name) {
        super.setName(name);
        operateList = new Operate[]{
                new Destroy(),
                new Show(),
                new Borrow(),
                new Search()
        };
    }
    //打印菜单
    @Override
    public void menu() {
        System.out.println("***************************************");
        System.out.println("Hello User " + super.getName() + " 欢迎来到图书管理系统");
        System.out.println("请问您要执行什么操作");
        System.out.println("1.查看图书");
        System.out.println("2.借阅图书");
        System.out.println("3.查找图书");
        System.out.println("0.退出系统");
        System.out.println("***************************************");
    }
    //选择并执行操作
    @Override
    public void operate() {
        Scanner sc = new Scanner(System.in);
        int input = -1;
        while(input != 0){
            input = sc.nextInt();
            sc.nextLine();
            try {
                operateList[input].operate();
            }catch (IndexOutOfBoundsException e){
                System.out.println("非法输入,请重新输入");
            }
        }
    }
}

系统运行

系统的主要运行逻辑我们就在Main类里面实现,首先肯定是用户选择自己的身份,是GeneralUser还是Admin,这个选择逻辑非常简单,直接上代码

    private static User login() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名");
        String name = sc.nextLine();
        while(true){
            System.out.println("请选择用户类别:1.普通用户 2.管理员");
            int choice = sc.nextInt();
            //清理回车
            sc.nextLine();
            if(choice == 1){
                return new GeneralUser(name);
            } else if (choice == 2) {
                return new Admin(name);
            }else {
                System.out.println("输入错误,请重新输入");
            }
        }
    }

然后我们在main()方法里面调用这个方法,然后再利用一个循环调用user里面的menu()operate()

    public static void main(String[] args) {
        User user = login();
        while(true){
            user.menu();
            user.operate();
        }
    }

那么这个死循环要怎么结束呢?我们只需要在Destroy操作中加入一个结束程序的代码,那么当我们选择运行Destroy里的operate()时程序即可结束

上面说我们要在这里关闭Scanner,所以我们这里也顺便把Scanner关了

public class Destroy implements Operate{
    @Override
    public void operate() {
        System.out.println("销毁数据");
        Scanner sc = new Scanner(System.in);
        sc.close();
        //退出程序
        System.exit(0);
    }
}

其中这个数字我们其实也经常看到,正常退出的话就输出0就行

问题修复

上面的逻辑本身是没有问题的,但是有一个比较小的漏洞。当我们输入的时候如果输入的不是int类型的数字,就会抛出InputMismatchException异常,原因就是我们输入的数据和要接受的int类型不匹配

那么怎么办呢?

方法一:用try-catch捕捉这个异常,然后输出提示

我们以Main类里面的login()方法为例子演示一下

    private static User login() {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名");
        String name = sc.nextLine();
        while(true){
            int choice = 0;
            try{
                System.out.println("请选择用户类别:1.普通用户 2.管理员");
                choice = sc.nextInt();
            }catch (InputMismatchException e){
                //什么都不做,后面的else会输出"非法输入,请重新输入"
            }
            //清理回车
            sc.nextLine();
            if(choice == 1){
                return new GeneralUser(name);
            } else if (choice == 2) {
                return new Admin(name);
            }else {
                System.out.println("非法输入,请重新输入");
            }
        }
    }

方法二:用字符串接收输入,然后再通过Integer类里面的paeseInt()方法将String转换为Int,同样需要捕获转换失败的异常

我们这里就通过Admin类里面的operate()来演示

    //选择并执行操作
    @Override
    public void operate() {
        Scanner sc = new Scanner(System.in);
        String input = sc.nextLine();
        try {
            //可能出现转换的异常,也可能出现越界的异常
            operateList[Integer.parseInt(input)].operate();
        } catch (IndexOutOfBoundsException | NumberFormatException e) {
            //由于提示都一样所以就合并在同一个catch分支里面
            System.out.println("非法输入,请重新输入");
        }
    }

我们这里简单介绍一下类似于Integer这样的包装类,我们在书写一些方法的时候会将类型定为Object类,但是int这样的基本类型又不是类,没有继承Object。在面向对象思想下,我们肯定希望能一个代码执行多种情况,基本类型的情况也希望包括在内,因此就有了基本类型对应的类,包装类

同时,各个基本数据类型的包装类中提供了一系列方法使得这个基本数据类型的使用更加的灵活,更加符合了面向对象的思想

具体实现

我们上面已经把大概的整个流程实现了,接下来就是将各个流程具体实现出来

初始化存储系统

首先是初始化整个存储系统,不然操作系统都不知道操作谁,那怎么运行下去呢?

我们在main()函数的开始先创建一个BookList

    public static void main(String[] args) {
        User user = login();
        //创建BookList
        BookList b = new BookList();
        while(true){
            user.menu();
            user.operate();
        }
    }

然后让所有的涉及到操作的方法的参数都加一个BookList b,最后在main()函数中的operate()方法将我们刚开始创建的BookList传过去

    public static void main(String[] args) {
        User user = login();
        BookList b = new BookList();
        while(true){
            user.menu();
            //传址,由于是引用类型所以是传址
            user.operate(b);
        }
    }

这样我们后面就可以在各个操作中操作这个我们刚开始创建的BookList

至于BookList的构造方法,如果你想刚开始直接有几本书那就可以在构造方法中加即可

操作细节实现

添加书籍

我们先实现添加的操作,这样我们可以往存储系统里面添加书籍方便对后续的操作进行测试

首先我们就简单的将一个书添加进去,非常简单,新建一个Book对象,然后接收各个信息并且把这些数据赋值给新的Book对象,最后把这个Book对象放入即可

由于我们把Book的各个属性定义为了private的,所以我们去给Book加一个构造方法,那么我们就可以通过构造方法一次性把所有信息塞入进去,就不用那些setter了(当然想用也是可以的)

    @Override
    public void operate(BookList b) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请根据指示输入以下数据");
        System.out.println("请输入书名:");
        String name = sc.nextLine();
        System.out.println("请输入作者:");
        String author = sc.nextLine();
        System.out.println("请输入类型:");
        String type = sc.nextLine();
        boolean isBorrowed;
        while (true) {
            System.out.println("请输入借阅状态(是/否):");
            String tmp = sc.nextLine();
            if (tmp.equals("是")) {
                isBorrowed = true;
                break;
            } else if (tmp.equals("否")) {
                isBorrowed = false;
                break;
            } else {
                System.out.println("非法输入,请重新输入");
            }
        }
        b.bookList[b.bookNum] = new Book(name, author, type, isBorrowed);
    }

很明显这一坨太长了,我们之后要做是否满了的判断都不好加,所以我们把这一段代码分离出去变成一个独立的方法

//分离出一个addBook()
	private void addBook(BookList b) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请根据指示输入以下数据");
        System.out.println("请输入书名:");
        String name = sc.nextLine();
        System.out.println("请输入作者:");
        String author = sc.nextLine();
        System.out.println("请输入类型:");
        String type = sc.nextLine();
        boolean isBorrowed;
        while (true) {
            System.out.println("请输入借阅状态(是/否):");
            String tmp = sc.nextLine();
            if (tmp.equals("是")) {
                isBorrowed = true;
                break;
            } else if (tmp.equals("否")) {
                isBorrowed = false;
                break;
            } else {
                System.out.println("非法输入,请重新输入");
            }
        }
        b.bookList[b.bookNum] = new Book(name, author, type, isBorrowed);
    }

分离完之后我们就开始考虑:如果存储系统满了,怎么办?

解决方案有两个,要么就不让放了,要么就扩容

我们这里就选择将这个BookList进行扩容,对数组的扩容操作其实很简单,只需要创建一个新的数组然后把原来的数据复制到新的数组就行

    if(b.bookList.length == b.bookNum){
        Book[] newBookList = new Book[b.bookList.length*2];
        for (int i = 0; i < b.bookNum; i++) {
            newBookList[i] = b.bookList[i];
        }
        b.bookList = newBookList;
    }

还有另外两个常见的方法:1. 使用Arrays.copyOf() 2.使用System.arraycopy()

//System.arraycopy(复制源,复制源起点,复制目的地,目的地起点,复制长度)
    if(b.bookList.length == b.bookNum){
        Book[] newBookList = new Book[b.bookList.length*2];
        System.arraycopy(b.bookList, 0, newBookList, 0, b.bookNum);
        b.bookList = newBookList;
    }
//Arrays.copyOf(复制源,新的数组的长度)  
//默认从头开始复制
    if (b.bookList.length == b.bookNum) {
        b.bookList = Arrays.copyOf(b.bookList, b.bookList.length * 2);
    }

其中Arrays.copyOf()非常适合做扩容工作,而System.arraycopy适合更加精细的复制工作

但是扩容的代码并不适合放在Add类中,因为它涉及到了对bookList的操作,因此更加推荐把这个方法放在BookList里面

那我们将它放入BookList类中,由于BookList内部我们可以直接访问成员,就不需要引用了

    public void increaseSize(){
        bookList = Arrays.copyOf(bookList, bookList.length * 2);
    }

那么最后Add类代码如下

public class Add implements Operate {
	private void addBook(BookList b) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请根据指示输入以下数据");
        System.out.println("请输入书名:");
        String name = sc.nextLine();
        System.out.println("请输入作者:");
        String author = sc.nextLine();
        System.out.println("请输入类型:");
        String type = sc.nextLine();
        boolean isBorrowed;
        while (true) {
            System.out.println("请输入借阅状态(是/否):");
            String tmp = sc.nextLine();
            if (tmp.equals("是")) {
                isBorrowed = true;
                break;
            } else if (tmp.equals("否")) {
                isBorrowed = false;
                break;
            } else {
                System.out.println("非法输入,请重新输入");
            }
        }
        b.bookList[b.bookNum] = new Book(name, author, type, isBorrowed);
    }

    @Override
    public void operate(BookList b) {
        if (b.bookList.length == b.bookNum) {
            b.increaseSize();
        }
        addBook(b);
        b.bookNum++;
    }
}

在后面书写operate()方法的时候都尽可能的不要把一坨代码全部塞在一起,因为假如有一些功能是可以共享的,比如我们的删除和修改都可以利用查找功能,我们就可以借助查找里面的代码,而不用去重新写,但是如果全部塞在一起就不好重复利用了

展示书籍

我们写好了添加书籍,但是现在只能通过调试去看具体情况,这很麻烦。为了能够更好的观察大概的情况,我们再把展示书籍的功能写好

展示书籍的方法也比较简单,一个一个打印bookList里面的元素即可,但是由于Book是自定义类型,我们最好回去Book重写一个toString()方法

但是用IDEA自动生成的打印又太丑了,所以我们给它修饰一下

    @Override
    public String toString() {
        return  "书名:" + name + "  " +
                "作者:" + author + "  " +
                "种类:" + type + "  " +
                (isBorrowed ? "已被借出" : "未被借出");
    }

这里用了一个条件操作符,如果isBorrowedtrue那么这个表达式的结果就是已被借出,否则就是未被借出,那么输出时就会根据true/false来输出这两个字符串

但这样还不够,我还希望它能够在打印前根据名字排序一下

那么我们既然是要给bookList排序,也就是说里面的Book是要能够比较的,能比较才能排序,因此我们回去给Book实现一个Comparable接口并且重写compareTo方法

根据名字排序,我们就比较名字就行,由于String本身就实现了comparable接口,我们直接调用里面的compareTo()来比较名字就行了

@Override
public int compareTo(Book o) {
    return this.name.compareTo(o.name);
}

最后就在打印前调用一个Arrays.sort()把我们这个bookList数组排序即可,但是要注意,我们的数组里面是有null指针的,如果不限定范围进行排序就会抛出空指针异常

所以我们多提供一个参数限定排序的范围在bookNum里面,这里的范围是左闭右开的

Arrays.sort(b.bookList,0,b.bookNum);

那么最后展示书籍Show类的代码就如下

public class Show implements Operate{
    private void print(BookList b){
        Arrays.sort(b.bookList,0,b.bookNum);
        for(int i = 0; i < b.bookNum; i++){
            System.out.println(b.bookList[i]);
        }
    }
    @Override
    public void operate(BookList b) {
        if(b.bookNum == 0){
            System.out.println("系统为空");
        }else{
            print(b);
        }
    }
}
查找书籍

由于我们之后的修改、删除、借阅操作都一定程度上的要进行查找的操作,所以我们先把查找功能完成

这里就只实现通过名字查找的方法,其他的可以尝试自己实现

同样也是非常简单的,首先,接收要查找的书名。然后写一个单独的方法,遍历整个数组,找到一样的就返回目标的下标,没找到返回-1,然后在operate里面接收这个返回值做出对应操作即可

代码如下

public class Search implements Operate {
    //查找方法,找到了返回当前下标,没找到返回-1
    int searchByName(String target, BookList b) {
        for (int i = 0; i < b.bookNum; i++) {
            if (b.bookList[i].getName().equals(target)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public void operate(BookList b) {
        if (b.bookNum == 0) {
            System.out.println("系统为空");
        } else {
            //接收书名
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要查找的书名");
            String target = sc.nextLine();
            //查找
            int ret = searchByName(target, b);
            //对返回值做出操作
            if(ret != -1){
                System.out.println(b.bookList[ret]);
            }else {
                System.out.println("系统中没有这本书");
            }
        }
    }
}

这里没有给searchByName加修饰符是因为我们只希望它被同一个包里面的那几个操作类使用

借阅书籍

借阅书籍我们就可以通过借助Search类里面的searchByName()方法来找到我们要借出的书

并且我们这里使用的是组合的思想,是通过在Borrow里面new一个Search类来借助里面的方法,并不是通过继承,因为它们并没有什么从属关系

代码思路依旧非常简单,首先接收要借出的书的书名,然后借助查找方法找到那本书,把那本书的借出状态改为true即可,但同时也要注意如果已经被借走了就不能借了

代码如下

public class Borrow implements Operate {
    //判断是否被借出
    private void borrow(int index,BookList b) {
        if (b.bookList[index].getIsBorrowed()){
            System.out.println("这本书已被借出");
        }else {
            b.bookList[index].setIsBorrowed(true);
            System.out.println("借阅成功");
        }
    }

    @Override
    public void operate(BookList b) {
        if (b.bookNum == 0) {
            System.out.println("系统为空");
        } else {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要借出的书的书名");
            String target = sc.nextLine();
            //借助search对象里面的searchByName方法
            Search search = new Search();
            int ret = search.searchByName(target, b);
            
            //保证下标合法
            if (ret != -1) {
                borrow(ret, b);
            } else {
                System.out.println("系统中没有这本书");
            }
        }
    }
}
修改书籍

修改书籍实际上也不难实现,我们依旧是借用searchByName方法,找到要修改的书,然后新建一个Book对象把原来的书直接替换掉就行,当然也可以接收一个信息用一下setter()

但是我们仔细联系一下我们之前的添加书籍的代码,不也是新建了一个Book对象,然后放到某个下标处吗?只不过添加书籍固定下标为bookNum处。那我们不妨把添加书籍的代码修改一下,放到Modify类里,然后在Add类里面通过组合的方式来实现添加书籍的效果

我们当时把添加书籍的操作代码独立出来了,那么也十分好修改,修改后如下

    void modifyBookList(int index, BookList b) {
    Scanner sc = new Scanner(System.in);
    System.out.println("请根据指示输入以下数据");
    System.out.println("请输入书名:");
    String name = sc.nextLine();
    System.out.println("请输入作者:");
    String author = sc.nextLine();
    System.out.println("请输入类型:");
    String type = sc.nextLine();
    boolean isBorrowed;
    while (true) {
        System.out.println("请输入借阅状态(是/否):");
        String tmp = sc.nextLine();
        if (tmp.equals("是")) {
            isBorrowed = true;
            break;
        } else if (tmp.equals("否")) {
            isBorrowed = false;
            break;
        } else {
            System.out.println("非法输入,请重新输入");
        }
    }
    b.bookList[index] = new Book(name, author, type, isBorrowed);
}

将上面的代码放到Modify类里面,然后再把addBook()方法修改一下

private void addBook(BookList b){
    Modify modify = new Modify();
    modify.modifyBookList(b.bookNum,b);
}

那么这边改好了就是开始正式实现修改书籍的操作

首先依旧是借用searchByName方法,找到要修改的书,然后通过上面我们拿过来的modify方法修改即可

代码如下

public class Modify implements Operate {
    void modifyBookList(int index, BookList b) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请根据指示输入以下数据");
        System.out.println("请输入书名:");
        String name = sc.nextLine();
        System.out.println("请输入作者:");
        String author = sc.nextLine();
        System.out.println("请输入类型:");
        String type = sc.nextLine();
        boolean isBorrowed;
        while (true) {
            System.out.println("请输入借阅状态(是/否):");
            String tmp = sc.nextLine();
            if (tmp.equals("是")) {
                isBorrowed = true;
                break;
            } else if (tmp.equals("否")) {
                isBorrowed = false;
                break;
            } else {
                System.out.println("非法输入,请重新输入");
            }
        }
        b.bookList[index] = new Book(name, author, type, isBorrowed);
    }

    @Override
    public void operate(BookList b) {
        if (b.bookNum == 0) {
            System.out.println("系统为空");
        } else {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要修改的书的书名");
            String target = sc.nextLine();
            Search search = new Search();
            int ret = search.searchByName(target, b);
            if (ret != -1) {
                modifyBookList(ret, b);
            } else {
                System.out.println("系统中没有这本书");
            }
        }
    }
}
删除书籍

删除书籍的操作,思路上也不难想,依旧是先通过searchByName()找到要删除的书,然后再删掉就行,可是怎么删?

直接将当前下标置空很明显是非常愚蠢的想法,因为我们是通过下标访问的,所以你如果在数组中间插一个null则大多数操作都会抛出空指针异常

置空后排序也是不可能的,对null进行排序同样也会抛出空指针异常

那么有没有不置空能删除数据的方法呢?答案是通过覆盖,覆盖掉了这个数据就没了不就相当于删除了吗

但是又要怎么覆盖呢?实际上,我们只要让删除位置的后面的数据全部往前面移一个位置,那么就可以实现删除,这种删除方式在数组中比较常用,如图所示

红色的为要删除的位置,但是这个删除方法要注意:
1.操作次序不能从后到前,否则会导致数据丢失
2.最后一个位置不能执行相同的操作,因为后面是空的,如果执行相同操作则会抛出空指针异常

因此最后一个位置我们要单独进行处理,例如手动将其置空

如果是使用for循环,则代码如下

    private void delete(int index, BookList b){
        for (int i = index; i < b.bookNum - 1; i++) {
            b.bookList[i] = b.bookList[i+1];
        }
        b.bookList[b.bookNum - 1] = null;
        b.bookNum--;
    }

同样的,我们可以借助上面说过的System.Arraycopy方法,因为这个删除操作可以看作是把index + 1后面的数组复制到index位置,复制的长度为bookNum - (index + 1)

System.arraycopy(b.bookList, index + 1, b.bookList, index, b.bookNum - 1 - index);

那么其他的代码和上面的没很大的区别,我们直接上代码

public class Delete implements Operate{
    private void delete(int index, BookList b){
        System.arraycopy(b.bookList, index + 1, b.bookList, index, b.bookNum - 1 - index);
        b.bookList[b.bookNum - 1] = null;
        b.bookNum--;
        System.out.println("删除成功");
    }
    @Override
    public void operate(BookList b) {
        if (b.bookNum == 0) {
            System.out.println("系统为空");
        } else {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你要删除的书的书名");
            String target = sc.nextLine();
            //借助search对象里面的searchByName方法
            Search search = new Search();
            int ret = search.searchByName(target, b);

            //保证下标合法
            if (ret != -1) {
                delete(ret, b);
            } else {
                System.out.println("系统中没有这本书");
            }
        }
    }
}
系统销毁

最后还剩下一个销毁系统,这个可以说是最简单的操作,遍历数组全部置空,然后将bookNum归零即可

public class Destroy implements Operate{
    @Override
    public void operate(BookList b) {
        for (int i = 0; i < b.bookNum; i++) {
            b.bookList[i] = null;
        }
        b.bookNum = 0;
        System.out.println("系统关闭");
        Scanner sc = new Scanner(System.in);
        sc.close();
        System.exit(0);
    }
}

那么到这里整个图书管理系统就写完了

代码总览

如果想要预览代码,可以到下面这个仓库里看,因为代码太多太杂,放到文章里面也不好看

图书管理系统代码

版权声明:本文为博主作者:Aoharu原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/qq_42150700/article/details/130972003

退出移动版