Java 小例子:图书馆课程设计(Java 8 版)

用 Java 模拟一个图书馆。包括创建图书、创建读者、借书、还书、列出所有图书、列出所有读者、列出已借出的图书、列出过期未还的图书等功能。每个读者最多只能借 3 本书,每个书最多只能借 3 个星期,超过就算过期。

这个例子跟 http://blog.csdn.net/yidinghe/article/details/3940437 相比,增加了 Java 8 特有的语法,包括:Lambda 表达式,java.time 日期 API,streaming API 等。功能也比前者稍微完善了些,体积有所减小。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 图书馆例子 Java 8 版本
 * created at 2014/11/4
 *
 * @author Yiding
 */
public class LibraryManager {

    public static final Scanner SCANNER = new Scanner(System.in);

    public static final String NUMBERS_ONLY = "^\\d+$"; // 表示只允许输入数字

    public static final String ANY_CONTENT = "^\\S+$";  // 表示可以输入任何内容

    public static void main(String[] args) {
        new LibraryManager().start();
    }

    

    private Library library = new Library();

    private ArrayList<Command> commands = new ArrayList<>();

    private String mainMenu;

    /**
     * 构造方法
     */
    public LibraryManager() {
        initCommands();
        initStudents();
        initBooks();
    }

    private void initBooks() {
        this.library.addBook("论程序员的自我修养", "带鱼", BookType.科学类.toString());
        this.library.addBook("印度四大名著全集", "阿达木", BookType.文学类.toString());
        this.library.addBook("睡眠的好处", "程序员阿迪", BookType.科学类.toString());
        this.library.addBook("架构师2014年10月刊", "美丽女人网", BookType.杂志.toString());
    }

    private void initStudents() {
        this.library.students.add(new Student("张三"));
        this.library.students.add(new Student("李四"));
        this.library.students.add(new Student("王五"));
    }

    /**
     * 初始化命令和主菜单
     */
    private void initCommands() {
        addCommand(new ListCommand<>("所有图书:", () -> library.books), "查询所有图书");
        addCommand(new ListCommand<>("所有学生:", () -> library.students), "查询所有学生");
        addCommand(new AddBookCommand(), "添加图书");
        addCommand(new DeleteBookCommand(), "删除图书");
        addCommand(new BorrowBookCommand(), "借阅图书");
        addCommand(new ReturnBookCommand(), "归还图书");
        addCommand(new ListCommand<>("所有借阅过期的图书:", library::expiredBooks), "查询借阅过期的图书");
        addCommand(new ExitCommand(), "退出");

        this.mainMenu = toMenu("请输入命令", this.commands);
    }

    private void addCommand(Command command, String title) {
        command.title = title;
        this.commands.add(command);
    }

    /**
     * 开始执行交互
     */
    private void start() {
        CommandResult result;

        // 在 while 条件中判断命令的执行结果是否表示要退出程序
        // 只有 ExitCommand 的执行结果是 CommandResult.EXIT
        do {

            try {
                String command = prompt(mainMenu, NUMBERS_ONLY); // 选择命令
                result = executeCommand(command);                // 执行命令
            } catch (CommandCancelException e) {
                result = CommandResult.FAIL;
            }

            System.out.println(result.prompt + "\n");
        } while (result != CommandResult.EXIT);
    }

    /**
     * 打印一条提示消息并返回用户的输入
     *
     * @param prompt  提示消息
     * @param pattern 指定格式,如果用户的输入不符合格式则会反复提示重新输入。为空则不检查用户输入
     *
     * @return 用户的输入
     */
    private String prompt(String prompt, String pattern) {
        String userInput;

        // 在 while 条件中判断用户输入的内容是否符合 pattern 指定的格式
        // 如果 pattern 为 null 则不做判断
        do {
            System.out.print(prompt);
            userInput = SCANNER.nextLine();

            // 用户直接回车时,表示取消命令执行
            if (userInput.equals("")) {
                throw new CommandCancelException();
            }

        } while (pattern != null && !userInput.matches(pattern));

        return userInput;
    }

