第五章 流程控制、异常与断言
5.1 if和switch语句
考试目标2.1 编写实现if或switch语句的代码,并辨别这些语句的合法变元类型。
if和switch语句通常被称作判决语句。在程序中使用判决语句时,是在要求程序计算一个给定的表达式,以判断要采取哪个操作。
5.1.1 if-else 分支
Java代码
int a = 10;
if(a>20){
//doStuff
}else if(a<20){
//doStuff
}else{
//doStuff
}
对于给定的if,可以有0个或者1个else,并且它必须出现在任何else if之后
对于给定的if,可以有0个或者多个else if,并且它们必须出现在(可选的)else之前。
一旦摸个else if测试成功,则不会测试其余所有的 else if或者else。
if 语句的合法表达式
if语句的唯一合法表达式是布尔表达式。换句话说,只能是可以被解析为布尔值或布尔变量的表达式。
注意布尔赋值(=),它可能被错认为布尔相等性(==)测试
Java代码
public class Test{
public static void main(String[] args){
boolean b = true;
if(b=true){
System.out.println("b="+b);
}
}
}
结果:b=true
Java代码
public class Test{
public static void main(String[] args){
boolean b = true;
if(b=false){
System.out.println("b="+b);
}
}
}
结果:(什么都没有)
5.1.2 switch 语句
Java代码
switch (x) {
case 1:
// doStuff
break;
case 2:
// doStuff
break;
case 3:
// doStuff
break;
case 4:
// doStuff
break;
default:
// doStuff
}
switch 和 case 的合法表达式
Java代码
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循环
Java代码
while (expression) {
//do stuff
}
//expression应该是一个测试条件,不应将声明放在这里。满足条件执行循环体一次。
5.2.2 使用do循环
Java代码
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循环的结束而结束。
Java代码
for(int x=10,y>3;y>3;y++){}
for([color=red]int y=0,z=0[/color];z<x.length;z++){}
基本for循环:条件(布尔)表达式
只能有一个测试表达式,不能使用逗号分隔的多个测试,但for语句中的另外两个部分可以有多个成分。
Java代码
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)。[color=red]新声明的块变量[/color],其类型与正在访问的数组元素的类型兼容。[color=red]使用一个之前申明过得变量名称是不行的[/color]
表达式(expression)。它必须计算为想要遍历的数组。
Java代码
List<Object> aa = new ArrayList<Object>();
for(Object a:aa){
// do stuff
}
增强型for循环假定:除非提前退出循环,否则总会遍历数组的每一个元素。
5.2.4 使用break和continue
break终止整个循环,continue终止当前的迭代。[color=red]continue必须用在循环内,break必须用在循环或者Switch里面。[/color]
5.2.5 无标签的语句
“5.2.4”中说的就是无标签的情况
5.2.6 带标签的语句
标签要作为所标识的循环的前缀,并在标签名后加“:”。如下:
Java代码
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循环。
outer:
for(int i=0;i<5;i++){
for(int j=0;j<5;j++){
System.out.println("hello");
continue outer;
}
System.out.println("outer");
}
System.out.println("goodbye");
}
//输出 五个hello一个goodbye。
5.3 处理异常
异常有两种形式:检验异常和非检验异常。
检验异常包括Exception的所有子类型,但不包括扩展RuntimeException的类。
检验异常遵守处理或声明它的规则,任何可能抛出检验异常的方法(包括调用能够抛出检验异常方法的方法),都必须使用throws关键字声明异常,或者用适当的try/catch处理异常。
Error或RuntimeException的子类型是非检验异常,因此,编译器不强迫要求处理或声明规则。你可以随意声明或处理它们,编译器对此并不关心。
5.3.1 使用try和catch捕获异常
5.3.2 使用finally
finally块,只要不是JVM关闭这种情况,则finally块总会运行。
Java代码
try{
//do stuff
}catch(Exception ex){
//catch exception
}finally{} [color=red]可以省略catch字句或者finally字句,但是不能两者同时省略。try catch之间不能有别的语句[/color]
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 异常匹配
最具体的异常处理程序必须总是放在更一般的异常处理程序的上面。[color=red]越上面的异常处理范围要越小。否则会编译出错[/color]
5.3.8 异常声明与公共接口
[color=red]每个方法必须通过提供catch子句处理所有检验异常,或者将每个未处理的检验异常列为一个抛出的异常,运行时异常就没有这个规定[/color]。
[color=red]RuntimeException,Error,以及他们的所有子类型都是非检验异常,非检验异常不必定义或者处理。[/color]
[color=red]重写方法可以抛出任何未检验(运行时)异常,无论被重写方法是否声明了该声明。
重写方法一定不能抛出比被重写方法声明的检验异常更大的检验异常。
重写方法能够抛出更少的异常。[/color]
5.3.9 重抛同一个异常
在catch子句中可以写 “throw xxx;”这样的语句来重抛同一个异常。
5.4 常见异常和错误
考试目标2.6 识别将导致以下任何一种异常或错误被抛出的情况:ArrayIndexOutOfBoundsException、ClassCastException、IllegalArgumentException、IllegalStateException、NullPointerException、NumberFormatException、AssertionError、ExceptionInInitializerError、StackOverflowError或者NoClassDefFoundError。理解其中哪些是由虚拟机抛出的,并识别应该以编程方式抛出另外一些异常和错误的情况。
异常来自于那里?
JVM异常。这些异常或错误由JVM抛出,具有排他性或者最具逻辑性。
程序异常。这些异常由应用程序和/或API程序员显式地抛出。
JVM抛出的异常
以编程方式抛出的异常
考试中的异常和错误小结
常见异常的描述和来源表:
异常 描述 通常由谁抛出
ArrayIndexOutOfBoundsException 试图用一个无效索引值(可能为负或者超出了数组的大小)访问数组时抛出 JVM
ClassCastException 试图将一个引用变量强制转换为一个不能通过IS-A测试的类型时抛出 JVM
IllegalArgumentException 当方法接收到的变元格式不同于该方法需要的格式时抛出 以编程方式
IllegalStateException 当环境的状态与正在尝试的操作不匹配时抛出(例如,使用一台已关闭的扫描仪) 以编程方式
NullPointerException 试图访问带有一个当前值为null的引用变量的对象时抛出 JVM
NumberFormatException 一个用于将String转换为数值的方法接收到一个不能转换的String时抛出 以编程方式
[color=red]AssertionError 当一条语句的布尔测试返回false时抛出 以编程方式[/color]
ExceptionInInitializerError 试图初始化静态变量或静态初始化块时抛出 JVM
StackOverflowError 当方法递归层次过深时,通常会抛出这个异常(每次调用都会添加到栈中) JVM
NoClassDefFoundError 由于命令行错误、类路径问题,或者丢失了.class文件,使JVM找不到需要的类时抛出 JVM
5.5 使用断言机制
5.5.1 断言(Assertion)概述
当测试和调试时,你想验证你的假设,但是又不希望在开发完成时去除print语句、运行时异常处理程序或if/else测试。但是将它们保留在程序中至少对向能有所影响。
这时就可以使用断言。它可以在实际部署时被系统自动清除。
断言的工作方式:它总是断定某件事情为true,如果不是,则立刻抛出AssertionError异常,告诉你这里跟你想的不一样。
断言的两种形式:
Java代码
//非常简单(really simple)
assert(y>x);
//简单(simple)
assert(y>x):"y is "+y+" x is "+ x;
//这个字符串会被添加到栈踪迹中。
断言表达式规则:
Java代码
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 启用断言
标识符与关键字
[color=red]可以将assert用作关键字或者标识符,但是不能同时将它用作这两者[/color]。
使用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 适当地使用断言
不要使用断言验证公共方法的变元
可以使用断言验证私有方法的变元
不要使用断言验证命令行变元
即使在公共方法内,也可以使用断言检查从不会发生的情况,即如果到达了就抛出一个AssertionError
不要使用可能产生副作用的断言表达式,因为可能断言会关闭。
5.1 if和switch语句
考试目标2.1 编写实现if或switch语句的代码,并辨别这些语句的合法变元类型。
if和switch语句通常被称作判决语句。在程序中使用判决语句时,是在要求程序计算一个给定的表达式,以判断要采取哪个操作。
5.1.1 if-else 分支
Java代码
int a = 10;
if(a>20){
//doStuff
}else if(a<20){
//doStuff
}else{
//doStuff
}
对于给定的if,可以有0个或者1个else,并且它必须出现在任何else if之后
对于给定的if,可以有0个或者多个else if,并且它们必须出现在(可选的)else之前。
一旦摸个else if测试成功,则不会测试其余所有的 else if或者else。
if 语句的合法表达式
if语句的唯一合法表达式是布尔表达式。换句话说,只能是可以被解析为布尔值或布尔变量的表达式。
注意布尔赋值(=),它可能被错认为布尔相等性(==)测试
Java代码
public class Test{
public static void main(String[] args){
boolean b = true;
if(b=true){
System.out.println("b="+b);
}
}
}
结果:b=true
Java代码
public class Test{
public static void main(String[] args){
boolean b = true;
if(b=false){
System.out.println("b="+b);
}
}
}
结果:(什么都没有)
5.1.2 switch 语句
Java代码
switch (x) {
case 1:
// doStuff
break;
case 2:
// doStuff
break;
case 3:
// doStuff
break;
case 4:
// doStuff
break;
default:
// doStuff
}
switch 和 case 的合法表达式
Java代码
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循环
Java代码
while (expression) {
//do stuff
}
//expression应该是一个测试条件,不应将声明放在这里。满足条件执行循环体一次。
5.2.2 使用do循环
Java代码
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循环的结束而结束。
Java代码
for(int x=10,y>3;y>3;y++){}
for([color=red]int y=0,z=0[/color];z<x.length;z++){}
基本for循环:条件(布尔)表达式
只能有一个测试表达式,不能使用逗号分隔的多个测试,但for语句中的另外两个部分可以有多个成分。
Java代码
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)。[color=red]新声明的块变量[/color],其类型与正在访问的数组元素的类型兼容。[color=red]使用一个之前申明过得变量名称是不行的[/color]
表达式(expression)。它必须计算为想要遍历的数组。
Java代码
List<Object> aa = new ArrayList<Object>();
for(Object a:aa){
// do stuff
}
增强型for循环假定:除非提前退出循环,否则总会遍历数组的每一个元素。
5.2.4 使用break和continue
break终止整个循环,continue终止当前的迭代。[color=red]continue必须用在循环内,break必须用在循环或者Switch里面。[/color]
5.2.5 无标签的语句
“5.2.4”中说的就是无标签的情况
5.2.6 带标签的语句
标签要作为所标识的循环的前缀,并在标签名后加“:”。如下:
Java代码
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循环。
outer:
for(int i=0;i<5;i++){
for(int j=0;j<5;j++){
System.out.println("hello");
continue outer;
}
System.out.println("outer");
}
System.out.println("goodbye");
}
//输出 五个hello一个goodbye。
5.3 处理异常
异常有两种形式:检验异常和非检验异常。
检验异常包括Exception的所有子类型,但不包括扩展RuntimeException的类。
检验异常遵守处理或声明它的规则,任何可能抛出检验异常的方法(包括调用能够抛出检验异常方法的方法),都必须使用throws关键字声明异常,或者用适当的try/catch处理异常。
Error或RuntimeException的子类型是非检验异常,因此,编译器不强迫要求处理或声明规则。你可以随意声明或处理它们,编译器对此并不关心。
5.3.1 使用try和catch捕获异常
5.3.2 使用finally
finally块,只要不是JVM关闭这种情况,则finally块总会运行。
Java代码
try{
//do stuff
}catch(Exception ex){
//catch exception
}finally{} [color=red]可以省略catch字句或者finally字句,但是不能两者同时省略。try catch之间不能有别的语句[/color]
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 异常匹配
最具体的异常处理程序必须总是放在更一般的异常处理程序的上面。[color=red]越上面的异常处理范围要越小。否则会编译出错[/color]
5.3.8 异常声明与公共接口
[color=red]每个方法必须通过提供catch子句处理所有检验异常,或者将每个未处理的检验异常列为一个抛出的异常,运行时异常就没有这个规定[/color]。
[color=red]RuntimeException,Error,以及他们的所有子类型都是非检验异常,非检验异常不必定义或者处理。[/color]
[color=red]重写方法可以抛出任何未检验(运行时)异常,无论被重写方法是否声明了该声明。
重写方法一定不能抛出比被重写方法声明的检验异常更大的检验异常。
重写方法能够抛出更少的异常。[/color]
5.3.9 重抛同一个异常
在catch子句中可以写 “throw xxx;”这样的语句来重抛同一个异常。
5.4 常见异常和错误
考试目标2.6 识别将导致以下任何一种异常或错误被抛出的情况:ArrayIndexOutOfBoundsException、ClassCastException、IllegalArgumentException、IllegalStateException、NullPointerException、NumberFormatException、AssertionError、ExceptionInInitializerError、StackOverflowError或者NoClassDefFoundError。理解其中哪些是由虚拟机抛出的,并识别应该以编程方式抛出另外一些异常和错误的情况。
异常来自于那里?
JVM异常。这些异常或错误由JVM抛出,具有排他性或者最具逻辑性。
程序异常。这些异常由应用程序和/或API程序员显式地抛出。
JVM抛出的异常
以编程方式抛出的异常
考试中的异常和错误小结
常见异常的描述和来源表:
异常 描述 通常由谁抛出
ArrayIndexOutOfBoundsException 试图用一个无效索引值(可能为负或者超出了数组的大小)访问数组时抛出 JVM
ClassCastException 试图将一个引用变量强制转换为一个不能通过IS-A测试的类型时抛出 JVM
IllegalArgumentException 当方法接收到的变元格式不同于该方法需要的格式时抛出 以编程方式
IllegalStateException 当环境的状态与正在尝试的操作不匹配时抛出(例如,使用一台已关闭的扫描仪) 以编程方式
NullPointerException 试图访问带有一个当前值为null的引用变量的对象时抛出 JVM
NumberFormatException 一个用于将String转换为数值的方法接收到一个不能转换的String时抛出 以编程方式
[color=red]AssertionError 当一条语句的布尔测试返回false时抛出 以编程方式[/color]
ExceptionInInitializerError 试图初始化静态变量或静态初始化块时抛出 JVM
StackOverflowError 当方法递归层次过深时,通常会抛出这个异常(每次调用都会添加到栈中) JVM
NoClassDefFoundError 由于命令行错误、类路径问题,或者丢失了.class文件,使JVM找不到需要的类时抛出 JVM
5.5 使用断言机制
5.5.1 断言(Assertion)概述
当测试和调试时,你想验证你的假设,但是又不希望在开发完成时去除print语句、运行时异常处理程序或if/else测试。但是将它们保留在程序中至少对向能有所影响。
这时就可以使用断言。它可以在实际部署时被系统自动清除。
断言的工作方式:它总是断定某件事情为true,如果不是,则立刻抛出AssertionError异常,告诉你这里跟你想的不一样。
断言的两种形式:
Java代码
//非常简单(really simple)
assert(y>x);
//简单(simple)
assert(y>x):"y is "+y+" x is "+ x;
//这个字符串会被添加到栈踪迹中。
断言表达式规则:
Java代码
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 启用断言
标识符与关键字
[color=red]可以将assert用作关键字或者标识符,但是不能同时将它用作这两者[/color]。
使用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 适当地使用断言
不要使用断言验证公共方法的变元
可以使用断言验证私有方法的变元
不要使用断言验证命令行变元
即使在公共方法内,也可以使用断言检查从不会发生的情况,即如果到达了就抛出一个AssertionError
不要使用可能产生副作用的断言表达式,因为可能断言会关闭。