3. 控制程序流程

本文介绍了Java中的运算符,包括赋值操作(尤其是对象赋值的别名现象)、自动递增/递减、关系运算符、对象相等性判断、逻辑运算符、按位和移位运算,以及各种控制结构如if-else、while、do-while、for和switch的用法。
摘要由CSDN通过智能技术生成

Version:1.0 StartHTML:0000000163 EndHTML:0000092209 StartFragment:0000041077 EndFragment:0000092169 SourceURL:file:///Z:/thinking_in_java/java_reveiwv2.docx

3.1 Java运算符

几乎所有运算符都只能操作原始数据类型(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作所有对象(也是对象易令人混淆的一个地方)。除此以外,String 类支持“+”和“+=”。

赋值

赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。右边的值可以是任何常 数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说, 它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4;),但不可 将任何东西赋给一个常数(比如不能4=A)。

但在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的句柄。所 以倘若“从一个对象到另一个对象”赋值,实际就是将句柄从一个地方复制到另一个地方。这意味着假若为 对象使用“C=D”,那么C 和 D 最终都会指向最初只有 D 才指向的那个对象

Example

class Number {

          int i;

         }

         public class Assignment {

          public static void main(String[] args) {

          Number n1 = new Number();

          Number n2 = new Number();

          n1.i = 9;

          n2.i = 47;

          System.out.println("1: n1.i: " + n1.i +

          ", n2.i: " + n2.i);

          n1 = n2;

          System.out.println("2: n1.i: " + n1.i +

          ", n2.i: " + n2.i);

          n1.i = 27;

          System.out.println("3: n1.i: " + n1.i +

          ", n2.i: " + n2.i);

          }

         }

Console output

1: n1.i: 9, n2.i: 47

2: n1.i: 47, n2.i: 47

3: n1.i: 27, n2.i: 27

看来改变n1 的同时也改变了n2!这是由于无论n1 还是n2 都包含了相同的句柄,它指向相同的对象(最初 的句柄位于 n1 内部,指向容纳了值9 的一个对象。在赋值过程中,那个句柄实际已经丢失;它的对象会由 “垃圾收集器”自动清除)。 这种特殊的现象通常也叫作“别名”,是 Java 操作对象的一种基本方式。但假若不愿意在这种情况下出现别 名,又该怎么操作呢?可放弃赋值,并写入下述代码:

n1.i = n2.i;

这样便可保留两个独立的对象,而不是将 n1 和n2 绑定到相同的对象。

自动递增和递减

对每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀版”和“后缀版”。“前递增”表示++ 运算符位于变量或表达式的前面;而“后递增”表示++运算符位于变量或表达式的后面。类似地,“前递 减”意味着--运算符位于变量或表达式的前面;而“后递减”意味着--运算符位于变量或表达式的后面。对 于前递增和前递减(如++A 或--A),会先执行运算,再生成值。而对于后递增和后递减(如A++或A--), 会先生成值,再执行运算。下面是一个例子:

//: AutoInc.java

//Demonstrates the ++ and -- operators

public class Assignment {

public static void main(String[] args) {

int i = 1;

prt("i : " + i);

prt("++i : " + ++i); // Pre-increment

prt("i++ : " + i++); // Post-increment

prt("i : " + i);

prt("--i : " + --i); // Pre-decrement

prt("i-- : " + i--); // Post-decrement

prt("i : " + i);

}

static void prt(String s) {

System.out.println(s);

}

} ///:~

关系运算符

关系运算符生成的是一个“布尔”(Boolean)结果。它们评价的是运算对象值之间的关系。若关系是真实

的,关系表达式会生成 true(真);若关系不真实,则生成false(假)。关系运算符包括小于(<)、大于

