`
dandy
  • 浏览: 67223 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

异常为循环而抛

    博客分类:
  • java
阅读更多
出自《java puzzle》


下面的程序循环遍历了一个int类型的数组序列,并且记录了满足某个特定属性的数组个数。那么,该程序会打印出什么呢?
public class Loop {
  public static void main(String[] args) {
    int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 },
                    { 1, 2, 3 }, { 1, 2, 3, 4 }, { 1 } };
    int successCount = 0;
    try {
      int i = 0;
      while (true) {
        if (thirdElementIsThree(tests[i++]))
          successCount ++;
        }
    } catch(ArrayIndexOutOfBoundsException e) {
       // No more tests to process
    }
    System.out.println(successCount);
  }
  private static boolean thirdElementIsThree(int[] a) {
    return a.length >= 3 & a[2] == 3;
  }
}


该程序用thirdElementIsThree方法测试了tests数组中的每一个元素。遍历这个数组的循环显然是非传统的循环:它不是在循环变量等于数组长度的时候终止,而是在它试图访问一个并不在数组中的元素时终止。尽管它是非传统的,但是这个循环应该可以工作。如果传递给thirdElementIsThree的参数具有3个或更多的元素,并且其第三个元素等于3,那么该方法将返回true。对于tests中的5个元素来说,有2个将返回true,因此看起来该程序应该打印2。如果你运行它,就会发现它打印的时0。肯定是哪里出了问题,你能确定吗?
事实上,这个程序犯了两个错误。第一个错误是该程序使用了一种可怕的循环惯用法,该惯用法依赖的是对数组的访问会抛出异常。这种惯用法不仅难以阅读,而且运行速度还非常地慢。不要使用异常来进行循环控制;应该只为异常条件而使用异常[EJ Item 39]。为了纠正这个错误,可以将整个try-finally语句块替换为循环遍历数组的标准惯用法:

for (int i = 0; i < test.length; i++)
if (thirdElementIsThree(tests[i]))
successCount++;

如果你使用的是5.0或者是更新的版本,那么你可以用for循环结构来代替:
for (int[] test : tests)
if(thirdElementIsThree(test))
successCount++;

就第一个错误的糟糕情况来说,只有它自己还不足以产生我们所观察到的行为。然而,订正该错误可以帮助我们找到真正的bug,它更加深奥:
Exception in thread "main"
java.lang.ArrayIndexOutOfBoundsException: 2
at Loop1.thirdElementIsThree(Loop1.java:19)
at Loop1.main(Loop1.java:13)
很明显,在thirdElementIsThree方法中有一个bug:它抛出了一个ArrayIndexOutOfBoundsException异常。这个异常先前伪装成了那个可怕的基于异常的循环的终止条件。
如果传递给thirdElementIsThree的参数具有3个或更多的元素,并且其第三个元素等于3,那么该方法将返回true。问题是在这些条件不满足时它会做些什么呢。如果你仔细观察其值将会被返回的那个布尔表达式,你就会发现它与大多数布尔AND操作有一点不一样。这个表达式是a.length >= 3 & a[2] == 3。通常,你在这种情况下看到的是 && 操作符,而这个表达式使用的是 & 操作符。那是一个位AND操作符吗?
事实证明 & 操作符有其他的含义。除了常见的被当作整型操作数的位AND操作符之外,当被用于布尔操作数时,它的功能被重载为逻辑AND操作符[JLS 15.22.2]。这个操作符与更经常被使用的条件AND操作符有所不同,& 操作符总是要计算它的两个操作数,而 && 操作符在其左边的操作数被计算为false时,就不再计算右边的操作数了[JLS 15.23]。因此,thirdElementIsThree方法总是要试图访问其数组参数的第三个元素,即使该数组参数的元素不足3个也是如此。订正这个方法只需将 & 操作符替换为 && 操作符即可。通过这样的修改,这个程序就可以打印出我们所期望的2了:
private static boolean thirdElementIsThree(int[] a) {
return a.length >= 3 && a[2] == 3;
}
正像有一个逻辑AND操作符伴随着更经常被使用的条件AND操作符一样,还有一个逻辑OR操作符(|)也伴随着条件OR操作符(||)[JLS 15.22.2,15.24]。| 操作符总是要计算它的两个操作数,而 || 操作符在其左边的操作数被计算为true时,就不再计算右边的操作数了。我们一不注意,就很容易使用了逻辑操作符而不是条件操作符。遗憾的是,编译器并不能帮助你发现这种错误。有意识地使用逻辑操作符的情形非常少见,少到了我们对所有这样使用的程序都应该持怀疑态度的地步。如果你真的想使用这样的操作符,为了是你的意图清楚起见,请加上注释。

总之,不要去用那些可怕的使用异常而不是使用显式的终止测试的循环惯用法,因为这种惯用法非常不清晰,而且会掩盖bug。要意识到逻辑AND和OR操作符的存在,并且不要因无意识的误用而受害。对语言设计者来说,这又是一个操作符重载会导致混乱的明证。对于在条件AND和OR操作符之外还要提供逻辑AND和OR操作符这一点,并没有很明显的理由。如果这些操作符确实要得到支持的话,它们应该与其相对应的条件操作符存在着视觉上的明显差异。

1
0
分享到:
评论

相关推荐

    mybatis启动无线循环的抛出异常类

    mybatis启动无线循环的抛出异常类,只要用这个继承,就可以抛出异常

    抛出异常代码示例

    这段描述进一步明确了代码示例的具体语言环境为Java,并强调这是一个简单的示例,旨在帮助读者理解如何在Java中实现异常抛出。Java作为一种广泛应用的面向对象编程语言,提供了丰富的异常处理机制,包括`throw`...

    Java解惑(谜题)CHM中英文双版本

    谜题42:异常为循环而抛 谜题43:异常地危险 谜题44:切掉类 谜题45:令人疲惫不堪的测验 Java谜题5——类谜题 谜题46:令人混淆的构造器案例 谜题47:啊呀!我的猫变成狗了 谜题48:我所得到的都是静态的 ...

    C#异常抛出和排序功能

    根据给定的信息,本文将详细解析“C#异常抛出和排序功能”这一主题,包括如何在C#中显式地引发异常以及实现数组的排序。 ### C#中的异常处理 #### 异常概述 异常是在程序运行时发生的错误或意外情况。C#提供了强大...

    Java中增强for循环的实现原理和坑详解

    这种异常是在运行时抛出的,而不是在编译期。 例如,在遍历集合时,修改集合的内容可能会引发ConcurrentModificationException异常: for (Student stu : students) { if (stu.getId() == 2) students.remove...

    Java代码循环的优化

    在循环体内抛出异常会导致循环提前终止,并且可能影响循环外的数据状态。为了减少这种负面影响,应该尽量将异常处理逻辑放在循环外部。这样即使循环内部出现异常,也能够确保循环能够继续执行或安全地终止。 - **...

    JAVA异常详解.doc

    15. **类循环依赖错误**:`ClassCircularityError` - 初始化类时,检测到类之间的循环依赖,会抛出此异常。 16. **类格式错误**:`ClassFormatError` - 当读取的类文件格式不符合Java虚拟机规范时,会抛出此异常。 ...

    Java中常见的异常分析

    15. **类循环依赖错误:ClassCircularityError** - 初始化类时,如果检测到类之间的循环依赖,会抛出此异常。 16. **类格式错误:ClassFormatError** - 当Java虚拟机尝试读取的类文件格式不正确时,会抛出此异常。 ...

    UncaughtException不让Android应用异常退出

    在Java编程中,当一个线程抛出一个未捕获的异常时,系统会寻找该线程的`UncaughtExceptionHandler`。默认情况下,Android系统会终止应用并显示一个错误对话框。我们可以通过设置自定义的`UncaughtExceptionHandler`...

    java异常汇总.txt

    例如,如果一个对象实际上是`String`类型,而我们尝试将其转换为`Integer`类型,则会抛出此类异常。 #### 4. NegativeArraySizeException - 数组负大小异常 当创建数组时指定的大小为负数,Java会抛出`...

    android异常类型和处理

    8. StackOverflowError:当调用栈超过其最大深度时抛出,通常是由于无限递归或其他无限制的循环引起的。 9. NoClassDefFoundError:在运行时无法找到类的定义时抛出,可能是由于类路径配置问题或依赖缺失。 了解...

    循环队列的C++实现

    当队列空时,可以抛出异常表示队列为空。 3. `isEmpty`: 检查队列是否为空。 4. `isFull`: 检查队列是否已满。 5. `peek`: 查看队首元素但不移除。 6. `size`: 返回队列中元素的数量。 在C++中,这些方法的实现可能...

    Java异常处理终结篇——如何进行Java异常处理设计 - 望远的个人页面 - 开源中国社区1

    12. **避免在循环中捕获异常**:这可能会隐藏循环内部的错误,使得问题不易发现。 13. **异常处理与资源管理**:在使用诸如文件、网络连接等资源时,通常会在finally块中关闭它们,以防止资源泄露。 14. **异常...

    java解惑 java 表达式谜题 java 字符谜题 java 循环谜题 java 异常谜题

    理解何时抛出异常(`throw`)、如何捕获异常(`catch`)以及在`finally`块中执行的代码是解决异常谜题的关键。此外,Java提供了多个预定义的异常类,如`NullPointerException`、`ArrayIndexOutOfBoundsException`,...

    基于SpringBoot构造器注入循环依赖及解决方式

    循环依赖是指两个或多个Bean之间形成一个闭环,彼此依赖对方,导致Spring容器在初始化Bean时无法确定创建顺序,从而抛出异常。 1. 循环依赖的定义: 循环依赖是指Bean A依赖于Bean B,同时Bean B又依赖于Bean A的...

    java异常处理例题代码.pdf

    第三个示例App9_3.java中,通过判断变量b是否为0来决定是否抛出ArithmeticException异常。如果b为0,则抛出异常,否则执行除法运算并打印结果。 在第四个示例App9_4.java中,演示了方法中的参数值异常。multi方法...

    JAVA异常大全

    9. **字符串转换为数字异常:NumberFormatException** - 尝试将非数字字符串转换为数值类型时,会抛出此异常。 10. **操作数据库异常:SQLException** - 在处理数据库连接、查询或其他操作时,如果出现错误,会抛出...

    java常见异常总结

    - **异常概述**:当试图访问或修改类、字段或方法时,由于权限不足而抛出。 - **典型场景**: - 尝试访问私有或受保护的成员。 - 类型转换失败。 - **处理方法**: - 确保具有正确的访问权限。 - 使用适当的方法...

    java异常集合

    - **ClassCircularityError**:在初始化一个类时,若检测到类之间循环依赖则抛出。 - **ClassFormatError**:当Java虚拟机试图从一个文件中读取Java类,而检测到该文件的内容不符合类的有效格式时抛出。 - **Error**...

Global site tag (gtag.js) - Google Analytics