形式化方法是用数学语言来描述、规范和验证系统(尤其是软件和硬件系统)的一套技术体系。它试图用 数学 而不是 经验 去证明一个系统是否正确。
普通开发:
“程序看起来没问题,测试也过了,应该能用。”
形式化方法:
“我能数学证明它一定不会出错(在给定模型下)。”
它广泛用于:
操作系统内核
编译器
芯片设计
航空航天
高铁信号系统
银行/密码学协议
区块链智能合约
因为这些领域:
“出一次错可能就死人、炸火箭、损失几亿”。(试错成本很高)
(一)为什么会有形式化方法?
传统软件开发主要靠:
测试(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 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 ; } }, 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)); } }
优势:使用时直接调用,无需任何条件判断。新增折扣类型也仅只需加一个枚举常量,不用修改旧代码。
(三)统一返回码(后端接口必备)
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
不解析编码,原样搬字节。文本、图片、视频、压缩包通吃——万能复制 。
不要用字符流复制图片/视频!编码转换会搞坏二进制数据,复制出来打不开。
七、使用 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()
代码稍多,需实例化接口并作为参数传入
八、终端扫雷游戏
以下为可交互运行的终端风格扫雷游戏 ,直接在页面中操作:
MINES 10
TIME 0s
STEPS 0
初级 9x9
中级 16x16
高级 30x16
SAVE
LOAD
RESTART
操作说明
操作
方式
翻开格子
左键点击
标记 / 取消旗子
右键点击
切换难度
点击初级 / 中级 / 高级按钮
存档
点击 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 public class Minesweeper { private int [][] board; 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++; } } } 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)
小数 (0.1-9.9)
?
确定
操作说明
操作
方式
输入答案
键盘输入后点击「确定」或按回车
切换运算
点击 + / - / 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(); } }