(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。等于和不等于适用于所有内

建的数据类型,但其他比较不适用于boolean 类型。

检查对象是否相等

//: Equivalence.java

public class Assignment {

public static void main(String[] args) {

Integer n1 = new Integer(47);

Integer n2 = new Integer(47);

System.out.println(n1 == n2);

System.out.println(n1 != n2);

}

} ///:~

其中,表达式System.out.println(n1 == n2)可打印出内部的布尔比较结果。一般人都会认为输出结果肯定

先是true,再是 false,因为两个 Integer 对象都是相同的。但尽管对象的内容相同,句柄却是不同的,而

==和!=比较的正好就是对象句柄。所以输出结果实际上先是 false,再是 true。这自然会使第一次接触的人

感到惊奇。

若想对比两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法

equals()。

//: EqualsMethod.java

public class EqualsMethod {

 public static void main(String[] args) {

 Integer n1 = new Integer(47);

 Integer n2 = new Integer(47);

 System.out.println(n1.equals(n2));

 }

} ///:~

正如我们预计的那样,此时得到的结果是 true。但事情并未到此结束!假设您创建了自己的类,就象下面这样:

//: EqualsMethod2.java

class Value {

 int i;

}

public class EqualsMethod2 {

 public static void main(String[] args) {

 Value v1 = new Value();

 Value v2 = new Value();

 v1.i = v2.i = 100;

System.out.println(v1.equals(v2));

 }

} ///:~

此时的结果又变回了false!这是由于 equals()的默认行为是比较句柄。所以除非在自己的新类中改变了

equals(),否则不可能表现出我们希望的行为。不幸的是,要到第 7 章才会学习如何改变行为。但要注意

equals()的这种行为方式同时或许能够避免一些“灾难”性的事件。

大多数 Java 类库都实现了 equals(),所以它实际比较的是对象的内容,而非它们的句柄。

逻辑运算符

逻辑运算符 AND(&&)、OR(||)以及 NOT(!)能生成一个布尔值(true 或 false)——以自变量的逻辑关系为基础。

短路

操作逻辑运算符时,我们会遇到一种名为“短路”的情况。这意味着只有明确得出整个表达式真或假的结

论,才会对表达式进行逻辑求值。因此,一个逻辑表达式的所有部分都有可能不进行求值:

public class Assignment {

          static boolean test1(int val) {

          System.out.println("test1(" + val + ")");

          System.out.println("result: " + (val < 1));

          return val < 1;

          }

          static boolean test2(int val) {

          System.out.println("test2(" + val + ")");

          System.out.println("result: " + (val < 2));

          return val < 2;

          }

          static boolean test3(int val) {

          System.out.println("test3(" + val + ")");

          System.out.println("result: " + (val < 3));

          return val < 3;

          }

          public static void main(String[] args) {

          if(test1(0) && test2(2) && test3(2))

          System.out.println("expression is true");

          else

          System.out.println("expression is false");

          }

         } ///:~

第一个测试生成一个true 结果,所以表达式求值会继续下去。然而,第二个测试产生了一个 false 结果。由于这意味着整个表达式肯定为 false,所以为什么还要继续剩余的表达式呢?这样做只会徒劳无益。事实

上,“短路”一词的由来正种因于此。如果一个逻辑表达式的所有部分都不必执行下去,那么潜在的性能提

升将是相当可观的。

按位运算符

按位运算符允许我们操作一个整数主数据类型中的单个“比特”,即二进制位。按位运算符会对两个自变量

中对应的位执行布尔代数,并最终生成一个结果。

按位运算来源于C 语言的低级操作。我们经常都要直接操纵硬件,需要频繁设置硬件寄存器内的二进制位。

Java 的设计初衷是嵌入电视顶置盒内,所以这种低级操作仍被保留下来了。然而,由于操作系统的进步,现在也许不必过于频繁地进行按位运算。

若两个输入位都是 1,则按位AND 运算符(&)在输出位里生成一个 1;否则生成 0。若两个输入位里至少有一个是 1,则按位 OR 运算符(|)在输出位里生成一个 1;只有在两个输入位都是0 的情况下,它才会生成一个0。若两个输入位的某一个是1,但不全都是1,那么按位 XOR(^,异或)在输出位里生成一个 1。按位NOT(~,也叫作“非”运算符)属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位 NOT 生成与输入位的相反的值——若输入 0,则输出1;输入 1,则输出0。

按位运算符和逻辑运算符都使用了同样的字符,只是数量不同。因此,我们能方便地记忆各自的含义:由于“位”是非常“小”的,所以按位运算符仅使用了一个字符。

按位运算符可与等号(=)联合使用,以便合并运算及赋值:&=,|=和^=都是合法的(由于~是一元运算符,所以不可与=联合使用)。我们将 boolean(布尔)类型当作一种“单位”或“单比特”值对待,所以它多少有些独特的地方。我们可执行按位AND,OR 和 XOR,但不能执行按位 NOT(大概是为了避免与逻辑 NOT 混淆)。对于布尔值,按位运算符具有与逻辑运算符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的按位运算为我们新增了一个XOR 逻辑运算符,它并未包括在“逻辑”运算符的列表中。在移位表达式中,我们被禁止使用布尔运算,原因将在下面解释。

移位运算符

移位运算符面向的运算对象也是二进制的“位”。可单独用它们处理整数类型(主类型的一种)。左移位运

算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补 0)。“有符号”右移位

运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。“有符号”右移位运算符使用了

“符号扩展”:若值为正,则在高位插入 0;若值为负,则在高位插入1。Java 也添加了一种“无符号”右

移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C 或C++没有的。

若对char,byte 或者short 进行移位处理,那么在移位进行之前,它们会自动转换成一个 int。只有右侧的5个低位才会用到。这样可防止我们在一个 int 数里移动不切实际的位数。若对一个 long 值进行处理,最后得到的结果也是long。此时只会用到右侧的 6 个低位,防止移动超过 long 值里现成的位数。但在进行“无符号”右移位时,也可能遇到一个问题。若对 byte 或short 值进行右移位运算,得到的可能不是正确的结果(Java 1.0 和Java 1.1 特别突出)。它们会自动转换成int 类型,并进行右移位。但“零扩展”不会发

生,所以在那些情况下会得到-1 的结果。

三元if -else运算符

布尔表达式 ? 值 0:值 1

若“布尔表达式”的结果为true,就计算“值0”,而且它的结果成为最终由运算符产生的值。但若“布尔

