摘要: 本文通过一个完整的命令行计算器项目,将Java第二阶段基础知识点(变量、运算符、条件判断、循环、方法、数组、字符串、JDK 21新特性)融会贯通。项目支持六种基本运算、输入校验、历史记录功能,采用模块化设计,使用Record封装结果,展示了如何将零散知识整合为实用程序。适合Java初学者通过实战巩固基础语法,为面向对象编程打下坚实基础。
关键词: Java实战项目, 命令行计算器, 基础语法综合, JDK 21新特性, 模块化设计
恭喜你,我们已经完成了第二阶段"基础篇"的全部知识点。回头看这十篇文章,我们走过了变量与数据类型、常量与字面量、运算符、条件判断、循环、方法、数组、字符串,还有 JDK 21 的那些让代码更简洁的"语法糖"。
知识点学了不少,但如果只是单个地学,它们就像一串散落的珠子。今天,我们要用一根线把这些珠子全部串起来——这根线就是一个完整的项目。
我们要写一个命令行版的交互式计算器。它不会像 Windows 计算器那样有图形界面,但它能做的事情一点也不少:支持多种运算、能持续使用、能记录历史、还能处理错误输入。
1. 我们要做什么?—— 需求分析
让我们先明确这个计算器要具备哪些功能:
核心功能:
- 支持加、减、乘、除、取模、幂运算(六种运算)。
- 用户通过菜单选择运算类型,然后输入两个数字,程序输出结果。
- 除法时,如果除数为 0,给出友好的错误提示,而不是程序崩溃。
- 一次运算完成后,询问用户是否继续,选择"是"则回到菜单,选择"否"则退出。
进阶功能(体现我们的知识积累):
5. 历史记录:记录每次运算的表达式和结果(比如 3 + 5 = 8),用户可以选择查看历史记录。
6. 输入校验:如果用户输入的不是有效数字,提示重新输入,而不是让程序崩溃。
交互流程(用户视角):
===== 命令行计算器 =====
1. 加法
2. 减法
3. 乘法
4. 除法
5. 取模
6. 幂运算
7. 查看历史记录
0. 退出
请选择操作 (0-7):1
请输入第一个数:10
请输入第二个数:20
10.0 + 20.0 = 30.0
是否继续?(y/n):y
...
这个需求正好涵盖了第二阶段的所有知识点:
- 用
Scanner处理输入 - 用
do-while实现"至少执行一次"的主循环 - 用
switch表达式做菜单分支 - 用
while或do-while做输入校验 - 用方法封装各个功能模块
- 用数组存储历史记录
- 用字符串格式化美化输出
- 用 JDK 21 的
var、文本块、Record 让代码更简洁
准备好了吗?我们开始动手。
2. 设计思路 —— 先想清楚再动笔
在写代码之前,我们先设计一下程序的结构。
① 数据存储
- 历史记录:我们存储最多 100 条,用
String[]数组(这是数组的最直接用法)。 - 记录条数:用一个
int变量historyCount来追踪已存储的条数。
② 方法划分
| 方法名 | 职责 |
|---|---|
main | 程序入口,控制主循环和菜单 |
showMenu | 显示菜单选项 |
getOperation | 从用户获取有效的运算类型(0-7) |
getNumber | 从用户获取一个有效的数字(含校验) |
calculate | 根据运算类型执行计算,返回结果字符串 |
addToHistory | 将一条运算记录添加到历史数组中 |
showHistory | 打印所有历史记录 |
printResult | 格式化并打印运算结果 |
这种划分遵循了单一职责原则:每个方法只做一件事。
③ 程序流程
1. 显示欢迎信息
2. 初始化历史记录数组
3. 进入主循环:
a. 显示菜单
b. 获取用户选择(0-7)
c. 如果选择 0,退出
d. 如果选择 7,显示历史记录,回到 a
e. 其他:获取两个数字,执行运算,打印结果,存入历史
f. 询问是否继续,如果"n"则退出,否则回到 a
3. 编写代码 —— 一步一步来
现在开始写代码。我会把完整的代码分成几个部分,逐段讲解。
第1部分:类的骨架和常量定义
package com.example.demo;
import java.util.Scanner;
/**
* 命令行计算器 —— 第二阶段综合实战项目
* 功能:加、减、乘、除、取模、幂运算,历史记录,输入校验
*/
public class Calculator {
// 历史记录数组:最多存储 100 条
private static final int MAX_HISTORY = 100;
private static final String[] history = new String[MAX_HISTORY];
private static int historyCount = 0; // 当前已存储的记录条数
// 创建一个静态的 Scanner,供所有方法共用
private static final Scanner scanner = new Scanner(System.in);
MAX_HISTORY是常量,用static final修饰,全大写。history数组和historyCount是静态成员,因为main和所有方法都是静态的,它们之间共享数据需要静态变量。scanner也是静态的,这样所有方法都可以使用同一个Scanner,不需要反复创建。
第2部分:主方法 —— 控制整体流程
public static void main(String[] args) {
System.out.println("""
╔═══════════════════════════════════╗
║ 欢迎使用命令行计算器 v1.0 ║
║ 支持:+ - * / % ^ ║
╚═══════════════════════════════════╝
""");
boolean running = true;
while (running) {
showMenu(); // 显示菜单
int choice = getChoice(); // 获取用户选择
if (choice == 0) {
System.out.println("感谢使用计算器,再见!");
running = false;
break;
}
if (choice == 7) {
showHistory(); // 查看历史
continue; // 回到循环开头
}
// 处理运算(choice 为 1-6)
double num1 = getNumber("请输入第一个数:");
double num2 = getNumber("请输入第二个数:");
// 执行计算(用 Record 来封装结果,优雅又简洁)
var result = calculate(choice, num1, num2);
printResult(result);
// 询问是否继续
System.out.print("是否继续?(y/n):");
String answer = scanner.next().trim().toLowerCase();
if (!answer.equals("y") && !answer.equals("yes")) {
System.out.println("感谢使用计算器,再见!");
running = false;
}
}
scanner.close();
}
这里用到了:
- 文本块:用
"""画了一个简单的欢迎边框。 var:var result让calculate的返回类型推断更简洁。while循环:控制程序持续运行。continue:查看历史后直接回到菜单,不执行后续的运算逻辑。
第3部分:显示菜单
/**
* 显示菜单选项
*/
private static void showMenu() {
System.out.println("\n========== 计算器菜单 ==========");
System.out.println("1. 加法 (+)");
System.out.println("2. 减法 (-)");
System.out.println("3. 乘法 (*)");
System.out.println("4. 除法 (/)");
System.out.println("5. 取模 (%)");
System.out.println("6. 幂运算 (^)");
System.out.println("7. 查看历史记录");
System.out.println("0. 退出");
System.out.print("请选择操作 (0-7):");
}
很直观,用 println 输出菜单项。
第4部分:获取用户选择(含校验)
/**
* 获取用户选择的菜单项,并校验输入是否合法
* @return 0-7 之间的整数
*/
private static int getChoice() {
while (true) {
if (scanner.hasNextInt()) {
int choice = scanner.nextInt();
if (choice >= 0 && choice <= 7) {
return choice;
}
} else {
scanner.next(); // 消费掉非整数输入
}
System.out.print("输入无效,请输入 0-7 之间的数字:");
}
}
这里用到了 scanner.hasNextInt() 来判断用户输入的是不是整数。如果不是,就调用 scanner.next() 消费掉这个无效输入,然后继续提示。这比直接 nextInt() 更健壮,不会因为输入字母而崩溃。
第5部分:获取数字(含校验)
/**
* 获取用户输入的一个有效数字
* @param prompt 提示信息
* @return 用户输入的数字(double)
*/
private static double getNumber(String prompt) {
while (true) {
System.out.print(prompt);
if (scanner.hasNextDouble()) {
return scanner.nextDouble();
} else {
scanner.next(); // 消费掉非数字输入
System.out.println("输入无效,请输入一个有效的数字!");
}
}
}
同样用 hasNextDouble() 做了校验,确保用户输入的是有效的数字。
第6部分:执行计算(核心逻辑)
我们用 Record 来封装计算结果,这样返回的数据既清晰又简洁。先定义这个 Record:
/**
* 用于封装计算结果的 Record
* @param expression 运算表达式(如 "3 + 5")
* @param result 运算结果
* @param error 错误信息(null 表示没有错误)
*/
public record CalculationResult(String expression, double result, String error) {
// 判断计算是否成功
public boolean isSuccess() {
return error == null;
}
}
然后写 calculate 方法:
/**
* 根据运算类型执行计算
* @param choice 运算类型(1-6)
* @param a 第一个数
* @param b 第二个数
* @return 包含表达式、结果和错误信息的 CalculationResult
*/
private static CalculationResult calculate(int choice, double a, double b) {
String operator;
double result;
String error = null;
switch (choice) {
case 1 -> {
operator = "+";
result = a + b;
}
case 2 -> {
operator = "-";
result = a - b;
}
case 3 -> {
operator = "*";
result = a * b;
}
case 4 -> {
operator = "/";
if (Math.abs(b) < 1e-9) {
return new CalculationResult("", 0, "错误:除数不能为 0!");
}
result = a / b;
}
case 5 -> {
operator = "%";
if (Math.abs(b) < 1e-9) {
return new CalculationResult("", 0, "错误:取模运算的除数不能为 0!");
}
result = a % b;
}
case 6 -> {
operator = "^";
result = Math.pow(a, b);
}
default -> {
return new CalculationResult("", 0, "未知的运算类型!");
}
}
// 构建表达式字符串(处理整数显示更美观)
String expr = formatNumber(a) + " " + operator + " " + formatNumber(b);
return new CalculationResult(expr, result, null);
}
- 用
switch表达式配合->,简洁清晰。 - 对除法和取模做了除数校验,如果接近 0(考虑到浮点数精度),返回错误。
- 使用
Math.pow(a, b)进行幂运算。 - 返回一个
CalculationResultRecord,同时包含表达式和结果。
formatNumber 是一个辅助方法,让数字显示更美观(如果是整数就不显示 .0):
/**
* 格式化数字:如果是整数则不显示小数部分
*/
private static String formatNumber(double num) {
if (num == (long) num) {
return String.valueOf((long) num);
}
return String.valueOf(num);
}
第7部分:打印结果与历史记录
/**
* 打印计算结果,并存入历史
*/
private static void printResult(CalculationResult result) {
if (!result.isSuccess()) {
System.out.println("❌ " + result.error());
return;
}
String output = result.expression() + " = " + formatNumber(result.result());
System.out.println("✅ " + output);
addToHistory(output);
}
/**
* 将一条记录添加到历史数组中
*/
private static void addToHistory(String record) {
if (historyCount < MAX_HISTORY) {
history[historyCount] = record;
historyCount++;
} else {
// 如果历史已满,把所有记录向前移动一位,覆盖最旧的
System.arraycopy(history, 1, history, 0, MAX_HISTORY - 1);
history[MAX_HISTORY - 1] = record;
// historyCount 保持不变(仍为 MAX_HISTORY)
}
}
/**
* 显示所有历史记录
*/
private static void showHistory() {
if (historyCount == 0) {
System.out.println("📭 暂无历史记录");
return;
}
System.out.println("\n========== 历史记录 ==========");
for (int i = 0; i < historyCount; i++) {
System.out.println((i + 1) + ". " + history[i]);
}
System.out.println("==============================");
}
addToHistory中,如果历史已满,我们用System.arraycopy将数组整体左移一位,丢弃最旧的记录,在最末尾插入新记录。这是一个经典的使用数组实现"循环队列"的简化做法。showHistory遍历historyCount条记录并打印。
4. 完整的代码
把上面的所有部分合在一起,就是最终的完整代码:
package com.example.demo;
import java.util.Scanner;
/**
* 命令行计算器 —— 第二阶段综合实战项目
* 功能:加、减、乘、除、取模、幂运算,历史记录,输入校验
* 知识点:变量、运算符、条件判断、循环、方法、数组、字符串、JDK 21 语法糖
*/
public class Calculator {
// 历史记录数组:最多存储 100 条
private static final int MAX_HISTORY = 100;
private static final String[] history = new String[MAX_HISTORY];
private static int historyCount = 0;
private static final Scanner scanner = new Scanner(System.in);
// ---- Record:封装计算结果 ----
public record CalculationResult(String expression, double result, String error) {
public boolean isSuccess() {
return error == null;
}
}
// ---- 主方法 ----
public static void main(String[] args) {
System.out.println("""
╔═══════════════════════════════════╗
║ 欢迎使用命令行计算器 v1.0 ║
║ 支持:+ - * / % ^ ║
╚═══════════════════════════════════╝
""");
boolean running = true;
while (running) {
showMenu();
int choice = getChoice();
if (choice == 0) {
System.out.println("感谢使用计算器,再见!");
running = false;
break;
}
if (choice == 7) {
showHistory();
continue;
}
double num1 = getNumber("请输入第一个数:");
double num2 = getNumber("请输入第二个数:");
var result = calculate(choice, num1, num2);
printResult(result);
System.out.print("是否继续?(y/n):");
String answer = scanner.next().trim().toLowerCase();
if (!answer.equals("y") && !answer.equals("yes")) {
System.out.println("感谢使用计算器,再见!");
running = false;
}
}
scanner.close();
}
// ---- 菜单 ----
private static void showMenu() {
System.out.println("\n========== 计算器菜单 ==========");
System.out.println("1. 加法 (+)");
System.out.println("2. 减法 (-)");
System.out.println("3. 乘法 (*)");
System.out.println("4. 除法 (/)");
System.out.println("5. 取模 (%)");
System.out.println("6. 幂运算 (^)");
System.out.println("7. 查看历史记录");
System.out.println("0. 退出");
System.out.print("请选择操作 (0-7):");
}
// ---- 获取菜单选择 ----
private static int getChoice() {
while (true) {
if (scanner.hasNextInt()) {
int choice = scanner.nextInt();
if (choice >= 0 && choice <= 7) {
return choice;
}
} else {
scanner.next();
}
System.out.print("输入无效,请输入 0-7 之间的数字:");
}
}
// ---- 获取数字 ----
private static double getNumber(String prompt) {
while (true) {
System.out.print(prompt);
if (scanner.hasNextDouble()) {
return scanner.nextDouble();
} else {
scanner.next();
System.out.println("输入无效,请输入一个有效的数字!");
}
}
}
// ---- 执行计算 ----
private static CalculationResult calculate(int choice, double a, double b) {
String operator;
double result;
String error = null;
switch (choice) {
case 1 -> {
operator = "+";
result = a + b;
}
case 2 -> {
operator = "-";
result = a - b;
}
case 3 -> {
operator = "*";
result = a * b;
}
case 4 -> {
operator = "/";
if (Math.abs(b) < 1e-9) {
return new CalculationResult("", 0, "错误:除数不能为 0!");
}
result = a / b;
}
case 5 -> {
operator = "%";
if (Math.abs(b) < 1e-9) {
return new CalculationResult("", 0, "错误:取模运算的除数不能为 0!");
}
result = a % b;
}
case 6 -> {
operator = "^";
result = Math.pow(a, b);
}
default -> {
return new CalculationResult("", 0, "未知的运算类型!");
}
}
String expr = formatNumber(a) + " " + operator + " " + formatNumber(b);
return new CalculationResult(expr, result, null);
}
// ---- 格式化数字 ----
private static String formatNumber(double num) {
if (num == (long) num) {
return String.valueOf((long) num);
}
return String.valueOf(num);
}
// ---- 打印结果 ----
private static void printResult(CalculationResult result) {
if (!result.isSuccess()) {
System.out.println("❌ " + result.error());
return;
}
String output = result.expression() + " = " + formatNumber(result.result());
System.out.println("✅ " + output);
addToHistory(output);
}
// ---- 添加历史记录 ----
private static void addToHistory(String record) {
if (historyCount < MAX_HISTORY) {
history[historyCount] = record;
historyCount++;
} else {
System.arraycopy(history, 1, history, 0, MAX_HISTORY - 1);
history[MAX_HISTORY - 1] = record;
}
}
// ---- 显示历史记录 ----
private static void showHistory() {
if (historyCount == 0) {
System.out.println("📭 暂无历史记录");
return;
}
System.out.println("\n========== 历史记录 ==========");
for (int i = 0; i < historyCount; i++) {
System.out.println((i + 1) + ". " + history[i]);
}
System.out.println("==============================");
}
}
5. 运行测试
运行这个程序,你会看到欢迎界面和菜单。试试这些操作:
- 选择 1(加法),输入
10和20,看到输出10 + 20 = 30。 - 选择 4(除法),输入
10和0,看到友好的错误提示,而不是程序崩溃。 - 选择 6(幂运算),输入
2和10,看到2 ^ 10 = 1024。 - 做几个运算后,选择 7(查看历史记录),看到所有记录按顺序列出。
- 输入 无效字符(比如菜单输入
abc),看到提示重新输入。 - 选择 0(退出),程序优雅结束。
6. 知识点回顾:我们用了什么?
这个不到 150 行的程序,用到了第二阶段的所有核心知识:
| 知识点 | 体现在 |
|---|---|
| 变量 | historyCount、choice、num1、num2 等 |
| 常量 | MAX_HISTORY |
| 数据类型 | int、double、String、boolean |
| 数组 | String[] history |
| 运算符 | 算术、比较、逻辑 |
| 条件判断 | if-else、switch 表达式 |
| 循环 | while、do-while(隐藏在输入校验中) |
| 方法 | 模块化拆分,各司其职 |
| 方法重载 | 无(但体现了方法设计) |
Scanner | 输入处理 + 校验 |
| 字符串 | 格式化、拼接、formatted() 等 |
| 文本块 | 欢迎界面 |
Record | 封装计算结果 |
var | var result = calculate(...) |
| 异常处理思路 | 用返回值而不是抛异常来传递错误 |
7. 如何进一步扩展?(给你的挑战)
你可以在这个基础上,继续完善计算器:
- 支持更多运算:比如平方根、三角函数、对数等(用
Math类)。 - 支持括号表达式:像真正的计算器一样,输入
(3+5)*2自动计算(这需要用栈和逆波兰表达式,我们以后学)。 - 持久化历史:把历史记录保存到文件中,下次启动时加载(我们学 I/O 后会做)。
- 用集合替代数组:
ArrayList比数组更灵活,我们第四阶段会学。 - 增加颜色输出:用 ANSI 转义码让输出更炫酷。
今天的总结
今天我们用一整个项目,把第二阶段学到的所有知识融会贯通了。你写出了自己第一个"真正有用"的 Java 程序——它不是一个一次性的 Hello, World!,而是一个可以反复使用、有交互、能处理错误、能记录历史的工具。
你做到了。这个程序的复杂程度已经远远超出了"入门"的范畴。
从下一篇开始,我们将进入一个全新的阶段——第三阶段:面向对象编程。这是 Java 最核心、最精髓的部分。如果说基础语法是"招式",那面向对象就是"心法"。掌握了它,你才能写出真正优雅、可扩展的 Java 程序。
动手试试
- 把计算器跑起来,做至少 10 次不同的运算,然后查看历史记录,确保它正确记录了所有操作。
- 尝试输入一些"坏"数据:比如在菜单输入
abc,在数字输入xyz,在继续询问时输入n,观察程序是否都正确处理了。 - 扩展功能:增加一个"科学计算器"模式,支持
sin、cos、sqrt等单参数运算(菜单增加选项)。 - 代码阅读:把
System.arraycopy(history, 1, history, 0, MAX_HISTORY - 1);这一行查一下文档,理解它是怎么实现"移除最旧记录"的。
我们下个阶段见。😃
📌 获取本系列示例代码请访问 GitCode。

3354

被折叠的 条评论
为什么被折叠?



