形式化方法是用数学语言来描述、规范和验证系统(尤其是软件和硬件系统)的一套技术体系。它试图用 数学 而不是 经验 去证明一个系统是否正确。
普通开发:
“程序看起来没问题,测试也过了,应该能用。”
形式化方法:
“我能数学证明它一定不会出错(在给定模型下)。”
它广泛用于:
- 操作系统内核
- 编译器
- 芯片设计
- 航空航天
- 高铁信号系统
- 银行/密码学协议
- 区块链智能合约
因为这些领域:
“出一次错可能就死人、炸火箭、损失几亿”。(试错成本很高)
为什么会有形式化方法?
传统软件开发主要靠:
- 测试(test)
- 调试(debug)
- Code Review
- 靠经验
但测试有一个根本问题:
测试只能说明目前没发现 有 bug;
不能说明:绝对没有 bug。
测试集是人想出来的,测试永远不可能覆盖所有情况。
于是:
Tip
科学家们想到:
“能不能像数学定理一样证明程序正确?”
所以:
开始出现,这即为形式化方法的 核心思想。
形式化方法的核心
-
形式化建模
先用数学语言描述系统。不是自然语言。因为自然语言有歧义。
-
形式化验证
证明:系统满足规格说明
-
自动推理
使用工具自动验证:
- 定理证明器
- 模型检查器
- 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) { if (n == 2) return true; if (n < 2 || n % 2 == 0) return false;
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; } }, SVIP { @Override public double apply(double price) { return price * 0.7; } };
public abstract double apply(double price); }
public class Main { public static void main(String[] args) { double price = 100;
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
| Class<?> clz = Class.forName("com.example.Person");
Object obj = clz.getConstructor().newInstance();
Method m = clz.getMethod("setName", String.class); m.invoke(obj, "张三");
Field f = clz.getDeclaredField("age"); f.setAccessible(true); 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
不解析编码,原样搬字节。文本、图片、视频、压缩包通吃——万能复制。
不用字符流复制图片/视频!编码转换会搞坏二进制数据,复制出来打不开。