表达式”的结果为 false,计算的就是“值 1”,而且它的结果成为最终由运算符产生的值。

当然,也可以换用普通的if-else 语句(在后面介绍),但三元运算符更加简洁。尽管 C 引以为傲的就是它

是一种简练的语言,而且三元运算符的引入多半就是为了体现这种高效率的编程,但假若您打算频繁用它,

还是要先多作一些思量——它很容易就会产生可读性极差的代码。

造型运算符

“造型”(Cast)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一

种。例如,假设我们为浮点变量分配一个整数值,计算机会将 int 自动转换成 float。通过造型,我们可明

确设置这种类型的转换,或者在一般没有可能进行的时候强迫它进行。

为进行一次造型,要将括号中希望的数据类型(包括所有修改符)置于其他任何值的左侧。下面是一个例

子:

void casts() {

int i = 200;

long l = (long)i;

long l2 = (long)200;

}

正如您看到的那样,既可对一个数值进行造型处理,亦可对一个变量进行造型处理。但在这儿展示的两种情况下,造型均是多余的,因为编译器在必要的时候会自动进行 int 值到long 值的转换。当然,仍然可以设置一个造型,提醒自己留意,也使程序更清楚。在其他情况下,造型只有在代码编译时才显出重要性。

大家可以看到,除 boolean 以外,任何一种主类型都可通过造型变为其他主类型。同样地,当造型成一种较小的类型时,必须留意“缩小转换”的后果。否则会在造型过程中不知不觉地丢失信息。

3.2 执行控制

Java和C,C++的真和假

所有条件语句都利用条件表达式的真或假来决定执行流程。条件表达式的一个例子是 A==B。它用条件运算符“==”来判断A 值是否等于 B 值。该表达式返回 true 或 false。本章早些时候接触到的所有关系运算符都可拿来构造一个条件语句。注意 Java 不允许我们将一个数字作为布尔值使用,即使它在 C 和 C++里是允许的(真是非零,而假是零)。若想在一次布尔测试中使用一个非布尔值——比如在 if(a)里,那么首先必须用一个条件表达式将其转换成一个布尔值,例如 if(a!=0)。

If-else

if(布尔表达式)

语句

else

语句

While

while(布尔表达式)

语句

在循环刚开始时,会计算一次“布尔表达式”的值。而对于后来每一次额外的循环,都会在开始前重新计算

一次。

do-while

do

语句

while(布尔表达式)

while 和do-while 唯一的区别就是do-while 肯定会至少执行一次;也就是说,至少会将其中的语句“过一

遍”——即便表达式第一次便计算为false。而在 while 循环结构中,若条件第一次就为false,那么其中的

语句根本不会执行。在实际应用中,while 比 do-while 更常用一些。

For

for 循环在第一次反复之前要进行初始化。随后,它会进行条件测试,而且在每一次反复的时候,进行某种

形式的“步进”(Stepping)。for 循环的形式如下:

for(初始表达式; 布尔表达式; 步进)

语句

无论初始表达式,布尔表达式,还是步进,都可以置空。每次反复前,都要测试一下布尔表达式。若获得的结果是 false,就会继续执行紧跟在 for 语句后面的那行代码。在每次循环的末尾,会计算一次步进。

for 循环通常用于执行“计数”任务。

中断和继续

在任何循环语句的主体部分,亦可用break 和continue 控制循环的流程。其中,break 用于强行退出循环,

不执行循环中剩余的语句。而continue 则停止执行当前的反复,然后退回循环起始和,开始新的反复。

开关switch

“开关”(Switch)有时也被划分为一种“选择语句”。根据一个整数表达式的值,switch 语句可从一系列

代码选出一段执行。它的格式如下:

switch(整数选择因子) {

case 整数值1 : 语句; break;

case 整数值2 : 语句; break;

case 整数值3 : 语句; break;

case 整数值4 : 语句; break;

case 整数值5 : 语句; break;

//..

default:语句;

其中,“整数选择因子”是一个特殊的表达式,能产生整数值。switch 能将整数选择因子的结果与每个整数

值比较。若发现相符的,就执行对应的语句(简单或复合语句)。若没有发现相符的,就执行default 语

句。

在上面的定义中,大家会注意到每个case 均以一个break 结尾。这样可使执行流程跳转至switch 主体的末

尾。这是构建switch 语句的一种传统方式,但break 是可选的。若省略break,会继续执行后面的case 语

句的代码,直到遇到一个break 为止。尽管通常不想出现这种情况,但对有经验的程序员来说,也许能够善

加利用。注意最后的default 语句没有break,因为执行流程已到了break 的跳转目的地。当然,如果考虑

到编程风格方面的原因,完全可以在default 语句的末尾放置一个break,尽管它并没有任何实际的用处。

switch 语句是实现多路选择的一种易行方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择

因子,并且必须是int 或char 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它

们在switch 语句里是不会工作的。对于非整数类型,则必须使用一系列if 语句。

将一个float 或double 值造型成整数值后,总是将小数部分“砍掉”,不作任何进位处理。

如果没有break语句,那么程序会继续向下执行。直到有下一个break出现跳出switch。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值