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.状态 / 类型定义

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("订单待支付,请尽快付款。");
}
}
}

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


2. 策略模式(替换大量 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));
}
}

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


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

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

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

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