    // 打印一组选项并返回用户选择的选项内容
    private String prompt(String prompt, List<?> options) {
        int index = promptIndex(prompt, options);
        return options.get(index - 1).toString();
    }

    // 打印一组选项并返回用户选择的位置
    private int promptIndex(String prompt, List<?> options) {
        String menu = toMenu(prompt, options);
        int index;

        do {
            index = Integer.parseInt(prompt(menu, NUMBERS_ONLY));
        } while (index == 0 || index > options.size());

        return index;
    }

    /**
     * 生成菜单内容
     *
     * @param prompt  提示,在列出所有选项后打印出来
     * @param options 选项
     *
     * @return 主菜单内容
     */
    private <T> String toMenu(String prompt, List<T> options) {
        final ArrayList<String> lines = new ArrayList<>();
        final AtomicInteger counter = new AtomicInteger();

        options.forEach((t) -> {
            int index = counter.incrementAndGet();
            String line = index + ": " + t.toString();
            lines.add(line);
        });

        return String.join("\n", lines) + "\n" + prompt + "(1-" + lines.size() + "):";
    }

    /**
     * 执行用户命令
     *
     * @param command 用户命令序号
     *
     * @return 执行结果
     */
    private CommandResult executeCommand(String command) {
        int index = Integer.parseInt(command);

        if (index > 0 && index <= commands.size()) {
            return commands.get(index - 1).execute();
        } else {
            return CommandResult.OK;
        }
    }

    

    static enum CommandResult {
        OK("命令已完成。"), FAIL("命令已取消。"), EXIT("");

        public String prompt;   // 在每个命令结束时打印出来

        CommandResult(String prompt) {
            this.prompt = prompt;
        }

    }

    static enum BookType {文学类, 科学类, 杂志}

    // 表示用户取消命令的异常
    static class CommandCancelException extends RuntimeException {

    }

    static class Book {

        public static final int EXPIRE_BORROW_DAYS = 21;

        public String name;

        public String author;

        public String type;

        public String borrowedBy;

        public String borrowDate;

        Book(String name, String author, String type) {
            this.name = name;
            this.author = author;
            this.type = type;
        }

        public boolean isBorrowed() {
            return this.borrowedBy != null;
        }

        @Override
        public String toString() {
            return name + ",作者:" + author + "," + type +
                    (isBorrowed() ? " -- 已被'" + borrowedBy + "'于" + borrowDate + "借出" : "");
        }

        public boolean isExpired() {
            if (!isBorrowed()) {
                return false;
            }

            // 从当前时间反推过期的借阅时间。如果实际借阅时间在过期的借阅时间之前,则表示过期了
            LocalDate maxBorrowDate = LocalDate.now().minus(EXPIRE_BORROW_DAYS, ChronoUnit.DAYS);
            String maxBorrowDateStr = DateTimeFormatter.ofPattern("yyyyMMdd").format(maxBorrowDate);
            return this.borrowDate.compareTo(maxBorrowDateStr) < 0;
        }
    }

    static class Student {

        public static final int MAX_BORROW = 3;

        public String name;

