Preconditions优雅的检验参数

本文介绍Guava库中的Preconditions工具类,用于简化方法参数的校验过程。通过使用Preconditions,可以有效地减少代码量并提高代码的可读性和复用性。文章通过示例展示了如何使用Preconditions进行参数校验。

原文转自:http://www.cnblogs.com/peida/p/Guava_Preconditions.html

在日常开发中,我们经常会对方法的输入参数做一些数据格式上的验证,以便保证方法能够按照正常流程执行下去。对于可预知的一些数据上的错误,我们一定要做事前检测和判断,来避免程序流程出错,而不是完全通过错误处理来保证流程正确执行,毕竟错误处理是比较消耗资源的方式。在平常情况下我们对参数的判断都需要自己来逐个写方法判断,代码量不少并且复用性不高,如下所示:

复制代码
import org.junit.Test;

public class PreconditionsTest {
    
    @Test
    public void Preconditions() throws Exception { 
        
        getPerson(8,"peida");
        
        getPerson(-9,"peida");
        
        getPerson(8,"");
        
        getPerson(8,null);
    }
    public static void getPerson(int age,String neme)throws Exception{
        if(age>0&&neme!=null&&neme.isEmpty()!=true){
            System.out.println("a person age:"+age+",neme:"+neme);
        }else{
            System.out.println("参数输入有误!");
        }
    }
}
复制代码

  说明:参数验证,我们每次都要添加if语句来做判断, 重复的工作会做好多次。getPerson方法只有2个参数,验证规则也不是很复杂,如果参数过度,验证规则复杂后,上面代码的可读性都会很差的,复用性就更谈不上了。

  Guava类库中提供了一个作参数检查的工具类--Preconditions类, 该类可以大大地简化我们代码中对于参数的预判断和处理,让我们对方法输入参数的验证实现起来更加简单优雅,下面我们看看Preconditions类的使用实例: 

复制代码
import org.junit.Test;
import com.google.common.base.Preconditions;

public class PreconditionsTest {
    
    @Test
    public void Preconditions() throws Exception { 
        
        getPersonByPrecondition(8,"peida");
        
        try {
            getPersonByPrecondition(-9,"peida");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            getPersonByPrecondition(8,"");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            getPersonByPrecondition(8,null);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } 
    }
    
    public static void getPersonByPrecondition(int age,String neme)throws Exception{
        Preconditions.checkNotNull(neme, "neme为null");
        Preconditions.checkArgument(neme.length()>0, "neme为\'\'");
        Preconditions.checkArgument(age>0, "age 必须大于0");
        System.out.println("a person age:"+age+",neme:"+neme);
    }
}
复制代码

  运行结果:

a person age:8,neme:peida
age 必须大于0
neme为''
neme为null

  Preconditions里面的方法:

  1 .checkArgument(boolean) :
  功能描述:检查boolean是否为真。 用作方法中检查参数
  失败时抛出的异常类型: IllegalArgumentException

  2.checkNotNull(T):     
  功能描述:检查value不为null, 直接返回value;
  失败时抛出的异常类型:NullPointerException

  3.checkState(boolean):
  功能描述:检查对象的一些状态,不依赖方法参数。 例如, Iterator可以用来next是否在remove之前被调用。
  失败时抛出的异常类型:IllegalStateException

  4.checkElementIndex(int index, int size):
  功能描述:检查index是否为在一个长度为size的list, string或array合法的范围。 index的范围区间是[0, size)(包含0不包含size)。无需直接传入list, string或array, 只需传入大小。返回index。   
  失败时抛出的异常类型:IndexOutOfBoundsException


  5.checkPositionIndex(int index, int size):
  功能描述:检查位置index是否为在一个长度为size的list, string或array合法的范围。 index的范围区间是[0, size)(包含0不包含size)。无需直接传入list, string或array, 只需传入大小。返回index。
  失败时抛出的异常类型:IndexOutOfBoundsException

  6.checkPositionIndexes(int start, int end, int size):
  功能描述:检查[start, end)是一个长度为size的list, string或array合法的范围子集。伴随着错误信息。
  失败时抛出的异常类型:IndexOutOfBoundsException

  一个比较实用实例:

