第五章 流程控制、异常与断言
5.1 if和switch语句
考试目标2.1 编写实现if或switch语句的代码,并辨别这些语句的合法变元类型。
if和switch语句通常被称作判决语句。在程序中使用判决语句时,是在要求程序计算一个给定的表达式,以判断要采取哪个操作。
5.1.1 if-else 分支
int a = 10; if(a>20){ //doStuff }else if(a<20){ //doStuff }else{ //doStuff }
if 语句的合法表达式
if语句的唯一合法表达式是布尔表达式。换句话说,只能是可以被解析为布尔值或布尔变量的表达式。
注意布尔赋值(=),它可能被错认为不二相等性(==)测试
public class Test{ public static void main(String[] args){ boolean b = true; if(b=true){ System.out.println("b="+b); } } } 结果:b=true
public class Test{ public static void main(String[] args){ boolean b = true; if(b=false){ System.out.println("b="+b); } } } 结果:(什么都没有)
5.1.2 switch 语句
switch (x) { case 1: // doStuff break; case 2: // doStuff break; case 3: // doStuff break; case 4: // doStuff break; default: // doStuff }
switch 和 case 的合法表达式
switch(expression){ case constant1: code block case constant2: code block default: code block }
switch的表达式必须能求值成char、byte、short、int或者枚举。这意味着如果没有使用枚举,则只有能够被自动提升为int的变量和值才是可接受的。
case后面的常量必须是字面值或final变量,或者是常量表达式,包括枚举。不能在case中包含非final变量或一个取值范围。
switch 块内的中断和直落
case只是这个switch块的入口,如果没有碰到break,它会一直执行下去,直到遇到break,或者switch的尾部。
默认 case
default :{}
default case 不一定总要置于switch的末尾。
5.2 循环和迭代器
考试目标2.2 编写代码,实现循环和迭代器的所有形式,包括使用for、增强型for循环(for-each)、do、while、标签、break和continue,并解释在循环执行期间和执行之后循环计数器变量所具有的值。
5.2.1 使用while循环
while (expression) { //do stuff } //expression应该是一个测试条件,不应将声明放在这里。满足条件执行循环体一次。
5.2.2 使用do循环
do{ //do stuff }while(expression); //先执行一次循环体,完成后判断expression的结果,为true则再次执行一次循环体,false则跳出循环
5.2.3 使用for循环
旧式的for循环称为“基本for循环”,新式的称之为“增强型for循环”(for-each循环)
基本for循环
for循环声明包括3个主要部分:变量的声明和初始化,布尔表达式(条件测试),迭代表达式
基本for循环:声明和初始化
for语句的第一部分允许在for关键字之后的圆括号内声明并初始化0个、1个或多个同类型的变量。变量的作用域随着for循环的结束而结束。
for(int x=10,y>3;y>3;y++){}
基本for循环:条件(布尔)表达式
只能有一个测试表达式,不能使用逗号分隔的多个测试,但for语句中的另外两个部分可以有多个成分。
for(int x=0;(((x<10)&&(y-->2))|x==3);x++){}
基本for循环:迭代表达式
for循环体每次执行之后,都会执行迭代表达式。
除了强制退出之外,for循环中最后做的两件事总是:先计算迭代表达式,然后计算条件表达式。
强制退出包括:break、return、System.exit()方法或者异常。
基本for循环:for循环问题
for(;i>10;){}就像一个while(i>10){}
增强型for循环(用于数组)
for语句的两个部分是:
声明(declaration)。新声明的块变量,其类型与正在访问的数组元素的类型兼容。
表达式(expression)。它必须计算为想要遍历的数组。
List<Object> aa = new ArrayList<Object>(); for(Object a:aa){ // do stuff }
增强型for循环假定:除非提前退出循环,否则总会遍历数组的每一个元素。
5.2.4 使用break和continue
break终止整个循环,continue终止当前的迭代。
5.2.5 无标签的语句
“5.2.4”中说的就是无标签的情况
5.2.6 带标签的语句
标签要作为所标识的循环的前缀,并在标签名后加“:”。如下:
boolean isTrue = true; outer: for(int i = 0;i<5;i++){ while(isTrue){ System.out.println("Hello"); break outer; } System.out.println("Outer loop."); } System.out.println("Good-Bye"); //结果: //Hello //Good-Bye //可见,如果无标签,break只跳出while,但实际直接跳出了被标记为outer的for循环。
5.3 处理异常
考试目标2.4 编写使用异常和异常处理子句(try、catch、finally)的代码,并声明抛出异常的方法和重写方法。
考试目标2.5 了解代码段内特定点所产生的异常的后果。注意,异常可以是运行时异常、检验异常或错误。
异常语句就是告诉JVM“这些代码可能会出问题,你来监视他,我会告诉你万一出问题了该怎么办”。
异常有两种形式:检验异常和非检验异常。
检验异常包括Exception的所有子类型,但不包括扩展RuntimeException的类。
检验异常遵守处理或声明它的规则,任何可能抛出检验异常的方法(包括调用能够抛出检验异常方法的方法),都必须使用throws关键字声明异常,或者用适当的try/catch处理异常。
Error或RuntimeException的子类型是非检验异常,因此,编译器不强迫要求处理或声明规则。你可以随意声明或处理它们,编译器对此并不关心。
5.3.1 使用try和catch捕获异常
5.3.2 使用finally
finally块,只要不是JVM关闭这种情况,则finally块总会运行。
try{ //do stuff }catch(Exception ex){ //catch exception }finally{}
5.3.3 传播未捕获的异常
当我们不打算在这里处理异常,可以将异常throw,由下面来处理它。
5.3.4 定义异常
异常是一个改变正常程序流程的事件。
当抛出一个异常时,某个特定的Exception子类型的对象被实例化,并作为catch子句的变元传递到异常处理程序。
5.3.5 异常层次
Throwable的两个子类型:Exception 和 Error。
应用程序不能从Error恢复,这就谈不上处理。异常表示不是由程序设计错误导致发生的错误,而是由于某个资源不可用,或者正确执行所需的其他某个条件不满足所造成的。
5.3.6 处理整个异常类层次
在单个catch子句内可以捕获多种类型的异常。通常我们的异常处理应该是有针对性的,不分情况直接catch Exception的做法是不可取的,最好捕获明确的异常子类型,有针对的处理,编写catch处理代码。
5.3.7 异常匹配
最具体的异常处理程序必须总是放在更一般的异常处理程序的上面。
5.3.8 异常声明与公共接口
每个方法必须通过提供catch子句处理所有检验异常,或者将每个未处理的检验异常列为一个抛出的异常。
5.3.9 重抛同一个异常
在catch子句中可以写 “throw xxx;”这样的语句来重抛同一个异常。
5.4 常见异常和错误
考试目标2.6 识别将导致以下任何一种异常或错误被抛出的情况:ArrayIndexOutOfBoundsException、ClassCastException、IllegalArgumentException、IllegalStateException、NullPointerException、NumberFormatException、AssertionError、ExceptionInInitializerError、StackOverflowError或者NoClassDefFoundError。理解其中哪些是由虚拟机抛出的,并识别应该以编程方式抛出另外一些异常和错误的情况。
异常来自于那里?
JVM抛出的异常
以编程方式抛出的异常
考试中的异常和错误小结
常见异常的描述和来源表:
异常 | 描述 | 通常由谁抛出 |
ArrayIndexOutOfBoundsException | 试图用一个无效索引值(可能为负或者超出了数组的大小)访问数组时抛出 | JVM |
ClassCastException | 试图将一个引用变量强制转换为一个不能通过IS-A测试的类型时抛出 | JVM |
IllegalArgumentException | 当方法接收到的变元格式不同于该方法需要的格式时抛出 | 以编程方式 |
IllegalStateException | 当环境的状态与正在尝试的操作不匹配时抛出(例如,使用一台已关闭的扫描仪) | 以编程方式 |
NullPointerException | 试图访问带有一个当前值为null的引用变量的对象时抛出 | JVM |
NumberFormatException | 一个用于将String转换为数值的方法接收到一个不能转换的String时抛出 | 以编程方式 |
AssertionError | 当一条语句的布尔测试返回false时抛出 | 以编程方式 |
ExceptionInInitializerError | 试图初始化静态变量或静态初始化块时抛出 | JVM |
StackOverflowError | 当方法递归层次过深时,通常会抛出这个异常(每次调用都会添加到栈中) | JVM |
NoClassDefFoundError | 由于命令行错误、类路径问题,或者丢失了.class文件,使JVM找不到需要的类时抛出 | JVM |
5.5 使用断言机制
考试目标2.3 编写使用断言的代码,并区分断言的正确使用和错误使用。
5.5.1 断言(Assertion)概述
当测试和调试时,你想验证你的假设,但是又不希望在开发完成时去除print语句、运行时异常处理程序或if/else测试。但是将它们保留在程序中至少对向能有所影响。
这时就可以使用断言。它可以在实际部署时被系统自动清除。
断言的工作方式:它总是断定某件事情为true,如果不是,则立刻抛出AssertionError异常,告诉你这里跟你想的不一样。
断言的两种形式:
//非常简单(really simple) assert(y>x); //简单(simple) assert(y>x):"y is "+y+" x is "+ x; //这个字符串会被添加到栈踪迹中。
断言表达式规则:
public class TestAssert { void noReturn(){} int aReturn(){return 1;} void go(){ int x = 2; boolean b = true; //合法的 assert(x==1); assert(b); assert true; assert(x==1):x; //第二种表达式“:”后面可以返回的是任何值 assert(x==1):aReturn(); assert(x==1):new TestAssert(); //非法的 // assert(x=1); // assert(x); // assert 0; // assert(x==1):; // assert(x==1):noReturn(); // assert(x==1):TestAssert test; } public static void main(String[] args){ TestAssert obj = new TestAssert(); obj.go(); } }
5.5.2 启用断言
标识符与关键字
可以将assert用作关键字或者标识符,但是不能同时将它用作这两者。
使用java和javac的版本6
编译带有断言的代码
javac -source 1.4 JavaObjcet.java
带断言运行
默认情况下 断言是禁止的。
运行时启用断言
java -ea com.geeksanonymous.TestClass
java -enableassertions com.geeksanonymous.TestClass
运行时禁用断言
java -da com.geeksanonymous.TestClass
java -disableassertions com.geeksanonymous.TestClass
有选择地启用和禁用
java -ea:com.foo.Bar 在类com.foo.Bar中启用断言
java -ea -da:com.foo.Bar 在除了类com.foo.Bar的其他类中启用断言
java -ea:com.foo... 在包com.foo以及它的任何子类包中启用断言
java -ea -dsa 通常启动断言,但在系统类中禁止
5.5.3 适当地使用断言
不要使用断言验证公共方法的变元
可以使用断言验证私有方法的变元
不要使用断言验证命令行变元
即使在公共方法内,也可以使用断言检查从不会发生的情况
不要使用可能产生副作用的断言表达式