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;
    }
}

最佳实践

  1. 不要依赖断言的副作用

    // 错误:断言可能被禁用
    assert ++counter > 0;
    
    // 正确:副作用在断言外
    counter++;
    assert counter > 0;
    
  2. 提供有意义的错误信息

    assert x > 0 : "x必须为正数,当前值: " + x;
    
  3. 不要用于公共API参数验证

    // 错误:用断言验证公共方法参数
    public void publicMethod(String param) {
        assert param != null; // 可能被禁用
    }
    
    // 正确:用显式检查
    public void publicMethod(String param) {
        if (param == null) {
            throw new IllegalArgumentException("参数不能为null");
        }
    }
    
  4. 避免复杂的断言条件

    // 复杂断言影响可读性
    assert checkComplexCondition() : "复杂条件检查失败";
    

注意事项

  1. 默认禁用:断言默认是禁用的,需要通过JVM参数启用
  2. 性能影响:启用断言可能影响性能,特别是在复杂断言中
  3. 不适合生产环境:通常只在开发和测试阶段使用
  4. 与异常的区别:断言用于检查程序逻辑,异常用于处理运行时错误

assert关键字是Java中强大的调试和测试工具,正确使用可以显著提高代码质量和可靠性。

powered by Gitbook© 2025 编外计划 | 最后修改: 2025-07-28 16:25:54

results matching ""

    No results matching ""