第三章:控制程序流程
“就象任何有感知的生物一样,程序必须能操纵自己的世界,在执行过程中作出判断与选择。”
在Java 里,我们利用运算符操纵对象和数据,并用执行控制语句作出选择。
3.1 使用Java运算符
运算符以一个或多个自变量为基础,可生成一个新值。自变量采用与原始方法调用不同的一种形式,但效果是相同的。
加号(+)、减号和负号(-)、乘号(*)、除号(/)以及等号(=)的用法与其他所有编程语言都是类似的。
所有运算符都能根据自己的运算对象生成一个值。除此以外,一个运算符可改变运算对象的值,这叫作“副作用”(Side Effect)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String 类支持“+”和“+=”。
3.1.1 优先级
运算符的优先级决定了存在多个运算符时一个表达式各部分的计算顺序。
3.1.2 赋值
赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。
由于主类型容纳了实际的值,而且并非指向一个对象的句柄,所以在为其赋值的时候,可将来自一个地方的内容复制到另一个地方。
但在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的句柄。
将一个对象传递到方法内部时,也会产生别名现象。
3.1.3 算数运算符
Java 的基本算术运算符与其他大多数程序设计语言是相同的。其中包括加号(+)、减号(-)、除号(/)、乘号(*)以及模数(%,从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。
Java 也用一种简写形式进行运算,并同时进行赋值操作。这是由等号前的一个运算符标记的,而且对于语言中的所有运算符都是固定的。例如,为了将4 加到变量x,并将结果赋给x,可用:x+=4。
3.1.4 自动递增和递减
和C 类似,Java 提供了丰富的快捷运算方式。这些快捷运算可使代码更清爽,更易录入,也更易读者辨读。两种很不错的快捷运算方式是递增和递减运算符(常称作“自动递增”和“自动递减”运算符)。其中,递减运算符是“--”,意为“减少一个单位”;递增运算符是“++”,意为“增加一个单位”。
对每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀版”和“后缀版”。“前递增”表示++运算符位于变量或表达式的前面;而“后递增”表示++运算符位于变量或表达式的后面。类似地,“前递减”意味着--运算符位于变量或表达式的前面;而“后递减”意味着--运算符位于变量或表达式的后面。对于前递增和前递减(如++A 或--A),会先执行运算,再生成值。而对于后递增和后递减(如A++或A--),会先生成值,再执行运算。
3.1.5 关系运算符
关系运算符生成的是一个“布尔”(Boolean)结果。它们评价的是运算对象值之间的关系。若关系是真实的,关系表达式会生成true(真);若关系不真实,则生成false(假)。关系运算符包括小于(<)、大于(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。等于和不等于适用于所有内建的数据类型,但其他比较不适用于boolean 类型。
检查对象是否相等,关系运算符==和!=也适用于所有对象,但它们的含义通常会使初涉Java 领域的人找不到北。==和!=比较的是对象句柄。
若想对比两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法equals()。但这个方法不适用于“主类型”,那些类型直接使用==和!=即可。equals()方法需要重写。
3.1.6 逻辑运算符
逻辑运算符AND(&&)、OR(||)以及NOT(!)能生成一个布尔值(true 或false)——以自变量的逻辑关系为基础。
只可将AND,OR 或NOT 应用于布尔值。与在C 及C++中不同,不可将一个非布尔值当作布尔值在逻辑表达式中使用。
注意若在预计为String 值的地方使用,布尔值会自动转换成适当的文本形式。
操作逻辑运算符时,我们会遇到一种名为“短路”的情况。这意味着只有明确得出整个表达式真或假的结论,才会对表达式进行逻辑求值。因此,一个逻辑表达式的所有部分都有可能不进行求值。
3.1.7 按位运算符
按位运算符允许我们操作一个整数主数据类型中的单个“比特”,即二进制位。按位运算符会对两个自变量中对应的位执行布尔代数,并最终生成一个结果。
若两个输入位都是1,则按位AND 运算符(&)在输出位里生成一个1;否则生成0。若两个输入位里至少有一个是1,则按位OR 运算符(|)在输出位里生成一个1;只有在两个输入位都是0 的情况下,它才会生成一个0。若两个输入位的某一个是1,但不全都是1,那么按位XOR(^,异或)在输出位里生成一个1。按位NOT(~,也叫作“非”运算符)属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位NOT 生成与输入位的相反的值——若输入0,则输出1;输入1,则输出0。
按位运算符可与等号(=)联合使用,以便合并运算及赋值:&=,|=和^=都是合法的(由于~是一元运算符,所以不可与=联合使用)。
它们不会中途“短路”。
3.1.8 移位运算符
移位运算符面向的运算对象也是二进制的“位”。可单独用它们处理整数类型(主类型的一种)。左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。“有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。“有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。Java 也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。
3.1.9 三元if-else运算符
布尔表达式 ? 值0:值1
若“布尔表达式”的结果为true,就计算“值0”,而且它的结果成为最终由运算符产生的值。但若“布尔表达式”的结果为false,计算的就是“值1”,而且它的结果成为最终由运算符产生的值。
3.1.10 逗号运算符
Java 里需要用到逗号的唯一场所就是for 循环
3.1.11 字串运算符+
这个运算符在Java 里有一项特殊用途:连接不同的字串。这一点已在前面的例子中展示过了。尽管与+的传统意义不符,但用+来做这件事情仍然是非常自然的。
3.1.12 运算符常规操作规则
3.1.13 造型运算符
“造型”(Cast)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。例如,假设我们为浮点变量分配一个整数值,计算机会将int 自动转换成float。通过造型,我们可明确设置这种类型的转换,或者在一般没有可能进行的时候强迫它进行。
为进行一次造型,要将括号中希望的数据类型(包括所有修改符)置于其他任何值的左侧。
需要注意的是,浮点数中的e是“exponential”基数的意思,一般就是10,而不是自然对数的基数。
3.1.14 Java没有“sizeof()”
在C 和C++中,sizeof()运算符能满足我们的一项特殊需要:获知为数据项目分配的字符数量。在C 和C++中,sizeof()最常见的一种应用就是“移植”。不同的数据在不同的机器上可能有不同的大小,所以在进行一些对大小敏感的运算时,程序员必须对那些类型有多大做到心中有数。Java 不需要sizeof() 运算符来满足这方面的需要,因为所有数据类型在所有机器的大小都是相同的。我们不必考虑移植问题——Java 本身就是一种“与平台无关”的语言。
3.2 执行控制
3.2.1 真和假
所有条件语句都利用条件表达式的真或假来决定执行流程。
3.2.2 if-else
if-else 语句或许是控制程序流程最基本的形式。其中的else 是可选的。
return 关键字有两方面的用途:指定一个方法返回什么值(假设它没有void 返回值),并立即返回那个值。
3.2.3 反复
while,do-while 和for 控制着循环,有时将其划分为“反复语句”。除非用于控制反复的布尔表达式得到“假”的结果,否则语句会重复执行下去。
3.2.4 do-while
while 和do-while 唯一的区别就是do-while 肯定会至少执行一次;也就是说,至少会将其中的语句“过一遍”——即便表达式第一次便计算为false。
3.2.5 for
for 循环在第一次反复之前要进行初始化。随后,它会进行条件测试,而且在每一次反复的时候,进行某种形式的“步进”(Stepping)。for 循环的形式如下:
for(初始表达式; 布尔表达式; 步进)
语句
无论初始表达式,布尔表达式,还是步进,都可以置空。每次反复前,都要测试一下布尔表达式。若获得的结果是false,就会继续执行紧跟在for 语句后面的那行代码。在每次循环的末尾,会计算一次步进。
3.2.6 中断和继续
在任何循环语句的主体部分,亦可用break 和continue 控制循环的流程。其中,break 用于强行退出循环,不执行循环中剩余的语句。而continue 则停止执行当前的反复,然后退回循环起始和,开始新的反复。
label1:
对Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方——在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于break 和continue 关键字通常只中断当前循环,但若随同标签使用,它们就会中断到存在标签的地方。
(1) 简单的一个continue 会退回最内层循环的开头(顶部),并继续执行。
(2) 带有标签的continue 会到达标签的位置,并重新进入紧接在那个标签后面的循环。
(3) break 会中断当前循环,并移离当前标签的末尾。
(4) 带标签的break 会中断当前循环,并移离由那个标签指示的循环的末尾。
3.2.7 开关
“开关”(Switch)有时也被划分为一种“选择语句”。根据一个整数表达式的值,switch 语句可从一系列代码选出一段执行。它的格式如下:
switch(整数选择因子) {
case 整数值1 : 语句; break;
case 整数值2 : 语句; break;
case 整数值3 : 语句; break;
case 整数值4 : 语句; break;
case 整数值5 : 语句; break;
//..
default:语句;
}
其中,“整数选择因子”是一个特殊的表达式,能产生整数值。switch 能将整数选择因子的结果与每个整数值比较。若发现相符的,就执行对应的语句(简单或复合语句)。若没有发现相符的,就执行default 语句。
但它要求使用一个选择因子,并且必须是int 或char 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在switch 语句里是不会工作的。