assert 关键字
概述
assert
关键字用于在程序中插入断言,进行调试和测试。断言是对程序状态的检查,如果条件为false,程序会抛出AssertionError。断言主要用于开发和测试阶段,可以通过JVM参数控制是否启用。
语法格式
// 简单断言
assert condition;
// 带错误信息的断言
assert condition : errorMessage;
启用断言
# 启用所有断言
java -ea MyClass
java -enableassertions MyClass
# 启用特定包的断言
java -ea:com.example.* MyClass
# 启用特定类的断言
java -ea:com.example.MyClass MyClass
# 禁用断言(默认)
java -da MyClass
java -disableassertions MyClass
基本用法
简单断言示例
public class BasicAssertionExample {
public int divide(int dividend, int divisor) {
// 前置条件断言
assert divisor != 0 : "除数不能为零";
int result = dividend / divisor;
// 后置条件断言
assert result * divisor == dividend : "计算结果验证失败";
return result;
}
public double sqrt(double x) {
// 前置条件
assert x >= 0 : "平方根的参数必须非负: " + x;
double result = Math.sqrt(x);
// 后置条件
assert result >= 0 : "平方根结果必须非负";
assert Math.abs(result * result - x) < 1e-10 : "平方根计算精度验证失败";
return result;
}
public static void main(String[] args) {
BasicAssertionExample example = new BasicAssertionExample();
System.out.println("=== 正常情况测试 ===");
System.out.println("10 ÷ 2 = " + example.divide(10, 2));
System.out.println("√9 = " + example.sqrt(9.0));
System.out.println("\n=== 异常情况测试(需要启用断言)===");
try {
example.divide(10, 0); // 会触发断言
} catch (AssertionError e) {
System.out.println("捕获断言错误: " + e.getMessage());
}
try {
example.sqrt(-1); // 会触发断言
} catch (AssertionError e) {
System.out.println("捕获断言错误: " + e.getMessage());
}
}
}
数组和集合操作
import java.util.*;
public class CollectionAssertions {
// 数组操作断言
public int findMax(int[] array) {
assert array != null : "数组不能为null";
assert array.length > 0 : "数组不能为空";
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
// 验证结果
assert containsValue(array, max) : "最大值必须在数组中存在";
for (int value : array) {
assert value <= max : "所有元素都应该小于等于最大值";
}
return max;
}
private boolean containsValue(int[] array, int value) {
for (int item : array) {
if (item == value) return true;
}
return false;
}
// 列表操作断言
public <T> T getElementAt(List<T> list, int index) {
assert list != null : "列表不能为null";
assert index >= 0 : "索引必须非负: " + index;
assert index < list.size() : "索引超出范围: " + index + ", 列表大小: " + list.size();
T element = list.get(index);
// 验证获取的元素确实存在于列表中
assert list.contains(element) : "获取的元素不在列表中";
return element;
}
// 映射操作断言
public <K, V> V putIfAbsent(Map<K, V> map, K key, V value) {
assert map != null : "映射不能为null";
assert key != null : "键不能为null";
assert value != null : "值不能为null";
boolean keyExisted = map.containsKey(key);
V previousValue = map.get(key);
V result = map.putIfAbsent(key, value);
// 验证操作结果
if (keyExisted) {
assert result.equals(previousValue) : "返回值应该是之前的值";
assert map.get(key).equals(previousValue) : "原有值应该保持不变";
} else {
assert result == null : "之前不存在键时返回值应该为null";
assert map.get(key).equals(value) : "新值应该被正确设置";
}
return result;
}
public static void main(String[] args) {
CollectionAssertions assertions = new CollectionAssertions();
// 数组测试
System.out.println("=== 数组操作测试 ===");
int[] numbers = {3, 7, 2, 9, 1, 5};
System.out.println("最大值: " + assertions.findMax(numbers));
// 列表测试
System.out.println("\n=== 列表操作测试 ===");
List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子");
System.out.println("索引1的元素: " + assertions.getElementAt(fruits, 1));
// 映射测试
System.out.println("\n=== 映射操作测试 ===");
Map<String, Integer> scores = new HashMap<>();
System.out.println("添加新键结果: " + assertions.putIfAbsent(scores, "Alice", 95));
System.out.println("重复添加结果: " + assertions.putIfAbsent(scores, "Alice", 90));
System.out.println("最终映射: " + scores);
// 异常情况测试
System.out.println("\n=== 异常情况测试 ===");
try {
assertions.findMax(new int[0]); // 空数组
} catch (AssertionError e) {
System.out.println("空数组断言: " + e.getMessage());
}
try {
assertions.getElementAt(fruits, 10); // 索引越界
} catch (AssertionError e) {
System.out.println("索引越界断言: " + e.getMessage());
}
}
}
类不变式和状态验证
public class BankAccount {
private String accountNumber;
private String accountHolder;
private double balance;
private final double minBalance;
private int transactionCount;
public BankAccount(String accountNumber, String accountHolder, double initialBalance, double minBalance) {
assert accountNumber != null && !accountNumber.trim().isEmpty() : "账号不能为空";
assert accountHolder != null && !accountHolder.trim().isEmpty() : "账户持有人不能为空";
assert initialBalance >= minBalance : "初始余额不能低于最低余额";
assert minBalance >= 0 : "最低余额不能为负";
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.balance = initialBalance;
this.minBalance = minBalance;
this.transactionCount = 0;
assertInvariant();
}
// 类不变式检查
private void assertInvariant() {
assert accountNumber != null && !accountNumber.trim().isEmpty() : "账号不变式违反";
assert accountHolder != null && !accountHolder.trim().isEmpty() : "账户持有人不变式违反";
assert balance >= minBalance : "余额不变式违反: 当前余额=" + balance + ", 最低余额=" + minBalance;
assert transactionCount >= 0 : "交易次数不变式违反";
}
public void deposit(double amount) {
assert amount > 0 : "存款金额必须为正: " + amount;
double oldBalance = balance;
balance += amount;
transactionCount++;
// 后置条件检查
assert balance == oldBalance + amount : "余额更新错误";
assert balance >= minBalance : "存款后余额低于最低余额";
assertInvariant();
}
public boolean withdraw(double amount) {
assert amount > 0 : "取款金额必须为正: " + amount;
if (balance - amount < minBalance) {
return false; // 余额不足
}
double oldBalance = balance;
balance -= amount;
transactionCount++;
// 后置条件检查
assert balance == oldBalance - amount : "余额更新错误";
assert balance >= minBalance : "取款后余额低于最低余额";
assertInvariant();
return true;
}
public void transfer(BankAccount targetAccount, double amount) {
assert targetAccount != null : "目标账户不能为null";
assert targetAccount != this : "不能向自己转账";
assert amount > 0 : "转账金额必须为正: " + amount;
double sourceOldBalance = this.balance;
double targetOldBalance = targetAccount.balance;
if (!this.withdraw(amount)) {
throw new IllegalStateException("余额不足,无法转账");
}
targetAccount.deposit(amount);
// 验证转账后的状态
assert this.balance == sourceOldBalance - amount : "源账户余额更新错误";
assert targetAccount.balance == targetOldBalance + amount : "目标账户余额更新错误";
assertInvariant();
targetAccount.assertInvariant();
}
// Getter方法
public String getAccountNumber() { return accountNumber; }
public String getAccountHolder() { return accountHolder; }
public double getBalance() { return balance; }
public double getMinBalance() { return minBalance; }
public int getTransactionCount() { return transactionCount; }
@Override
public String toString() {
return String.format("BankAccount{账号='%s', 持有人='%s', 余额=%.2f, 最低余额=%.2f, 交易次数=%d}",
accountNumber, accountHolder, balance, minBalance, transactionCount);
}
public static void main(String[] args) {
System.out.println("=== 银行账户断言测试 ===");
BankAccount account1 = new BankAccount("001", "张三", 1000.0, 100.0);
BankAccount account2 = new BankAccount("002", "李四", 500.0, 50.0);
System.out.println("初始状态:");
System.out.println(account1);
System.out.println(account2);
// 正常操作
System.out.println("\n=== 正常操作 ===");
account1.deposit(200);
System.out.println("张三存款200后: " + account1);
boolean success = account1.withdraw(150);
System.out.println("张三取款150: " + (success ? "成功" : "失败") + ", " + account1);
account1.transfer(account2, 300);
System.out.println("张三向李四转账300后:");
System.out.println(" " + account1);
System.out.println(" " + account2);
// 异常情况测试
System.out.println("\n=== 异常情况测试 ===");
try {
account1.withdraw(-50); // 负数取款
} catch (AssertionError e) {
System.out.println("负数取款断言: " + e.getMessage());
}
try {
account1.deposit(0); // 零金额存款
} catch (AssertionError e) {
System.out.println("零金额存款断言: " + e.getMessage());
}
try {
new BankAccount("", "空账号", 100, 0); // 空账号
} catch (AssertionError e) {
System.out.println("空账号断言: " + e.getMessage());
}
}
}
算法和数据结构验证
public class SortingAlgorithms {
// 冒泡排序
public static void bubbleSort(int[] array) {
assert array != null : "数组不能为null";
int[] original = array.clone(); // 保存原始数组用于验证
for (int i = 0; i < array.length - 1; i++) {
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
// 交换元素
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
// 验证部分排序的正确性
assert isPartiallySort(array, array.length - 1 - i) :
"冒泡排序第" + (i + 1) + "轮后验证失败";
}
// 最终验证
assert isSorted(array) : "排序结果不正确";
assert hasAllElements(original, array) : "排序后丢失或改变了元素";
}
// 快速排序
public static void quickSort(int[] array) {
assert array != null : "数组不能为null";
int[] original = array.clone();
quickSortRecursive(array, 0, array.length - 1);
assert isSorted(array) : "快速排序结果不正确";
assert hasAllElements(original, array) : "快速排序后丢失或改变了元素";
}
private static void quickSortRecursive(int[] array, int low, int high) {
assert low >= 0 && high < array.length : "快速排序索引越界";
assert low <= high : "快速排序索引顺序错误";
if (low < high) {
int pivotIndex = partition(array, low, high);
assert pivotIndex >= low && pivotIndex <= high : "分区索引错误";
assert isPartitioned(array, low, high, pivotIndex) : "分区操作失败";
quickSortRecursive(array, low, pivotIndex - 1);
quickSortRecursive(array, pivotIndex + 1, high);
}
}
private static int partition(int[] array, int low, int high) {
int pivot = array[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (array[j] <= pivot) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, high);
return i + 1;
}
private static void swap(int[] array, int i, int j) {
assert i >= 0 && i < array.length : "交换索引i越界: " + i;
assert j >= 0 && j < array.length : "交换索引j越界: " + j;
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
// 验证辅助方法
private static boolean isSorted(int[] array) {
for (int i = 1; i < array.length; i++) {
if (array[i - 1] > array[i]) {
return false;
}
}
return true;
}
private static boolean isPartiallySort(int[] array, int fromIndex) {
for (int i = fromIndex + 1; i < array.length; i++) {
if (i > fromIndex + 1 && array[i - 1] > array[i]) {
return false;
}
}
return true;
}
private static boolean isPartitioned(int[] array, int low, int high, int pivotIndex) {
int pivotValue = array[pivotIndex];
// 检查pivot左边的元素都小于等于pivot
for (int i = low; i < pivotIndex; i++) {
if (array[i] > pivotValue) {
return false;
}
}
// 检查pivot右边的元素都大于等于pivot
for (int i = pivotIndex + 1; i <= high; i++) {
if (array[i] < pivotValue) {
return false;
}
}
return true;
}
private static boolean hasAllElements(int[] original, int[] sorted) {
// 简化版:检查长度和元素总和
if (original.length != sorted.length) {
return false;
}
long originalSum = 0, sortedSum = 0;
for (int i = 0; i < original.length; i++) {
originalSum += original[i];
sortedSum += sorted[i];
}
return originalSum == sortedSum;
}
public static void main(String[] args) {
System.out.println("=== 排序算法断言测试 ===");
// 测试冒泡排序
System.out.println("=== 冒泡排序测试 ===");
int[] array1 = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前: " + java.util.Arrays.toString(array1));
bubbleSort(array1);
System.out.println("排序后: " + java.util.Arrays.toString(array1));
// 测试快速排序
System.out.println("\n=== 快速排序测试 ===");
int[] array2 = {64, 34, 25, 12, 22, 11, 90};
System.out.println("排序前: " + java.util.Arrays.toString(array2));
quickSort(array2);
System.out.println("排序后: " + java.util.Arrays.toString(array2));
// 边界情况测试
System.out.println("\n=== 边界情况测试 ===");
int[] empty = {};
int[] single = {42};
int[] duplicate = {3, 1, 3, 2, 3, 1};
bubbleSort(empty);
System.out.println("空数组排序: " + java.util.Arrays.toString(empty));
quickSort(single);
System.out.println("单元素排序: " + java.util.Arrays.toString(single));
bubbleSort(duplicate);
System.out.println("重复元素排序: " + java.util.Arrays.toString(duplicate));
// 错误情况测试
System.out.println("\n=== 错误情况测试 ===");
try {
bubbleSort(null);
} catch (AssertionError e) {
System.out.println("null数组断言: " + e.getMessage());
}
}
}
断言的使用场景
1. 前置条件检查
public void processFile(String filename) {
assert filename != null : "文件名不能为null";
assert !filename.trim().isEmpty() : "文件名不能为空";
// 处理文件
}
2. 后置条件验证
public List<String> sortStrings(List<String> input) {
List<String> result = new ArrayList<>(input);
Collections.sort(result);
assert result.size() == input.size() : "排序后元素数量改变";
assert isSorted(result) : "排序结果不正确";
return result;
}
3. 内部状态验证
private void assertInternalState() {
assert size >= 0 : "大小不能为负";
assert capacity >= size : "容量不能小于大小";
assert data != null : "数据数组不能为null";
}
4. 控制流验证
public void processInput(String input) {
switch (input.toLowerCase()) {
case "start":
startProcess();
break;
case "stop":
stopProcess();
break;
default:
assert false : "未知的输入: " + input;
}
}
最佳实践
不要依赖断言的副作用:
// 错误:断言可能被禁用 assert ++counter > 0; // 正确:副作用在断言外 counter++; assert counter > 0;
提供有意义的错误信息:
assert x > 0 : "x必须为正数,当前值: " + x;
不要用于公共API参数验证:
// 错误:用断言验证公共方法参数 public void publicMethod(String param) { assert param != null; // 可能被禁用 } // 正确:用显式检查 public void publicMethod(String param) { if (param == null) { throw new IllegalArgumentException("参数不能为null"); } }
避免复杂的断言条件:
// 复杂断言影响可读性 assert checkComplexCondition() : "复杂条件检查失败";
注意事项
- 默认禁用:断言默认是禁用的,需要通过JVM参数启用
- 性能影响:启用断言可能影响性能,特别是在复杂断言中
- 不适合生产环境:通常只在开发和测试阶段使用
- 与异常的区别:断言用于检查程序逻辑,异常用于处理运行时错误
assert关键字是Java中强大的调试和测试工具,正确使用可以显著提高代码质量和可靠性。