Java 课后作业

一、形式化方法(Formal Methods)

形式化方法是用数学语言来描述、规范和验证系统(尤其是软件和硬件系统)的一套技术体系。它试图用 数学 而不是 经验 去证明一个系统是否正确。

普通开发:
“程序看起来没问题,测试也过了,应该能用。”

形式化方法:
“我能数学证明它一定不会出错(在给定模型下)。”

它广泛用于:

  • 操作系统内核
  • 编译器
  • 芯片设计
  • 航空航天
  • 高铁信号系统
  • 银行/密码学协议
  • 区块链智能合约

因为这些领域:
“出一次错可能就死人、炸火箭、损失几亿”。(试错成本很高)


(一)为什么会有形式化方法?

传统软件开发主要靠:

  • 测试(test)
  • 调试(debug)
  • Code Review
  • 经验

但测试有一个根本问题:

测试只能说明目前没发现 有 bug
不能说明:绝对没有 bug
测试集是人想出来的,测试永远不可能覆盖所有情况。

于是:

Tip

科学家们想到:
“能不能像数学定理一样证明程序正确?”

所以:

Important

程序 = 数学对象

开始出现,这即为形式化方法的 核心思想


(二)形式化方法的核心

  1. 形式化建模
    先用数学语言描述系统。不是自然语言。因为自然语言有歧义。

  2. 形式化验证
    证明:系统满足规格说明

  3. 自动推理
    使用工具自动验证:

    • 定理证明器
    • 模型检查器
    • SAT Solver
    • SMT Solver

大象-Thinking in UML



二、Prime number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.Scanner;

public class IsPrime {
public static boolean isPrime(int n) {
// 2是唯一偶质数
if (n == 2)
return true;
// 排除小于2和偶数
if (n < 2 || n % 2 == 0)
return false;

// 步长设2,只看奇数,只需检查到√n
for (int j = 3; j * j <= n; j += 2) {
if (n % j == 0) // 能被循环因子整除的是非质数
return false;
}
return true;
}

public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = sc.nextInt();
System.out.println(num + " 的结果是:" + isPrime(num));
sc.close();
}
}

核心:for (int j = 3; j * j <= n; j += 2)

Important

如果 n 有大于√n 的因数,那它一定有一个小于√n 的因数。

例如:n = 36,则 √36 = 6,因数配对:1×36, 2×18, 3×12, 4×9, 6×6。

大于 6 的因数:36、18、12、9
小于 6 的因数:1、2、3、4、6

所以说只要存在大因数,就一定配一个小因数。

只遍历小于等于√n的数试除,只要找不到能整除的数就说明没有任何一对因数,直接判定是质数不用再去查更大的数,省一半以上循环次数。



三、枚举类型应用场景

(一)状态 / 类型定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义订单状态枚举
enum OrderStatus {
WAIT_PAY, // 待支付
PAID, // 已支付
DELIVERED, // 已发货
COMPLETED // 已完成
}

public class Main {
public static void main(String[] args) {
OrderStatus status = OrderStatus.PAID;

if (status == OrderStatus.PAID) {
System.out.println("订单已支付,准备发货!");
} else if (status == OrderStatus.WAIT_PAY) {
System.out.println("订单待支付,请尽快付款。");
}
}
}

优势:避免魔法数字,类型安全,自带含义,维护方便。


(二)策略模式(替换大量 if/else)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 用枚举封装不同的折扣策略
enum Discount {
NORMAL {
@Override
public double apply(double price) {
return price; // 原价
}
},
VIP {
@Override
public double apply(double price) {
return price * 0.9; // 9折
}
},
SVIP {
@Override
public double apply(double price) {
return price * 0.7; // 7折
}
};

public abstract double apply(double price);
}

public class Main {
public static void main(String[] args) {
double price = 100;

// 直接调用,不用写一堆 if/else
System.out.println("普通用户价格:" + Discount.NORMAL.apply(price));
System.out.println("VIP价格:" + Discount.VIP.apply(price));
System.out.println("SVIP价格:" + Discount.SVIP.apply(price));
}
}

优势:使用时直接调用,无需任何条件判断。新增折扣类型也仅只需加一个枚举常量,不用修改旧代码。


(三)统一返回码(后端接口必备)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 接口返回码枚举
enum ResultCode {
SUCCESS(200, "请求成功"),
PARAM_ERROR(400, "参数错误"),
SERVER_ERROR(500, "服务器异常");

public final int code;
public final String msg;

ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}

public class Main {
public static void main(String[] args) {
// 模拟接口返回
ResultCode code = ResultCode.SUCCESS;

System.out.println("状态码:" + code.code);
System.out.println("提示信息:" + code.msg);
}
}

优势:所有接口的状态码、提示语都集中维护,再也不会出现同一个错误返回不同提示的混乱情况。



四、了解Java的反射机制

反射:运行时动态获取类的结构信息并操作它(构造器、方法、字段)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 获取 Class 对象
Class<?> clz = Class.forName("com.example.Person"); // 最灵活,类名可写配置文件

// 2. 创建对象
Object obj = clz.getConstructor().newInstance();

// 3. 调用方法
Method m = clz.getMethod("setName", String.class);
m.invoke(obj, "张三"); // 等价于 obj.setName("张三")

// 4. 读写字段(私有也能搞)
Field f = clz.getDeclaredField("age");
f.setAccessible(true); // 暴力破解 private
f.set(obj, 20);

Important

invoke(对象, 实参) 是反射的灵魂。框架(Spring)全是靠它实现"你写类名我帮你创建"——控制反转 IoC。

反射 = 灵活但慢。写业务代码直接 new,写框架才上反射。