        Student(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    

    class Library {

        private List<Book> books = new ArrayList<>();

        private List<Student> students = new ArrayList<>();

        /**
         * 添加书籍
         *
         * @param bookName 书名
         * @param author   作者
         * @param type     类型
         *
         * @return 执行结果
         */
        public CommandResult addBook(String bookName, String author, String type) {
            if (books.stream().anyMatch((b) -> b.name.equals(bookName))) {
                System.out.println("添加失败:书名已存在");
                return CommandResult.FAIL;
            }

            this.books.add(new Book(bookName, author, type));
            return CommandResult.OK;
        }

        public List<Book> availableBooks() {
            return this.books.stream().filter((b) -> !b.isBorrowed()).collect(Collectors.toList());
        }

        public List<Book> borrowedBooks() {
            return this.books.stream().filter(Book::isBorrowed).collect(Collectors.toList());
        }

        public List<Book> expiredBooks() {
            return this.books.stream().filter(Book::isExpired).collect(Collectors.toList());
        }

        /**
         * 删除书籍
         *
         * @param index 序号
         *
         * @return 执行结果
         */
        public CommandResult deleteBook(int index) {
            this.books.remove(index);
            return CommandResult.OK;
        }

        public int countBorrowedBooks(String student) {
            return (int) this.books.stream().filter((b) -> student.equals(b.borrowedBy)).count();
        }
    }

    

    /**
     * 表示命令的抽象类
     */
    static abstract class Command {

        public String title;  // 命令标题,将显示在主菜单中

        abstract CommandResult execute();

        @Override
        public String toString() {
            return title;
        }
    }

    // 列出满足要求的对象
    class ListCommand<T> extends Command {

        private Supplier<List<T>> supplier; // 查询满足要求的对象的方法

        private String title;               // 输出标题

        ListCommand(String title, Supplier<List<T>> supplier) {
            this.title = title;
            this.supplier = supplier;
        }

        @Override
        CommandResult execute() {
            System.out.println("\n" + title);
            supplier.get().forEach(System.out::println);
            return CommandResult.OK;
        }
    }

    // 添加图书
    class AddBookCommand extends Command {

        @Override
        CommandResult execute() {
            return library.addBook(
                    prompt("请输入书名:", ANY_CONTENT),
                    prompt("请输入作者:", ANY_CONTENT),
                    prompt("请选择书籍类型:", Arrays.asList(BookType.values()))
            );
        }
    }

    // 删除图书
    class DeleteBookCommand extends Command {

        @Override
        CommandResult execute() {
            if (library.books.isEmpty()) {
                System.out.println("没有可删除的书籍。");
                return CommandResult.FAIL;
            }

            int index = promptIndex("请选择书籍序号", library.books);
            return library.deleteBook(index - 1);
        }
    }

    // 借阅图书
    class BorrowBookCommand extends Command {

        @Override
        CommandResult execute() {
            List<Book> availableBooks = library.availableBooks();
            if (availableBooks.isEmpty()) {
                System.out.println("没有可借阅的图书。");
                return CommandResult.FAIL;
            }

            int index = promptIndex("请选择要借阅的图书", availableBooks);
            Book book = availableBooks.get(index - 1);

            String student = prompt("请选择借阅者:", library.students);
            if (library.countBorrowedBooks(student) >= Student.MAX_BORROW) {
                System.out.println("该同学不能借阅更多图书了。");
                return CommandResult.FAIL;
            }

            String bDate = prompt("请输入借阅日期(YYYYMMDD):", "^\\d{8}$");

            book.borrowedBy = student;
            book.borrowDate = bDate;
            return CommandResult.OK;
        }
    }

    // 归还图书
    class ReturnBookCommand extends Command {

        @Override
        CommandResult execute() {
            List<Book> borrowedBooks = library.borrowedBooks();
            if (borrowedBooks.isEmpty()) {
                System.out.println("没有图书需要归还。");
                return CommandResult.FAIL;
            }

            int index = promptIndex("请选择已借阅的图书", borrowedBooks);
            Book book = borrowedBooks.get(index - 1);
            book.borrowedBy = null;
            book.borrowDate = null;
            System.out.println("图书已归还。");
            return CommandResult.OK;
        }
    }

    // 退出程序
    class ExitCommand extends Command {

        @Override
        CommandResult execute() {
            return CommandResult.EXIT;
        }
    }
}


  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
光盘内容及使用说明 1. 内容及使用方法 (1) 本光盘提供了书中案例的Java源代码文件及编译发布后的可执行文件。 (2) 读者可以使用文本编辑工具(例如Windows中的记事本)或Java编辑工具来打开和编辑Java源代码文件。 (3) 文件夹图标后的数字编号为章号。例如CH03,为第3章的内容。 (4) 本光盘中,每章均含有打包发布的程序文件和案例的源代码文件(第3章除外,另请注意第1章和第2章因内容简单,所以光盘中没有包含)。 (5) 直接双击各章“打包发布”文件夹中的BAT文件即可运行相应章的案例程序(注意必须已安装JDK 1.5,并检查Path环境变量中应已经有JDK路径)。 (6) 也可以直接双击各章“打包发布”文件夹中的JAR文件来运行相应章的案例程序。但如果JAR文件已被RAR等压缩软件关联,则应当通过右击JAR文件图标,从快捷菜单中选择【打开方式】|【选择程序】|【推荐的程序】|【Java(TM) 2 Platform Standard Edition binary】命令,来取消与其他压缩软件的关联。取消后若JDK安装配置无误,即可直接双击JAR文件运行应用程序。 (7) 第11章中的database文件夹存放有该章的数据库文件,运行该章的程序前请首先配置数据源,配置方法请参考书中的讲解。 2. 书中案例运行的配置要求 (1) 硬件配置 CPU:≥Pentium III 600 内存:≥128MB 硬盘剩余空间:≥512MB (2) 软件环境 安装Sun公司的Java运行环境(JRE 1.5或以上本),推荐安装Java开发工具包(JDK 1.5)。 安装相应平台上的Java虚拟机(JVM)。 读者可以到www.sun.com下载JDK 1.5的相应本,安装并配置Path之后即可达到运行要求,Windows下的配置方法请参照第1章。 书中的案例可以跨平台运行。 3. 编译提示 (1) 有使用其他可视化Java编程工具经验的用户也可以利用这些软件已经携带的JDK。例如从“控制面板”中通过【系统】|【高级】|【环境变量】,针对JBuilder8设置环境变量,即把Path路径设置为C:\JBuilder8\jdk1.4\bin,然后将每章“源程序”目录之中的内容保存在例如C:\temp目录中,然后即可在命令提示符窗口中切换到C:\temp>并使用javac –classpath . *.java命令进行编译(其中-classpath用来告诉编译器用户Java文件的路径,“ . ”表示*.java文件的路径在当前目录位置,注意点的前后均有一个空格)。经过编译产生*.class文件后,在C:\temp>提示符下使用java –classpath . * 命令即可运行程序(其中-classpath用来告诉编译器用户类文件的路径,“ . ”表示*.class文件的路径在当前目录位置,注意点的前后均有一个空格。“*”为不包括扩展名的类文件名)。 (2) 上述方法对于直接安装的JDK 1.5亦完全适用。 (3) 也可以象书中介绍的那样,在使用javac命令之前使用set classpath=c:\temp设置类路径(等号前后不能有空格),然后即可直接使用javac *.java编译命令和java *运行命令。 (4) 但是应注意,在使用JDK 1.4时,个别程序可能会无法正常编译运行(因JDK 1.5的编程方式略有变化)。所以应首选JDK 1.5。 (5) 与在“环境变量”对话框中设置类路径相比,在命令提示符窗口中设置类路径是比较稳妥的方法。因为用户存放程序代码的目录位置是根据内容不同而随时确定的。此外您可能会发现“环境变量”对话框中的ClassPath设置在有些情况下毫无作用。 4. 注意事项 (1) 建议读者将光盘中的所有文件备份到硬盘上运行。 (2) 在练习过程中,希望读者每做完一部分工作就存盘,这样可以避免因意外而导致的数据大量丢失。 5. 特别声明 本光盘中的文件仅可作为学习和欣赏之用,未经许可不得用于任何商业或其他用途。 6. 技术支持 关于本书的相关技术支持和软件问题请发电子邮件到bookforjava@163.com寻求帮助。 7. 作者信息 作者(技术支持及相关问题探讨) 姓 名:张广彬、孟红蕊 电子邮件:bookforjava@163.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值