ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java银行存取款模拟项目:一次打通面向对象、异常与并发

Java银行存取款模拟项目:一次打通面向对象、异常与并发 如果你正在学 Java想找一个能串起零基础到入门再到进阶的练手项目我第一个推荐的永远不是图书管理、也不是学生成绩系统而是Java 模拟银行存款取款业务。这个项目我以前带过很多新人写也在实验报告里见过各种版本它最迷人的地方在于业务规则非常清楚存钱、取钱、转账、查余额但实现过程中几乎能把 Java 的面向对象、异常机制、集合框架、IO 流、并发、单元测试全都踩一遍。说白了一个看似简单的存取款模拟写得好不好直接暴露你对 Java 掌握到了什么程度。这篇文章我会从需求分析讲到类设计、再到核心业务、异常处理、控制台交互、进阶扩展和排错心得全程按我实际带项目的思路来保证零基础能跟上、有基础的人也能拿到不少可以直接抄走的经验。1. 用银行存取款业务练手能一次打通哪些 Java 核心知识点很多人学 Java 有一个误区跟着教程敲一遍Hello World、背几个语法就觉得自己入门了。等到真正写一个小项目才发现连一个类该怎么拆、方法该怎么命名、数据要怎么存脑子里全是乱的。模拟银行存取款业务恰恰是一块非常好的试金石它逼着你去思考真实业务和代码之间的映射关系。1.1 这个场景天然自带工程复杂度银行存取款业务不像打印九九乘法表那样一步到位它的复杂度来自几个地方有状态账户有余额、有卡号、有户名余额会变。有规则取款不能超余额存款金额必须大于 0转账涉及两个账户的状态变化。有边界金额输入非法字符怎么办余额刚好为 0 时取 1 分钱怎么办连续操作会不会导致数据不对有交互不是把所有代码堆在 main 里跑一遍而是要让用户在控制台反复操作。有数据保存需求程序关掉再打开账户还在不在这就引出持久化、序列化。这些复杂度恰好对应着 Java 里最常考、最常用的知识点。学完这个项目你回头看 Java 面试题里关于面向对象、异常、集合的题目会明显感觉不再抽象了。1.2 一个练手项目覆盖的知识点清单我习惯把这个项目能覆盖的知识点整理成一张表方便自测哪里还没掌握知识点在这个项目里的落点类和对象账户类 Account、银行类 Bank、存取款操作对应的方法封装余额、卡号用 private只通过 getter/setter 或业务方法访问构造器开户时初始化卡号、户名、初始金额继承/多态扩展活期账户、定期账户时体现接口定义账户通用能力便于扩展异常处理自定义 InsufficientBalanceException、IllegalAmountException集合框架用 MapString, Account 管理所有账户常用 APIString 格式化、BigDecimal 精度处理、Scanner 输入IO/序列化把账户列表写入文件下次启动恢复并发多个线程同时存取时保证余额正确单元测试JUnit 对取款、存款、转账写断言很多人写实验报告交上去的代码就是在 main 里写三个 if 分支按 1 存款、按 2 取款、按 3 查余额。那叫用 Java 写了一个小程序不叫模拟银行业务。真正值得写进简历、写进实验报告里的版本至少应该有清晰的类职责划分有异常处理有数据校验还能简单扩展。1.3 和纯语法练习的本质区别纯语法练习是我知道怎么写出 for 循环、知道 String 和 int 怎么转换而做这个小项目我必须知道用户输入的是字符串但我需要把字符串解析成 BigDecimal解析失败时不能让程序崩溃还要提示用户重新输入。这个差别就是会语法和会写程序的分水岭。所以这篇文章的定位很明确既照顾零基础把每一步怎么拆讲清楚也照顾已经写过但想优化的人给出更工程化的写法。2. 动手前的面向对象设计类结构这么拆后面不返工每次拿到一个项目我最怕的就是有人上来就写代码。写银行存取款之前先在纸上花十分钟把有哪些对象、对象之间什么关系、谁负责干什么想清楚后面写起来效率完全不一样。这十分钟想不明白后面往往要花十个小时改。2.1 先把需求一条条摆到桌面上模拟银行存取款业务我认为最基础的需求是这样几条开户输入户名生成一个账号可以存初始金额也可以 0 金额开户。存款往指定账户里加钱。取款从指定账户里扣钱余额不足要报错。转账从 A 账户扣钱B 账户加钱。查询余额输入账号显示户名和当前余额。销户可选删除账户。先不要一上来就想我要做成网页版、数据库版、Spring Boot 版控制台版能跑通就已经完成了 90% 的核心逻辑。Web 版本往后只是在外面套一层壳业务部分完全复用。2.2 类的职责划分Account、Bank、Main各管各的根据需求我通常把代码拆成三个主要角色Account账户实体只负责描述一个账户是什么样子包括账号、户名、余额以及存款、取款、转账这样修改自身状态的动作。Bank银行门面负责管理所有账户比如开户、根据账号找账户相当于业务入口。Main控制台交互只负责和用户打交道打印菜单、读输入、调用 Bank 的方法。这个拆分的好处是Account 里面不会出现 Scanner、不会出现 System.out.println它只做纯业务逻辑后面测试也好测、复用也好复用。我第一次带新人时很多人喜欢把从控制台读数字直接写进 Account 的取款方法里当时看起来没问题但后面一旦想改成 GUI 或者 Web就发现 Account 被输入输出代码绑架了改起来想死。Account 类最基础的骨架长这样import java.math.BigDecimal; public class Account { private String accountId; // 账号 private String name; // 户名 private BigDecimal balance; // 余额用 BigDecimal 而不是 double public Account(String accountId, String name, BigDecimal initialBalance) { this.accountId accountId; this.name name; this.balance initialBalance; } public String getAccountId() { return accountId; } public String getName() { return name; } public BigDecimal getBalance() { return balance; } // 存款金额必须大于 0否则抛异常 public void deposit(BigDecimal amount) { if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalArgumentException(存款金额必须大于 0); } this.balance this.balance.add(amount); } // 取款金额必须大于 0且余额不能不足 public void withdraw(BigDecimal amount) { if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalArgumentException(取款金额必须大于 0); } if (this.balance.compareTo(amount) 0) { throw new IllegalArgumentException(余额不足); } this.balance this.balance.subtract(amount); } // 转账从当前账户转出到目标账户 public void transfer(Account target, BigDecimal amount) { this.withdraw(amount); target.deposit(amount); } }看到这里零基础的同学可能有一个疑问为什么存款和取款要用BigDecimal直接用double不行吗这个问题问得非常好也是银行项目里最经典的一个坑。2.3 为什么账户余额必须用 BigDecimal而不是 double因为浮点数在计算机里不能精确表示很多十进制小数。比如你用 double 做0.1 0.2得到的结果不是 0.3而是 0.30000000000000004。银行账目上一分钱都不能差所以凡是涉及金额的运算我都建议直接用BigDecimal。BigDecimal使用时有几个细节构造时优先用字符串构造new BigDecimal(19.99)不要用new BigDecimal(19.99)后者又把 double 的精度问题带进来了。比较大小用compareTo不要用equals。compareTo忽略小数点后多余的 0比如 2.0 和 2.00 在compareTo看来相等但equals会认为不相等。加减乘除分别对应add、subtract、multiply、divide注意divide除不尽时要指定精度和舍入方式。只要在项目一开始就养成用 BigDecimal 的习惯后面做支付、做电商相关项目会少踩很多坑。2.4 接口和继承现在用不上但类结构里要留好位置如果说零基础阶段只需要 Account 一个类就能跑通那精通阶段就要考虑如果我以后要支持活期账户余额利息按月算和定期账户未到期取款要罚息怎么办建议现在就把 Account 定义成接口或者抽象类而不是直接写死成一个具体类。我比较推荐的做法是定义接口Account声明deposit、withdraw、getBalance等方法。写一个BaseAccount抽象类把公共字段账号、户名、余额和公共逻辑放进去。将来要加SavingsAccount活期、FixedAccount定期时各自继承 BaseAccount覆盖计算利息的逻辑。这样做的好处是Bank 类里所有方法都依赖接口而不是具体类以后扩展新账户类型Bank 不用改。改代码的范围越小越不容易引入 bug。这就是面向对象里开闭原则最简单的体现。3. 核心业务落地存款、取款、转账背后不只是加减法很多人觉得存取款不就是balance balance money吗其实工程上的难点在于每一步操作都要考虑校验、异常、状态一致性。这一章我把核心业务拆开讲把最容易出问题的几个地方说透。3.1 存款逻辑先校验再修改顺序不能反存款看起来最简单金额大于 0加到余额上。但我见过不少新手写出的版本是public void deposit(double money) { balance money; if (money 0) { System.out.println(金额错误); } }这段代码的问题是先改余额再校验。意味着用户输入 -100 时余额已经被扣掉 100 了然后才打印金额错误。正确做法一定是校验前置一旦不合格立刻抛出异常后面的更新代码根本不会执行。这也是我在写任何业务方法时的习惯方法入口先做参数校验再执行业务动作。public void deposit(BigDecimal amount) { if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalArgumentException(存款金额必须大于 0); } this.balance this.balance.add(amount); }3.2 取款逻辑余额不足怎么判断才严谨取款比存款多一个判断余额是否充足。最容易犯的错是用if (balance money)这种形式来判断但 balance 和 money 都是 BigDecimal 时不能直接用必须用compareTo。if (this.balance.compareTo(amount) 0) { throw new IllegalArgumentException(余额不足当前余额 this.balance); }还有一类边界情况余额刚好等于取款金额。比如余额是 100.00取款金额也是 100.00那应该允许成功取完后余额为 0。用compareTo(amount) 0判断等于时不触发异常是正确的。3.3 转账一个操作涉及两个账户最容易出脏数据转账逻辑上有两步从 A 账户扣钱往 B 账户加钱。这里就出现了一个非常经典的问题**如果扣完 A 的钱给 B 加钱时抛异常了怎么办**A 的钱没了B 的钱没到账就平不了。我推荐在最基础的版本里先用一个简单而稳妥的规则先执行转出再执行转入同时保证转入失败时整个转账操作不产生部分影响。在单机控制台版本里最常见的做法是像下面这样public void transfer(Account target, BigDecimal amount) { this.withdraw(amount); target.deposit(amount); }withdraw如果余额不足会在第一步就抛异常转账直接失败A 和 B 都不会变。如果target.deposit抛异常比如传了 null 账户那确实 A 的钱已经被扣了这个缺陷在单线程、并且我们自己控制传入 target 非空的情况下几乎不会发生。但如果想严谨一点可以在 deposit 失败时把 A 的余额回滚回去public void transfer(Account target, BigDecimal amount) { this.withdraw(amount); try { target.deposit(amount); } catch (RuntimeException e) { // 回滚把扣掉的钱加回来 this.deposit(amount); throw e; } }这个回滚的思想正是数据库事务里最核心的原子性概念在真实银行系统里更是重中之重。你在这个小项目里提前体会到后面学 Spring 事务时会有种原来如此的感觉。3.4 操作后要不要返回结果有经验的开发者会注意修改余额的方法一般返回什么我通常让 deposit、withdraw 返回void成功就正常返回失败就抛异常。查询余额的 getter 返回BigDecimal。不要把操作完打印一句话写进业务方法里比如System.out.println(存款成功)这种代码放进 Account 类会让类依赖控制台输出测试时非常别扭。想给用户反馈放在 Main 里 catch 异常后打印或者根据操作结果打印这才是合理的分层。4. 异常处理银行系统里最不能凑合的部分模拟银行项目如果不做异常处理程序遇到余额不足可能直接崩溃或者打印一堆看不懂的报错。真实业务里用户输入什么鬼东西都有可能程序不能一遇到非法输入就退出而要给用户清晰、友好的提示然后让流程继续。4.1 先分清两类异常业务异常和系统异常业务异常用户取款金额大于余额、存款金额为负数。这不是程序 bug而是业务规则不允许。应该用自定义异常明确告诉用户。系统异常读取文件失败、网络断开。这是环境或程序自身问题应该记录日志并让程序妥善退出或恢复。Java 的异常分受检异常checked和非受检异常unchecked。对于这个项目里的业务异常我建议继承RuntimeException因为账户余额不足是可以在调用前就通过 if 判断避免的情况用非受检异常写起来更简洁调用方想处理就 catch不想处理就继续往上抛。4.2 自定义异常类一眼看懂错在哪我一般会定义这样几个异常类// 余额不足 public class InsufficientBalanceException extends RuntimeException { public InsufficientBalanceException(String message) { super(message); } } // 非法金额负数、0、格式错误 public class IllegalAmountException extends RuntimeException { public IllegalAmountException(String message) { super(message); } } // 账户不存在 public class AccountNotFoundException extends RuntimeException { public AccountNotFoundException(String message) { super(message); } }然后在 Account 的方法里抛出这些更具体的异常public void withdraw(BigDecimal amount) { if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalAmountException(取款金额必须大于 0); } if (this.balance.compareTo(amount) 0) { throw new InsufficientBalanceException(余额不足当前余额 this.balance); } this.balance this.balance.subtract(amount); }为什么不用IllegalArgumentException因为它的语义太笼统catch 的时候很难区分到底是金额非法还是账户非法。自定义异常相当于给错误打了标签可读性和可维护性都高很多。4.3 输入校验让用户永远不能弄崩你的程序用户是攻击者也是糊涂蛋控制台程序要面对的是各种奇怪输入输入abc当金额、输入空字符串当账号、输入负数当存款金额。我常用一个简单的工具方法private static BigDecimal parseAmount(String input) { try { return new BigDecimal(input.trim()); } catch (NumberFormatException e) { throw new IllegalAmountException(金额格式不正确 input); } }这里有个特别值得注意的坑new BigDecimal(abc)会抛NumberFormatException所以必须用 try-catch 包住。另外如果你允许用户输入1,000这种带千分位的格式还需要先去掉逗号但控制台版本尽量别给自己加戏——就要求纯数字输入提示语写清楚就行。5. 让程序活起来控制台交互与主流程编排到这一章类和业务逻辑都有了但程序还不能直接跑。我们需要一个 main 方法把所有部件串起来。很多教材会给你一个一次性流程开户存钱取钱打印结果程序结束。但真实可用的程序应该是循环的——用户操作完一次回到菜单直到用户选择退出。5.1 Bank 类用 Map 管理所有账户账户不止一个怎么存数组当然也可以但增删账户麻烦。我直接用MapString, Accountkey 是账号value 是账户对象。好处是查找账户的时间复杂度是常数级而且天然支持根据账号唯一确定账户的业务规则。import java.util.HashMap; import java.util.Map; public class Bank { private MapString, Account accounts new HashMap(); private int nextAccountNumber 1000; // 开户自动生成账号 public Account openAccount(String name, BigDecimal initialBalance) { String accountId AC (nextAccountNumber); Account acc new Account(accountId, name, initialBalance); accounts.put(accountId, acc); return acc; } public Account findAccount(String accountId) { Account acc accounts.get(accountId); if (acc null) { throw new AccountNotFoundException(账号不存在 accountId); } return acc; } public void closeAccount(String accountId) { accounts.remove(accountId); } public int getAccountCount() { return accounts.size(); } }账号用AC1001、AC1002这种方式递增生成比让用户自己输账号好得多也省去了账号重复的麻烦。零基础的读者可能奇怪nextAccountNumber是先自增再使用所以第一个账号是 AC1001这是故意的看起来更真实。5.2 主程序循环一个 whlie(true) 加 switch 就够主流程最基础的版本import java.math.BigDecimal; import java.util.Scanner; public class BankConsoleApp { private static final Bank bank new Bank(); private static final Scanner scanner new Scanner(System.in); public static void main(String[] args) { while (true) { printMenu(); String choice scanner.nextLine().trim(); switch (choice) { case 1 - openAccount(); case 2 - deposit(); case 3 - withdraw(); case 4 - transfer(); case 5 - queryBalance(); case 0 - { System.out.println(感谢使用再见); return; } default - System.out.println(无效选项请重新输入); } } } private static void printMenu() { 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.print(请选择); } }这里用到了 Java 14 以后的switch箭头语法如果你还在用 Java 8可以改成传统的case 1: break;写法。练习题里经常考 Java 版本特性建议有条件的话直接用新语法顺便感受一下 Java 的演进。5.3 每个分支的交互细节比想象中多拿存款和取款举例真正的难点不是写方法而是处理用户输入private static void deposit() { System.out.print(请输入账号); String accountId scanner.nextLine().trim(); System.out.print(请输入存款金额); String amountStr scanner.nextLine().trim(); try { Account acc bank.findAccount(accountId); BigDecimal amount parseAmount(amountStr); acc.deposit(amount); System.out.println(存款成功当前余额 acc.getBalance()); } catch (RuntimeException e) { System.out.println(操作失败 e.getMessage()); } }这样写有一个好处底层抛出的任何业务异常都会在这个 catch 里被拦截打印成一行友好提示程序继续回到主菜单。如果用户输入根本没找到账号findAccount会抛AccountNotFoundException同样被捕获并提示账号不存在。转账交互稍微复杂一点因为要读两个账号和一个金额private static void transfer() { System.out.print(请输入转出账号); String fromId scanner.nextLine().trim(); System.out.print(请输入转入账号); String toId scanner.nextLine().trim(); System.out.print(请输入转账金额); String amountStr scanner.nextLine().trim(); try { Account from bank.findAccount(fromId); Account to bank.findAccount(toId); BigDecimal amount parseAmount(amountStr); from.transfer(to, amount); System.out.println(转账成功转出方余额 from.getBalance()); } catch (RuntimeException e) { System.out.println(转账失败 e.getMessage()); } }一个细节是转出账号和转入账号如果是同一个账户业务上应该直接拒绝。转账方法里加个 if 判断最稳妥if (from to) { throw new IllegalArgumentException(不能向自己转账); }这种细节不写用户自己转自己钱扣了又加回来结果虽然一样但会产生一笔毫无意义的交易记录严谨起见必须禁止。6. 进阶扩展把这个小项目往工程化方向推一把如果你已经能跑通上面的版本说明你具备了零基础入门的水平。但离精通还差一步**能不能让账户数据重启后还在能不能在多线程环境下保证余额正确能不能写出自动化的单元测试**这三点分别对应持久化、并发、测试也是很多 Java 面试题背后的实际场景。6.1 用序列化把账户数据持久化到本地文件现在已经有了 Map 存账户但程序一关数据全丢。真实银行不可能这样所以这个小项目要进阶第一步就是让数据落盘。最简单的方案是用 Java 自带的序列化机制让 Account 实现Serializable接口然后把整个 accounts Map 写到文件里。public class Bank implements Serializable { private static final long serialVersionUID 1L; private MapString, Account accounts new HashMap(); // ... }保存和加载private static void saveData(Bank bank) { try (ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(bank.dat))) { oos.writeObject(bank); } catch (IOException e) { System.out.println(保存失败 e.getMessage()); } } private static Bank loadData() { File file new File(bank.dat); if (!file.exists()) { return new Bank(); } try (ObjectInputStream ois new ObjectInputStream(new FileInputStream(file))) { return (Bank) ois.readObject(); } catch (IOException | ClassNotFoundException e) { System.out.println(读取存档失败将创建新银行 e.getMessage()); return new Bank(); } }主程序启动时Bank bank loadData();每次操作完调用saveData(bank)即可。这里有一个真实场景体会如果没有异常处理文件被损坏时程序会直接崩溃而有了 try-catch程序至少还能用全新的空银行启动不会让用户彻底无法使用。6.2 并发存取synchronized 到底锁的是什么练到这一步可以去想一个更专业的问题如果两个线程同时给同一个账户存钱余额会不会出错真实银行系统并发量极大靠的是数据库事务和锁来保证数据一致。控制台单线程版本看不出问题但你可以写一段多线程测试代码来体会Account acc new Account(A001, 张三, new BigDecimal(1000)); // 模拟 10 个线程同时取款 100 for (int i 0; i 10; i) { new Thread(() - { try { acc.withdraw(new BigDecimal(100)); } catch (InsufficientBalanceException e) { System.out.println(取款失败 e.getMessage()); } }).start(); }如果不做同步理论上可能出现两个线程同时读到余额 1000然后各自扣 100最后余额变成 900 而不是 800。为什么因为balance balance.subtract(amount)这行代码不是原子的它分成了读旧值、算新值、写回三步多线程环境下可能交错执行。解决方式是在 withdraw 方法上加synchronizedpublic synchronized void withdraw(BigDecimal amount) { // ... }synchronized保证同一时刻只有一个线程能执行这个方法。这里要理解一个关键点锁是加在对象上的不同 Account 对象各有一把锁所以存 A 账户和存 B 账户互不影响但两个线程同时操作同一个账户时后一个会等前一个完成。这正是银行场景需要的粒度。6.3 JUnit 单元测试把看似正确变成确实正确很多初学者写完程序手点几下觉得没问题就交差了。但方法一多、改动一多以前能跑的功能可能悄悄坏掉。这时候单元测试的价值就出来了。用 JUnit 5 写一个简单的取款测试import org.junit.jupiter.api.Test; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.*; public class AccountTest { Test void testWithdrawSuccess() { Account acc new Account(A001, 张三, new BigDecimal(500)); acc.withdraw(new BigDecimal(200)); assertEquals(new BigDecimal(300), acc.getBalance()); } Test void testWithdrawInsufficientBalance() { Account acc new Account(A001, 张三, new BigDecimal(100)); assertThrows(InsufficientBalanceException.class, () - acc.withdraw(new BigDecimal(200))); } Test void testDepositNegativeAmount() { Account acc new Account(A001, 张三, new BigDecimal(100)); assertThrows(IllegalAmountException.class, () - acc.deposit(new BigDecimal(-50))); } }这三个测试分别覆盖了正常取款余额不足被拦截非法金额被拦截。以后你改 Account 的代码跑一遍测试就知道有没有破坏原有规则。这个习惯如果从一开始就有到后面面试写算法题、做项目都会比同龄人省心很多。在我带过的学员里会写测试几乎成了中高级开发的隐性门槛它逼着你把方法设计得职责单一、依赖清晰——因为不好测的代码通常就是设计有问题的代码。7. 踩坑实录这些年我在银行模拟项目里见过最多的错做了这么多年项目、看了无数份实验报告有些错误反复出现我专门整理出来既是帮大家避坑也算是一个快速排错清单。7.1 瞎用 int 或 double 存金额然后发现精度不对这是最经典的问题。有人的实验报告里写double balance 1000.0; balance 0.1; System.out.println(balance); // 1000.1 单看一两步好像没错但一旦涉及大量运算浮点数误差就会累积。更隐蔽的是用double做比较balance 0.3这种写法在很多场景下为 false。我从一开始就要求用 BigDecimal就是不想让学员在这个坑里浪费时间。7.2 把用户输入直接拿来运算不 try-catchnew BigDecimal(abc)直接抛异常程序就崩了。正确姿势永远是在解析处包一层 try-catch或者封装成 parse 方法统一处理。这一条几乎每次都能在我看的代码里找到反例。7.3 先改余额再校验或者校验不完整前面提到过先扣款再判断金额是否合法这种代码藏着一个大雷一旦金额非法余额已经被污染了。所有业务方法的第一行应该是参数合法性校验而不是业务运算。7.4 账号不存在时返回 null然后在 main 里到处判断 null我看到很多人写findAccount时找不到就return null。调用方拿到 null 后一不小心就acc.getBalance()直接抛NullPointerException。与其四处判断 null不如让findAccount找不到就抛AccountNotFoundException。这样要么返回一个有效账户要么抛出明确异常不存在第三种模糊状态。这也是我写代码时的一个核心理念尽量让非法状态不可表示。7.5 程序无法退出或者退出时数据没保存main 里用while (true)没问题但如果没有退出分支或者退出时忘记调用保存方法那用户关掉程序后所有操作白做。记得在退出分支里先保存数据再 break 或 return。下面是我常用的一套调试小技巧给还不习惯看堆栈的新手参考多看堆栈第一行也就是Exception in thread main后面那句话它会直接告诉你异常类型和出错行号。在关键方法前后打印入参和余额确认到底哪一步和预期不符。怀疑并发问题时用Thread.sleep(10)放大竞争窗口更容易看到错误结果。想快速验证某个类不要每次都走完整主菜单直接写一个 main 方法或者一个 JUnit 测试单独调用那个类的方法。8. 后面还可以怎么玩从控制台到更真实的业务系统基础版、进阶版都跑通以后这个项目的天花板还远没有到头。我个人认为以下几个方向可以按照自己的兴趣和阶段去扩展。8.1 加交易记录让每一笔钱都有迹可循现在程序只存余额但真实银行一定会有流水。你可以增加一个TransactionRecord类记录交易类型、金额、时间、对方账号然后放在 Account 里每次存取款都追加一条记录。查询流水的方法也加上输入账号打印所有交易记录。这样做之后你的实验报告会明显比只能看余额的版本高一个档次。8.2 加利率和账户类型把继承、多态用起来定义抽象类 BaseAccount再派生活期账户和定期账户。活期账户按月计息定期账户提前支取会损失利息。这个扩展会逼着你用到抽象方法、方法重写和向上转型正好把面向对象三大特性真正练熟。8.3 换成数据库存储从文件到 MySQL等你把 Java 基础练扎实了可以尝试用 JDBC 把账户数据存到 MySQL 里。你会发现业务逻辑几乎不用改只要把 Bank 类里加载、保存的部分换成数据库读写。到这一步你已经慢慢摸到企业级开发的门槛了。如果再配合 Spring Boot、MyBatis 这些框架那就完全是另一个领域的事情但底层还是你现在写的那一套业务方法。每次我带新人跑到这一步都会提醒一句框架会变、数据库会换但类职责清晰业务校验前置异常语义明确这些基本功一旦在模拟银行这种小项目里养成习惯后面做任何项目都受益。别小看这个控制台里的存取款程序把它写透你对 Java 的理解绝对会上一个台阶。
返回列表