五、文本文件复制 — 字符缓冲流(最常用)

字符流操作文本,readLine() 一次读一行,自动处理编码。

1
2
3
4
5
6
7
8
9
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
br.close();
bw.close();

Important

自带缓冲区,readLine() 方便,newLine() 跨平台换行。处理 .txt.java 等文本文件首选。



六、任意文件复制 — 字节缓冲流(万能复制)

字节流不关心内容,一个字节一个字节地搬。什么文件都能复制。

1
2
3
4
5
6
7
8
9
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.jpg"));
byte[] buf = new byte[1024];
int len;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
bis.close();
bos.close();

Important

不解析编码,原样搬字节。文本、图片、视频、压缩包通吃——万能复制

不要用字符流复制图片/视频!编码转换会搞坏二进制数据,复制出来打不开。



七、使用 Thread 子类创建线程和使用 Thread 直接创建线程(Runnable接口)的区别

(一)继承 Thread 类(创建子类)

1
2
3
4
5
6
7
class MyThread extends Thread {
public void run() {
// 线程执行的代码
}
}
// 使用
new MyThread().start();

(二)实现 Runnable 接口(配合 Thread 直接创建)

1
2
3
4
5
6
7
8
class MyRunnable implements Runnable {
public void run() {
// 线程执行的代码
}
}
// 使用
Thread t = new Thread(new MyRunnable());
t.start();

(三)继承 Thread vs 实现 Runnable 核心区别对比

对比维度 继承 Thread 类 实现 Runnable 接口
灵活性 (单继承限制,无法再继承其他类) (不影响继承其他类)
数据共享 (线程对象独立,数据不互通) (多个线程可共享同一个 Runnable 对象)
解耦程度 (任务与线程绑定在一起) (任务逻辑与线程对象分离)
编码简易度 代码简洁,直接 start() 代码稍多,需实例化接口并作为参数传入


八、终端扫雷游戏

以下为可交互运行的终端风格扫雷游戏,直接在页面中操作:

TERMINAL MINESWEEPER v1.0
MINES 10 TIME 0s STEPS 0

操作说明

操作 方式
翻开格子 左键点击
标记 / 取消旗子 右键点击
切换难度 点击初级 / 中级 / 高级按钮
存档 点击 SAVE(保存到浏览器 localStorage)
读档 点击 LOAD(恢复上次存档)
重新开始 点击 RESTART

Tip

数字表示周围 8 格中的地雷数量。用旗子(F)标记推测的雷位,翻开所有非雷格子获胜!

核心 Java 实现思路(点击展开)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 扫雷核心:数据结构 + flood-fill 展开
public class Minesweeper {
private int[][] board; // -1 = 雷, 0~8 = 周围雷数
private boolean[][] revealed;
private boolean[][] flagged;

// 随机布雷(确保不重复)
private void placeMines(int rows, int cols, int mines) {
Random rand = new Random();
int placed = 0;
while (placed < mines) {
int r = rand.nextInt(rows);
int c = rand.nextInt(cols);
if (board[r][c] != -1) {
board[r][c] = -1;
placed++;
}
}
}

// 递归展开空白区域(flood-fill / DFS)
private void reveal(int r, int c) {
if (r < 0 || r >= rows || c < 0 || c >= cols) return;
if (revealed[r][c] || flagged[r][c]) return;
revealed[r][c] = true;
if (board[r][c] == 0) { // 空格:递归展开相邻格
for (int dr = -1; dr <= 1; dr++)
for (int dc = -1; dc <= 1; dc++)
reveal(r + dr, c + dc);
}
}
}


九、Swing 算术练习系统

以下为可交互运行的算术练习应用,直接在页面中操作:

算术小练习 Arithmetic Practice
对 0 错 0 总 0
?

操作说明

操作 方式
输入答案 键盘输入后点击「确定」或按回车
切换运算 点击 + / - / x / / 按钮
切换难度 简单(1-10)/ 中等(1-50)/ 困难(1-100)/ 小数
下一题 点击「下一题」或自动在判断后出题
重置成绩 点击「重置成绩」

Tip

除法结果保留两位小数。减法保证被减数大于减数(结果非负),除法自动调整使结果整洁。

核心 Java 实现思路(点击展开)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 算术练习核心逻辑
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;

public class ArithmeticApp extends JFrame {
private int a, b, correctAnswer;
private int score = 0, total = 0;
private char operator = '+';
private Random rand = new Random();

private JLabel problemLabel, scoreLabel;
private JTextField answerField;
private JButton checkBtn, nextBtn;
private JComboBox<String> opSelector, diffSelector;

private void generateProblem() {
int max = getMaxByDifficulty();
a = rand.nextInt(max) + 1;
b = rand.nextInt(max) + 1;
if (operator == '-' && a < b) { int t = a; a = b; b = t; }

switch (operator) {
case '+': correctAnswer = a + b; break;
case '-': correctAnswer = a - b; break;
case '*': correctAnswer = a * b; break;
case '/':
if (b == 0) b = 1;
a = a * b; // 保证整除
correctAnswer = a / b;
break;
}
problemLabel.setText(a + " " + operator + " " + b + " = ?");
}

private void checkAnswer() {
int userAnswer = Integer.parseInt(answerField.getText());
total++;
if (userAnswer == correctAnswer) {
score++;
JOptionPane.showMessageDialog(this, "正确!");
} else {
JOptionPane.showMessageDialog(this, "错误! 答案是 " + correctAnswer);
}
scoreLabel.setText("得分: " + score + " / " + total);
answerField.setText("");
generateProblem();
}
}