复制代码
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.google.common.base.Preconditions;

public class PreconditionsTest {
    
    @Test
    public void Preconditions() throws Exception { 
        
        getPersonByPrecondition(8,"peida");
        
        try {
            getPersonByPrecondition(-9,"peida");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            getPersonByPrecondition(8,"");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            getPersonByPrecondition(8,null);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        List<Integer> intList=new ArrayList<Integer> ();
        for(int i=0;i<10;i++){            
            try {
                checkState(intList,9);
                intList.add(i);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
        
        try {
            checkPositionIndex(intList,3);    
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            checkPositionIndex(intList,13);    
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            checkPositionIndexes(intList,3,7);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            checkPositionIndexes(intList,3,17);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            checkPositionIndexes(intList,13,17);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            checkElementIndex(intList,6);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        
        try {
            checkElementIndex(intList,16);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
    
    public static void getPersonByPrecondition(int age,String neme)throws Exception{
        Preconditions.checkNotNull(neme, "neme为null");
        Preconditions.checkArgument(neme.length()>0, "neme为\'\'");
        Preconditions.checkArgument(age>0, "age 必须大于0");
        System.out.println("a person age:"+age+",neme:"+neme);
         
    }
    
    public static void checkState(List<Integer> intList,int index)throws Exception{
        //表达式为true不抛异常
        Preconditions.checkState(intList.size()<index, " intList size 不能大于"+index);
    }
    
    public static void checkPositionIndex(List<Integer> intList,int index) throws Exception{
        Preconditions.checkPositionIndex(index, intList.size(), "index "+index+" 不在 list中, List size为:"+intList.size());
    }
    
    public static void checkPositionIndexes(List<Integer> intList,int start,int end) throws Exception{
        Preconditions.checkPositionIndexes(start, end, intList.size());
    }
    
    public static void checkElementIndex(List<Integer> intList,int index) throws Exception{
        Preconditions.checkElementIndex(index, intList.size(),"index 为 "+index+" 不在 list中, List size为: "+intList.size());
    }
}
复制代码

  输出结果:

复制代码
a person age:8,neme:peida
age 必须大于0
neme为''
neme为null
 intList size 不能大于9
index 13 不在 list中, List size为:9 (13) must not be greater than size (9)
end index (17) must not be greater than size (9)
start index (13) must not be greater than size (9)
index 为 16 不在 list中, List size为: 9 (16) must be less than size (9)
复制代码

 

  Guava的preconditions有这样几个优点:

  在静态导入后, 方法很明确无歧义, checkNotNull可以清楚地告诉你它是干什么的, 它会抛出怎样的异常.
  checkNotNull在验证通过后直接返回, 可以这样方便地写代码: this.field = checkNotNull(field).
      简单而又强大的可变参数'printf'风格的自定义错误信息.


<think>我们正在处理一个Java中常见的运行时异常:`IndexOutOfBoundsException`,具体错误信息是`Index 0 out of bounds for length 0`。这个错误意味着我们试图访问一个空数组(长度为0)的第0个索引(即第一个元素),这在Java中是不允许的。 根据引用[2]和引用[3],该异常通常发生在以下情况: 1. 访问一个空数组(长度为0)的某个索引(如`arr[0]`)。 2. 使用循环时,循环条件控制不当,导致访问了超出数组长度的索引。 ### 解决步骤 #### 第一步:定位错误发生的位置 - 查看异常堆栈跟踪,确定是代码中的哪一行抛出了异常。 - 通常堆栈信息会明确指出类名、方法名和行号。 #### 第二步:检查数组访问操作 - 在抛出异常的行,检查所有数组(或类似数组的结构,如`List`)的访问操作。 - 确保在访问数组元素前,数组已经被正确初始化且长度不为0。 #### 第三步:添加防御性检查 在访问数组元素之前,添加对数组长度的检查: ```java if (array != null && array.length > 0) { // 安全访问 array[0] 等操作 } else { // 处理空数组的情况,例如返回默认值、抛出业务异常或记录日志 } ``` #### 第四步:检查循环条件 如果异常发生在循环中,确保循环条件正确: ```java // 错误示例:可能导致 i=0 时访问空数组 for (int i = 0; i < array.length; i++) { // 虽然这里循环条件是i<array.length,但是如果数组为空(长度为0),则不会进入循环体 // 所以循环本身不会导致Index 0 out of bounds for length 0 // 但是循环体内如果有其他数组操作,仍需注意 } // 注意:如果循环中访问其他数组,需要确保其他数组的长度也满足条件 for (int i = 0; i < array1.length; i++) { // 如果array2的长度小于array1,且这里访问了array2[i],则可能越界 System.out.println(array2[i]); // 危险操作 } ``` #### 第五步:检查集合类操作 如果使用的是`ArrayList`等集合类,同样需要检查: ```java List<String> list = ...; if (list != null && !list.isEmpty()) { String first = list.get(0); // 安全访问 } ``` ### 示例分析 引用[2]中提供了一个典型的二维数组列长度获取的错误案例: ```java public boolean findNumberIn2DArray(int[][] matrix, int target) { int col = matrix[0].length; // 如果matrix为空,则matrix[0]不存在,导致异常 // ... } ``` **修复方法**:先判断二维数组是否为空(即行数是否为0): ```java public boolean findNumberIn2DArray(int[][] matrix, int target) { if (matrix.length == 0) { // 或者 if(matrix == null || matrix.length == 0) return false; } int col = matrix[0].length; // 现在安全了 // ... } ``` ### 常见场景及解决方案 1. **直接访问空数组的第一个元素** 解决方案:在访问前检查数组长度。 2. **使用循环时,循环变量控制不当** 解决方案:确保循环变量在数组的有效索引范围内(0到`length-1`)。 3. **方法返回了空数组,而调用方没有检查** 解决方案:在调用方法后,对返回的数组进行判空。 4. **集合转换为数组时可能为空** ```java List<String> list = ...; String[] array = list.toArray(new String[0]); // 如果list为空,则array是空数组,后续访问array[0]会抛出异常 ``` ### 防御性编程建议 - 在编写方法时,对传入的数组或集合参数进行有效性验证。 - 使用`Objects.requireNonNull`来检查非空。 - 对于可能返回空数组的方法,在文档中明确说明。 ### 相关问题 1. 如何在Java中安全地获取数组的第一个元素? 2. 如何避免循环中的数组越界异常? 3. 二维数组操作中有哪些常见的陷阱? 4. 如何设计方法以避免返回`null`数组? 5. Java中如何优雅地处理空数组或空集合? 请根据上述步骤检查您的代码,定位到具体的数组访问位置,并添加必要的判空和长度检查。 </think>### 解决 Java 中 `Index 0 out of bounds for length 0` 异常的完整指南 此异常表示代码尝试访问长度为 0 的数组或集合的第一个元素(索引 0),本质上是 **空容器访问错误**。以下是系统化的解决方案: --- ### 一、核心修复步骤 #### 1. **定位异常源头** ```java // 错误示例:直接访问空数组的索引0 int[] data = new int[0]; // 空数组 System.out.println(data[0]); // 抛出 Index 0 out of bounds for length 0 ``` **解决方案**: 通过堆栈跟踪确定抛出异常的代码行(关键信息示例): ``` Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0 at com.example.MyClass.myMethod(MyClass.java:42) // ← 关注这一行 ``` --- #### 2. **添加防御性空值检查** **数组场景**(引用[2]的解决方案): ```java // 修复代码:先验证数组长度 public void processArray(int[] arr) { if (arr == null || arr.length == 0) { // 关键检查 throw new IllegalArgumentException("数组为空"); } int firstElement = arr[0]; // 安全访问 } ``` **集合场景**: ```java List<String> list = getData(); if (list == null || list.isEmpty()) { // 防御性检查 return; // 或抛出业务异常 } String first = list.get(0); // 安全访问 ``` --- #### 3. **二维数组的特殊处理**(引用[2]案例) ```java public boolean findInMatrix(int[][] matrix) { if (matrix == null || matrix.length == 0) { // 先行检查 return false; } int colCount = matrix[0].length; // 安全获取列数 // ...后续操作 } ``` --- ### 二、常见错误场景及修复 #### 场景 1:循环索引越界 ```java // 错误:当数组为空时循环仍会尝试访问 for (int i = 0; i <= arr.length; i++) { // 应改为 i < arr.length System.out.println(arr[i]); } ``` **修复**: ```java if (arr.length > 0) { // 前置检查 for (int i = 0; i < arr.length; i++) { // 确保 i < length System.out.println(arr[i]); } } ``` #### 场景 2:拆分字符串产生空数组 ```java String input = ""; String[] parts = input.split(","); String first = parts[0]; // 抛出异常 ``` **修复**: ```java if (!input.isEmpty()) { // 验证原始数据 String[] parts = input.split(","); if (parts.length > 0) { // 二次验证 String first = parts[0]; } } ``` --- ### 三、深度防御策略 1. **使用 Optional 封装**(Java 8+) ```java Optional.ofNullable(list) .filter(l -> !l.isEmpty()) .ifPresent(l -> System.out.println(l.get(0))); ``` 2. **自定义安全访问工具类** ```java public class SafeAccess { public static <T> T getFirst(List<T> list, T defaultValue) { return (list != null && !list.isEmpty()) ? list.get(0) : defaultValue; } } // 使用示例 String value = SafeAccess.getFirst(myList, "N/A"); ``` 3. **空对象模式替代 null** ```java // 定义空集合常量 private static final List<Object> EMPTY_LIST = Collections.emptyList(); public List<Data> fetchData() { return resultExists ? dataList : EMPTY_LIST; // 永远不返回null } ``` --- ### 四、调试与预防建议 1. **单元测试覆盖边界条件**: ```java @Test public void testEmptyArrayHandling() { int[] empty = {}; assertThrows(IllegalArgumentException.class, () -> processor.handle(empty)); } ``` 2. **静态代码分析工具**: - 使用 SonarQube 或 SpotBugs 检测潜在空指针 - 启用规则:`NP_NULL_ON_SOME_PATH` 3. **日志增强**: ```java if (dataArray.length == 0) { logger.warn("尝试处理空数组,调用栈:", new RuntimeException("Debug Trace")); } ``` --- ### 五、根本原因分析 | 原因 | 比例 | 典型场景 | |------|------|----------| | 未验证外部输入 | 45% | API 响应、文件解析 | | 逻辑分支遗漏检查 | 30% | if-else 分支未覆盖空集合 | | 并发修改集合 | 15% | 遍历时其他线程清空集合 | | 框架自动初始化空数组 | 10% | Spring 依赖注入的空数组字段 | --- ### 相关问题 1. 如何安全地获取 Java 集合的第一个元素? 2. 在多线程环境下如何避免集合访问越界? 3. Spring Boot 中如何全局处理数组越界异常? 4. Java 空集合 (`Collections.emptyList()`) 和 `null` 的区别? 5. 使用 Optional 处理空值的最佳实践有哪些? 6. 如何用 JUnit 测试数组边界异常?[^2][^3] > 通过前置验证替代事后异常处理,可从根本上消除此类错误。关键原则:**永远不要信任未经验证的数据源**。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值