资讯详情 Java Swing餐厅点餐系统实战:界面构建、订单流转与持久化
📅 2026/10/27 6:57:36
简介基于JavaSwing的餐厅点餐系统完整工程文件可作为Java课程设计、毕业设计或Swing入门进阶的参考项目。系统面向用餐客户与餐厅管理员两类角色涵盖管理员登录并查看营业总账单、点菜选菜、顾客下单结账、修改餐厅名称字体样式与颜色四个功能模块并对应四个操作窗口完整覆盖点餐流程中的核心业务。资源共计119个文件压缩包仅1.56MB其中4个java源文件为系统代码15个class为编译后的字节码92个jpg为运行截图或界面素材还包含txt说明、docx文档以及Eclipse项目配置文件classpath、project、prefs导入Eclipse并配置JDK即可直接运行。项目代码将界面与业务逻辑分层结构清晰便于学习Swing的事件监听、菜单栏搭建和窗口间数据传递。已有2098人学习下载适合希望通过实际案例理解Java桌面应用开发流程的读者。1. 餐厅点餐系统到底在做什么Swing 桌面应用的真实边界“基于JavaSwing实现餐厅点餐系统”是Java入门阶段出现频率极高的项目标题大学生拿它交课程设计初级开发者拿它练桌面程序手感。这类项目看起来不过是一个带图形界面的 CRUD 小程序实际会把面向对象、事件监听、集合框架、文件读写甚至多线程全部串在一起是少数几个能完整训练 Java 基础的项目载体。一个反直觉的点在于Swing 常被评价为“过时技术”但在门店点餐、内部工具、教学演示这类场景里它依然比 Electron 和 JavaFX 更省事JDK 自带控件不用引入前端构建链打包后只有一个 jar 文件装个 JRE 就能跑。这里不讲课设范文只把点餐系统从头拆开——菜单展示、购物车、订单流转、本地存储、典型踩坑最后给你一套能直接运行的骨架和验证方法。2. 开工前先拆四层Swing 界面的边界与数据存储选型写桌面应用最忌讳一上来就拖控件写事件。Swing 虽然没有强制分层但想让它成为一个“系统”而不是“界面堆”我一般会先把代码按职责拆成四块视图层view负责窗口和控件模型层model定义菜品、订单的实体数据访问层dao管增删改查业务层service处理订单流转和结算。界面代码只做两件事把用户动作翻译成业务调用再把业务结果渲染回屏幕。这样菜品从 10 道涨到 100 道时你只需要动 DAO 层界面一行不用改。2.1 为什么选 Swing 而不是 JavaFX / Web 前端Swing 是 JDK 自带的图形界面工具箱从 Java 1.2 开始就存在底层基于 JComponent 体系。JavaFX 功能更强、样式更现代但它从 JDK 11 起被剥离出标准 JDK需要单独引入依赖对课设环境和内网部署都不友好如果考虑 Web 前端配 Spring Boot 的方案开发成本又抬高了不止一个量级——前后端分离意味着你要额外管理 Node 工具链或模板引擎。选 Swing 的决策依据通常很朴素目标机器有 JRE 就能运行无外部控件依赖开发环境只需要纯 JDK。而且对理解事件驱动编程有直接帮助。你看 Java 面试题里大量考察的“监听器模式”“多线程与界面刷新”都能在 Swing 代码里看到活例子。对“餐厅点餐系统”这个范围Swing 的应用边界就是单机部署、数据量不大、用户数不超过几个服务员加后厨完全在它的舒适区内。2.2 最小项目结构与实体模型Dish、Order、OrderDetail我不会给这个项目加 Spring、MyBatis 这类重量级框架Swing 项目加上它们反而让问题复杂化。常见的结构是一个 Maven 工程包名按职责划分src/main/java/com/restaurant/ ├── App.java // main 入口负责启动窗口 ├── model/ │ ├── Dish.java // 菜品实体 │ ├── Order.java // 订单头 │ └── OrderDetail.java // 订单明细一份订单含多个菜品 ├── dao/ │ ├── DishDao.java // 菜品内存存取 │ └── OrderDao.java // 订单读写 ├── service/ │ ├── OrderService.java // 下单、状态流转 │ └── SettlementService.java // 金额计算 └── view/ ├── MainFrame.java // 主窗体 ├── MenuPanel.java // 左侧菜单区 ├── CartPanel.java // 右侧购物车区 └── OrderListPanel.java // 订单列表窗口实体类建议写成纯 POJO不带任何 Swing 依赖这样业务逻辑和 DAO 层可以脱离界面单独测试。Dish 至少要包含 id、name、price、category、sales 五个字段Order 要包含订单号、桌号、明细列表、总金额、下单时间、状态。OrderDetail 单独拆出来的理由是同一个订单里菜品可能重复出现多次如果不拆订单实体就要维护一个二维的“菜品数量”结构。用组合关系Order 里持有 List 每个 OrderDetail 持有 Dish id 和购买数量。// Order.java -- 订单头字段尽量贴合实际点餐场景 public class Order { private String orderId; // 订单号建议生成规则固定前缀时间戳 private int tableNo; // 餐桌号 private ListOrderDetail details; // 该订单包含的菜品明细 private double totalAmount; // 结算总金额保存快照而非实时计算 private LocalDateTime createTime; // 下单时间 private String status; // 订单状态 public double calculateTotal() { return details.stream() .mapToDouble(OrderDetail::getSubtotal) .sum(); } }注意calculateTotal放在实体里是因为金额计算在订单提交、订单列表展示、结算三个地方都会用到放在实体里没有额外副作用比每次在 Service 里手写循环省事。totalAmount存的是结算快照不是实时计算的这样订单落盘后哪怕菜品价格改了历史订单金额也不会变。2.3 数据存储先选内存 Map再补 JSON 文件兜底桌面点餐系统的数据量不大常见做法是程序运行期内用内存存取再把数据落成文件。先看三个存储方案的取舍方案优点缺点适用场景内存 Map零依赖、启动即用重启丢失临时演示、开发阶段JSON 文件只需引入 Gson数据可读、可手工修并发写入需手工同步单机点餐系统SQLite/嵌入式数据库查询能力强、事务安全需要额外驱动代码订单量大、需要按日期统计报表我一般建议先用内存 Map再在数据访问层外层补一个 JSON 文件兜底。这样不改变上层业务逻辑只替换 DAO 实现。DishDao 的内存版核心代码长这样// DishDao.java -- 数据访问层先给内存实现 public class DishDao { // 用 LinkedHashMap 而不是 HashMap保持菜单插入顺序 private final MapString, Dish dishMap new LinkedHashMap(); public ListDish findAll() { return new ArrayList(dishMap.values()); } public ListDish findByCategory(String category) { return dishMap.values().stream() .filter(d - d.getCategory().equals(category)) .collect(Collectors.toList()); } public Dish findById(String id) { return dishMap.get(id); } }LinkedHashMap的选择很关键HashMap在遍历时无序直接会导致菜单每次启动排列顺序都不一样。findByCategory用 stream 过滤是 Java 8 以后的常见写法比手写 for 循环加 if 判断更短。如果这个类再往上扩展就把增删改查的接口抽象出来用ListDish的批量载入方法一次性灌入初始化数据。要落盘持久化我推荐 Gson 而不推荐 Java 自带的 ObjectOutputStream。序列化文件是二进制一旦实体类字段变动老文件直接读不出来JSON 是文本能打开看、能手工修、还能在项目之间迁移。Gson 是单个 jar 依赖引入成本很低dependency groupIdcom.google.code.gson/groupId artifactIdgson/artifactId version2.10.1/version /dependency这段依赖如果课程设计不允许用 Maven也可以直接把 gson.jar 放进项目 lib 目录并在 IDE 里添加到 classpath效果一致。之后在 DAO 里用同一个 Gson 实例做序列化和反序列化尽量避免 new 多个实例因为 Gson 本身线程安全可复用。提示本地 JSON 持久化只适合单机、低并发场景。如果点餐数据要汇总到总部服务器做经营分析需要换数据库加后端接口不能只往 JSON 文件里写。3. 用 Swing 搭出点餐主界面JFrame、JTable 和购物车联动界面部分的目标很明确服务员能在一屏内看到菜单、点菜、调数量、看总价。传统做法是左侧放菜单区带分类 Tab 页的 JTable右侧放购物车区JTable 加合计金额底部放结算相关按钮。窗口用 JFrame 承载内部区域交给 JPanel 和布局管理器。3.1 主窗口布局BorderLayout 划分菜单区、购物车区与状态栏JFrame 默认布局是 BorderLayout正好适合“左大右小、底条固定”的点餐界面。CENTER 区域放菜单面板EAST 区域放购物车面板SOUTH 区域放状态栏——显示当前桌号、已点菜数、合计金额。先写主窗体的最小骨架// MainFrame.java -- 主窗口骨架 public class MainFrame extends JFrame { public MainFrame() { setTitle(餐厅点餐系统); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(1024, 768); setLocationRelativeTo(null); // 窗口居中显示 add(new MenuPanel(), BorderLayout.CENTER); add(new CartPanel(), BorderLayout.EAST); add(createStatusBar(), BorderLayout.SOUTH); // 所有控件就绪后再显示窗口 setVisible(true); } private JPanel createStatusBar() { JPanel bar new JPanel(new FlowLayout(FlowLayout.LEFT)); bar.add(new JLabel(当前桌号A01)); bar.add(new JLabel(合计¥ 0.00)); return bar; } }setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)决定了点关闭按钮时 JVM 直接退出如果要做“误关提示”改成 DO_NOTHING_ON_CLOSE 再在 windowClosing 事件里写确认逻辑。setLocationRelativeTo(null)传 null 表示屏幕居中很多初学者的窗口跑到左上角就是少了这一行。不要在图 3-1 的构造函数里提前调用 setVisible否则可能出现窗口显示后控件顺序不对的情况——Swing 老手通常把 setVisible 放在构造函数最后一行。菜单区和购物车区如何通信是界面耦合的第一道坎。常见做法是让 MainFrame 持有 MenuPanel 和 CartPanel 的引用在 MenuPanel 的“加购”按钮事件里直接调用cartPanel.addDish(dish)。这属于界面内部的直接调用对单窗口应用足够了如果后面要做多窗口联动再引入观察者模式也不晚。3.2 菜单区表格化把 Dish 集合灌进 JTable 的注意事项菜单区最大的坑不在布局而在 JTable 的数据模型。很多人直接往 TableModel 构造器传二维数组其实最稳的写法是先用 DefaultTableModel 定义列名再逐行 addRow。列名跟 Dish 字段一一对应后厨加菜、改价都会反馈到这张表上。// MenuPanel.java -- 菜单表格初始化 public class MenuPanel extends JPanel { private final DishDao dishDao; private final JTable table; public MenuPanel(DishDao dishDao) { this.dishDao dishDao; setLayout(new BorderLayout()); DefaultTableModel model new DefaultTableModel( new Object[]{编号, 菜品名称, 分类, 单价, 销量}, 0 ); table new JTable(model) { Override public boolean isCellEditable(int row, int column) { return false; // 禁止双击直接改单元格 } }; // 名称列宽一点、编号列窄一点靠建议宽控制 table.getColumnModel().getColumn(1).setPreferredWidth(200); table.setRowHeight(32); reload(); add(new JScrollPane(table), BorderLayout.CENTER); add(createButtonBar(), BorderLayout.SOUTH); } public void reload() { DefaultTableModel model (DefaultTableModel) table.getModel(); model.setRowCount(0); // 先清空再重灌 for (Dish d : dishDao.findAll()) { model.addRow(new Object[]{ d.getId(), d.getName(), d.getCategory(), String.format(%.2f, d.getPrice()), d.getSales() }); } } }匿名内部类里重写isCellEditable是个常见细节否则用户双击就能改单元格里的价格测试时会造成混乱。reload()里先setRowCount(0)再addRow是避免旧数据残留的标准手法。String.format(%.2f, price)控制价格显示两位小数直接用 double 塞进表格会出现 12.0 或 12.000000001 这类尴尬数字。列宽用 setPreferredWidth 只是给布局管理器的“建议值”不是强制值所以还要配合table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF)和 JScrollPane 的滚动策略一起调。3.3 购物车数量增减与金额联动事件监听的最小实现购物车是点餐系统的状态中心。这里选择用LinkedHashMapString, Integer记录“Dish id - 已点数量”用另一个MapString, Dish保存菜品快照。为什么不能只存 Dish 对象因为同一个菜品加两次要在表格里合并成一行而不是出现两行。合并逻辑用 Map 的 merge 方法一行就能做// CartPanel.java -- 加购、减购与金额刷新 public class CartPanel extends JPanel { private final MapString, Integer countMap new LinkedHashMap(); private final MapString, Dish dishMap new LinkedHashMap(); private final DefaultTableModel cartModel new DefaultTableModel( new Object[]{菜品, 单价, 数量, 小计}, 0 ); private JLabel totalLabel; public void addDish(Dish dish) { // merge: 已存在则数量1不存在则数量置为1 countMap.merge(dish.getId(), 1, Integer::sum); dishMap.put(dish.getId(), dish); refreshCart(); } public void decreaseDish(String dishId) { if (!countMap.containsKey(dishId)) return; int newCount countMap.get(dishId) - 1; if (newCount 0) { countMap.remove(dishId); dishMap.remove(dishId); } else { countMap.put(dishId, newCount); } refreshCart(); } private void refreshCart() { cartModel.setRowCount(0); double total 0; for (Map.EntryString, Integer entry : countMap.entrySet()) { Dish dish dishMap.get(entry.getKey()); double subtotal dish.getPrice() * entry.getValue(); total subtotal; cartModel.addRow(new Object[]{ dish.getName(), String.format(%.2f, dish.getPrice()), entry.getValue(), String.format(%.2f, subtotal) }); } totalLabel.setText(String.format(合计¥ %.2f, total)); } }countMap.merge(..., Integer::sum)是这次实现里最值得记的一行比“先 containsKey 判断再加”短得多。refreshCart每次全量重建表格模型对桌面上几十个菜品这个规模完全没问题如果商品上百就要改成增量更新避免整表重绘。数量减到 0 的条目直接移除结算时只遍历存在的 Map 项不会出现数量为 0 的脏数据。到这里主界面的静态结构和动态联动都有了下一步把订单提交、状态流转和结算接进来才算一个闭环系统。4. 订单流转与结算状态机、优惠规则和一键下单点餐系统的核心不止是“把餐品摆上桌”还要能记录状态。常见做法是把订单状态设计成枚举因为枚举在 Java 里天然具备“不可变、可比较、可带显示文本”的特性比用字符串状态更防呆——字符串拼写错了编译器不会发现枚举写错了会直接报编译错。4.1 订单状态的四种流转已下单、制作中、待结算、已完成先看订单状态枚举的设计// OrderStatus.java -- 订单状态机 public enum OrderStatus { PLACED(已下单), COOKING(制作中), READY(待结算), COMPLETED(已完成); private final String label; OrderStatus(String label) { this.label label; } public String getLabel() { return label; } }状态流转我建议做在 OrderService 里用一个 switch 明确允许的前进路径而不是让任意代码直接 setStatus// OrderService.java -- 下单与状态推进 public class OrderService { private final OrderDao orderDao; public Order placeOrder(int tableNo, ListOrderDetail details) { Order order new Order(); order.setOrderId(T tableNo - System.currentTimeMillis()); order.setTableNo(tableNo); order.setDetails(details); order.setCreateTime(LocalDateTime.now()); order.setStatus(OrderStatus.PLACED); order.setTotalAmount(order.calculateTotal()); orderDao.save(order); return order; } public void advanceStatus(Order order) { switch (order.getStatus()) { case PLACED - order.setStatus(OrderStatus.COOKING); case COOKING - order.setStatus(OrderStatus.READY); case READY - order.setStatus(OrderStatus.COMPLETED); default - throw new IllegalStateException(已完成订单不能再次流转); } orderDao.update(order); } }订单号用T tableNo - System.currentTimeMillis()带桌号前缀的毫秒级时间戳基本不会重复。switch 加箭头语法是 Java 14 之后的写法如果你的环境是 Java 8就要回退成case PLACED: order.setStatus(...); break;的传统写法。抛出 IllegalStateException 是防御式编程防止把已完成订单又翻回制作中这种状态错乱在现实里会造成后厨和前台对不上账。注意如果目标运行环境是 JDK 8上述 switch 箭头语法无法编译必须改回传统冒号写法。4.2 结算逻辑金额统计、折扣率与找零计算结算模块的重点不在乘法和加法而在“钱数别用 double 裸算”。Java 的 double 做浮点运算会产生 0.1 0.2 0.30000000000000004 这类误差显示在收银界面就是事故。这个规模的项目里我采用的折中方案是金额字段在实体里用 double 存储方便显示但计算时全部转成“分”单位的 long最后再转换回元。// SettlementService.java -- 金额计算避免浮点误差 public class SettlementService { // 传入菜品明细和折扣率返回保留两位小数的金额 public double settle(ListOrderDetail details, double discount) { long totalFen 0; for (OrderDetail detail : details) { // 单价转成分再乘以数量避免浮点累积误差 long fen Math.round(detail.getPrice() * 100) * detail.getCount(); totalFen fen; } double yuan totalFen / 100.0; return Math.round(yuan * discount * 100) / 100.0; } // 找零: 输入实收金额返回应找金额 public double calcChange(double paid, double payable) { long paidFen Math.round(paid * 100); long payableFen Math.round(payable * 100); return (paidFen - payableFen) / 100.0; } }Math.round(detail.getPrice() * 100)先把单价转成“分”再和数量做乘法把浮点误差控制在最小范围。结算后的总金额快照保存到订单实体里不每次现算避免后续菜品价格变动导致历史订单金额漂移。折扣参数抽成方法入参而不是写进方法内部判断后面加“午市特惠”“会员价”时不用改这个方法本身。4.3 持久化到 JSON 文件把订单写成本地文件订单落盘要在 DAO 层处理用 Gson 把订单列表序列化到本地文件。有一个容易被忽略的关键点订单明细里保存的是菜品快照名称、单价、小计不是只存菜品 id。因为菜单会改价、菜品可能下架等回头重新打开已结算订单时如果菜品 id 已不存在整个订单就无法显示。// OrderDao.java -- 订单的 JSON 文件读写 public class OrderDao { private static final String FILE_PATH data/orders.json; private final Gson gson new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(LocalDateTime.class, (JsonSerializerLocalDateTime) (src, type, context) - context.serialize(src.toString())) .create(); private final ListOrder orderList new ArrayList(); public void save(Order order) { orderList.add(order); writeFile(); } public ListOrder loadAll() { File file new File(FILE_PATH); if (!file.exists()) return new ArrayList(); try (FileReader reader new FileReader(file)) { // 反序列化泛型集合必须用 TypeToken Type type new TypeTokenListOrder(){}.getType(); ListOrder loaded gson.fromJson(reader, type); orderList.clear(); if (loaded ! null) orderList.addAll(loaded); } catch (IOException e) { // 加载失败不直接退出保留已有内存数据 e.printStackTrace(); } return orderList; } private void writeFile() { try { File parent new File(FILE_PATH).getParentFile(); if (!parent.exists()) parent.mkdirs(); try (FileWriter writer new FileWriter(FILE_PATH)) { gson.toJson(orderList, writer); } } catch (IOException e) { e.printStackTrace(); } } }用registerTypeAdapter处理LocalDateTime是因为 Gson 原生不支持 Java 8 时间类型序列化时会直接抛 UnsupportedOperationException。TypeTokenListOrder是 Gson 反序列化泛型集合的标准写法如果直接gson.fromJson(reader, List.class)会丢失元素类型读回来的是一个 List edtreemap 运行期再 get 字段会抛 ClassCastException这是最常见的黑匣子报错来源。文件路径建议写成常量方便以后挪到配置类统一管理。订单落盘后要清空当日订单可以做成删除并重建文件真想稳妥直接把原文件改名备份成带时间戳的历史文件就是最简单的后悔药。5. Swing 避坑五个最常见的界面异常与排查顺序这一章不贴大段实现代码写实际调试中遇到最多的五个问题每条按“现象 → 原因 → 解决”的顺序展开。它们有个共同特征代码能编译能运行但界面行为完全不符合预期。5.1 窗口启动白屏或不显示setVisible 与 EDT 的问题现象Java 程序编译运行后控制台正常但 JFrame 要么不出现要么出现后白屏。原因第一种是忘了调用frame.setVisible(true)第二种是把耗时的数据加载放在 main 线程里在窗口显示前执行了长时间阻塞导致界面线程迟迟得不到渲染机会。解决把 setVisible 放到构造函数最后一行把文件加载、网络请求这类耗时操作放到SwingUtilities.invokeLater里再加载。主线程启动界面的标准格式是// App.java -- Swing 程序的规范入口 public class App { public static void main(String[] args) { SwingUtilities.invokeLater(() - { new MainFrame(); // 所有 Swing 组件都在 EDT 线程中创建 }); } }SwingUtilities.invokeLater的作用是把 Runnable 提交到事件分发线程EDT的队列里让整个界面的创建和刷新发生在同一个线程上避免多线程同时操作控件造成状态错乱。记住main 方法里不直接 new JFrame。5.2 JTable 刷新卡顿setRowCount 和 setModel 的区别现象菜品数量增加或购物车更新时表格闪动明显鼠标滚轮滚动流畅度严重下降。原因每次刷新直接调用table.setModel(new DefaultTableModel(...))等于把整张表的列结构也重建了Swing 会重算列宽、重绘所有单元格。而数据变化场景应该复用同一个模型只清行加行。解决在初始化时创建一次 model之后一直用model.setRowCount(0)加model.addRow(...)。如果只改某一行的某个单元格还可以用model.setValueAt(...)做单格更新连整行重绘都省了。5.3 界面假死耗时的任务阻塞 EDT 线程现象点击“结算”按钮后窗口转圈、无法拖动几秒后才恢复。原因Swing 是单线程模型所有 UI 更新都跑在 EDT 线程上。如果在事件回调里直接做 Thread.sleep、大文件读写、数据库查询EDT 被占住界面就只能干等。解决耗时任务放SwingWorker后台线程在 done() 里回 UI 更新结果。判断标准一句话任何超过几百毫秒的操作都不要直接写在 ActionListener 事件方法里。SwingWorker 的 doInBackground 里跑耗时逻辑done 里更新界面本质是把“后台计算”和“界面渲染”两个阶段拆开。5.4 中文乱码与字体编码不一致和默认字体现象界面上中文全是方块或乱码控制台输出正常。原因编译编码和运行环境编码不一致。Windows 下默认编码是 GBK如果源码是 UTF-8 而 javac 不加-encoding参数字符串常量就会被 GCK 错误解码另一个原因是 Swing 默认字体 Dialog 对某些中文环境覆盖不全。解决Maven 项目在 pom 里配置项目编码属性手动编译用javac -encoding UTF-8程序里可以统一替换字体为Microsoft YaHei或SimSun界面上的中文就不会变成豆腐块。乱码问题纯靠 UI 层解决是治标不治本编码必须在编译参数层面就对齐。5.5 高 DPI 屏幕下窗口模糊、控件被拉伸现象在 1440P 或 4K 屏上运行窗口整体被 Windows 缩放拉伸字体发虚。原因Swing 的默认设计基于 96 DPI 逻辑坐标高分屏下系统缩放比例不是 100% 时Java 进程没正确感知缩放因子。解决Java 9 以上可以在启动参数加-Dsun.java2d.uiScale.enabledtrue并在 JFrame 里主动调用setMinimumSize限制最小尺寸更通用是打包成可执行 jar 后附一个启动脚本写入-Dsun.java2d.uiScale150配合实测。这个问题在不同 JDK 版本下表现差异很大最好在目标机器上实测后再固定参数。6. 从能跑到好用功能验证清单、打包分发与扩展方向6.1 用手工回归测试清单验证核心流程这里不引入 JUnit 也能验收但至少要有一套每次改代码后都能快速过一遍的手工清单启动后窗口能正常居中显示且表格里有初始化菜品点加购后购物车表格出现对应菜品数量累加点减号后数量减到 0 时该行被清除提交订单后状态变为“已下单”订单列表能看到结算金额等于各明细小计之和重启程序后订单文件能被加载菜单保持原始顺序。6.2 不用 Maven 也能打 jar 包并分发如果项目用了 Mavenpackage 阶段直接生成可执行 jar但总有人环境里没配 Maven命令行也能完成# 先编译到 out 目录注意指定 UTF-8 编码 javac -encoding UTF-8 -d out -classpath lib/gson.jar \ src/com/restaurant/*.java src/com/restaurant/view/*.java # 打可执行 jarcfe 中的 e 参数指定入口类 jar cfe restaurant.jar com.restaurant.App -C out . java -jar restaurant.jarcfe参数里的 e 指定 main 入口类之后-C out .表示把 out 目录下所有字节码打进 jar。分发时把 restaurant.jar 和 lib/gson.jar 放到同一目录用java -jar -classpath lib/gson.jar restaurant.jar运行即可。6.3 可扩展方向SQLite、后厨屏、小票打印扩展不是推翻重来。把 DishDao 和 OrderDao 换成 SQLite 实现Service 和 View 完全不用改订单状态推进后可以在状态栏刷新时推送一个模拟消息给“后厨端”或者在控制台打一行日志。我自己一般会在结算完成时调用一个打印命令把订单内容输出成文本文件客户要纸质小票也能二次处理。下一步最好的练习是给这个点餐系统补一个“全天订单统计”界面用 DAO 层数据做一个按小时分组的柱状展示这会再把集合操作和小组件渲染串一遍。跑得通、能讲清、敢改造这三个标准比界面花哨重要得多也是这个项目投入价值的核心所在。希望帮到你。本文还有配套的精品资源点击获取