System.out.printf()
格式化输出商品名称、单价、数量和金额,创建商品对象列表,遍历计算总价,最后打印表头和汇总信息,关键点包括对齐列宽、保留小数位数和添加分隔线增强可读性。在Java中打印购物小票是零售、餐饮等系统开发的常见需求,以下是三种实用方法,涵盖不同场景(控制台调试、物理打印机输出、PDF生成),均采用标准Java API或主流库实现,确保代码健壮性和可维护性。
基础方案:控制台打印(调试用)
适用于开发阶段快速验证小票格式和逻辑。
import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; public class ConsoleReceiptPrinter { public static void main(String[] args) { // 模拟订单数据 Map<String, Double> items = new HashMap<>(); items.put("可口可乐", 3.50); items.put("薯片", 8.00); items.put("矿泉水", 2.00); printReceipt("C001", items, 0.9); // 9折优惠 } public static void printReceipt(String orderId, Map<String, Double> items, double discount) { StringBuilder sb = new StringBuilder(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 小票头部 sb.append("n============ 超市收银小票 ============n"); sb.append("订单号: ").append(orderId).append("n"); sb.append("时间: ").append(sdf.format(new Date())).append("n"); sb.append("-----------------------------------n"); // 商品列表 double subtotal = 0; for (Map.Entry<String, Double> entry : items.entrySet()) { String itemName = entry.getKey(); double price = entry.getValue(); sb.append(String.format("%-10st¥%.2fn", itemName, price)); // 左对齐商品名 subtotal += price; } // 费用计算 double discountedAmount = subtotal * discount; double savings = subtotal - discountedAmount; sb.append("-----------------------------------n"); sb.append(String.format("小计: tt¥%.2fn", subtotal)); sb.append(String.format("折扣: tt%.0f折n", discount * 10)); sb.append(String.format("优惠: tt¥%.2fn", savings)); sb.append(String.format("总计: tt¥%.2fn", discountedAmount)); sb.append("===================================n"); sb.append("感谢惠顾!客服电话:400-123-4567n"); System.out.println(sb.toString()); } }
输出效果:
============ 超市收银小票 ============
订单号: C001
时间: 2025-10-05 14:30:25
-----------------------------------
可口可乐 ¥3.50
薯片 ¥8.00
矿泉水 ¥2.00
-----------------------------------
小计: ¥13.50
折扣: 9折
优惠: ¥1.35
总计: ¥12.15
===================================
感谢惠顾!客服电话:400-123-4567
实战方案:连接物理打印机
使用 javax.print
API 直接驱动打印机(支持USB/网络打印机)。
import javax.print.*; import javax.print.attribute.HashPrintRequestAttributeSet; import javax.print.attribute.PrintRequestAttributeSet; import javax.print.attribute.standard.Copies; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; public class PhysicalPrinterService { public static void main(String[] args) { String receiptContent = generateReceiptContent(); // 生成小票文本 try { // 1. 获取打印机服务 PrintService printer = PrintServiceLookup.lookupDefaultPrintService(); if (printer == null) { throw new IllegalStateException("未找到默认打印机"); } // 2. 设置打印参数 PrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet(); attributes.add(new Copies(1)); // 打印份数 // 3. 创建打印任务 DocPrintJob job = printer.createPrintJob(); Doc doc = new SimpleDoc( new ByteArrayInputStream(receiptContent.getBytes(StandardCharsets.UTF_8)), DocFlavor.INPUT_STREAM.AUTOSENSE, null ); // 4. 执行打印 job.print(doc, attributes); System.out.println("小票打印任务已发送"); } catch (Exception e) { e.printStackTrace(); } } private static String generateReceiptContent() { // 复用控制台方案中的生成逻辑 return new ConsoleReceiptPrinter().generateReceiptString(); } }
关键注意事项:
- 中文乱码问题:
确保打印机支持UTF-8编码,热敏打印机通常需设置为中文字库模式(如ESC/POS指令集)。 - 打印机选择:
可通过PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
列出所有打印机。 - 格式对齐:
使用制表符t
或空格调整列宽,推荐固定宽度字体(如”Courier New”)。
扩展方案:生成PDF小票
使用 Apache PDFBox 库生成可保存/打印的PDF文件(需添加Maven依赖):
<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.27</version> </dependency>
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDType1Font; import java.io.IOException; public class PdfReceiptGenerator { public static void main(String[] args) throws IOException { try (PDDocument document = new PDDocument()) { PDPage page = new PDPage(); document.addPage(page); try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) { contentStream.setFont(PDType1Font.COURIER_BOLD, 14); contentStream.beginText(); contentStream.newLineAtOffset(50, 700); // 起始坐标 // 逐行写入内容(实际项目可封装为方法) contentStream.showText("============ 超市电子小票 ============"); contentStream.newLineAtOffset(0, -20); // 下移20像素 contentStream.showText("订单号: PDF20251005001"); // 更多行省略... 参考控制台方案的格式 contentStream.endText(); } document.save("receipt.pdf"); // 保存文件 System.out.println("PDF小票已生成"); } } }
最佳实践与避坑指南
-
格式对齐
- 使用
String.format()
控制列宽,
String.format("%-20s %10.2f", productName, price)
(左对齐商品名,右对齐价格)
- 使用
-
字符编码
- 物理打印时,若遇中文乱码:
- 方案1:打印机切换至GBK/GB2312模式
- 方案2:将文本转换为字节流时指定编码:
receiptContent.getBytes("GB18030")
- 物理打印时,若遇中文乱码:
-
打印超时处理
job.print(doc, attributes); // 默认可能阻塞 // 建议改用异步线程,避免主线程卡死
-
商业系统推荐
- 复杂场景(如POS机)使用 ESC/POS指令集 直接控制打印机(需厂商文档)
- 开源库推荐:
JavaPOS
、QZ Tray
(支持网页调用打印机)
引用说明:
- Java Print Service API:Oracle官方文档
- PDFBox库:Apache PDFBox官网
- 热敏打印机指令参考:ESC/POS命令手册
代码示例遵循MIT许可,可自由用于商业项目。
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/28744.html