3 判断语句与循环语句
限于篇幅,这里就不对Java语言中的所有语句进行介绍了,仅介绍两种常用的语句:判断语句与循环语句。
3.1 判断语句
在Java语言中判断语句有三种:if 判断语句、condition ? value1 : value2 判断语句与switch 判断语句。
在书写if 判断语句时,特别是在通过“==”做比较判断时,不要将其误写为“=”。例如:
... ... boolean isTrue = false; if (isTrue = true) { ... } ... ...
上面的代码在编译时,编译器是不会报错的,但是这却不是所期望的,因为它把比较、判断语句写成了赋值语句,怎样避免这种情况的发生呢?你可以将true 或false 写在表达式的前面,如:
... ... boolean isTrue = false; if (true == isTrue) { ... } ... ...
这样就可以避免上面错误的产生,当然上面的这种书写格式很少被使用。
另外一个方面就是不要在判断语句之前给条件变量误赋值,造成判断语句下面的代码“不可达”,例如:
... ... isTrue = false; ... if (! isTrue) { ... } else { ... } ... ...
在上面的语句中,if 语句的条件(!isTrue) 永远为真,因此后续else 子句的代码就变成不可到达的代码分支,这种错误是非常严重的逻辑错误,会影响整个应用程序的正确运行并且不易被发现,因此提醒你一定要注意这个问题。
condition ? value1 : value2 判断语句较为简洁,适用于单一分支判断,返回适合条件值的情况,也就是非此即彼,对于多分支的判断是不能采用的。
Switch 判断语句适用于分支较多的情况,虽然可以使用if else 语句嵌套实现,但是代码显得有些冗长,格式不明快。关于switch 语句的书写格式在本文前面的章节中讨论过,在此不再赘述。
3.2 循环语句
Java语言中的循环语句也分为三种:for 循环、while 循环和 do while 循环。
for 循环
不可在for 循环体内修改循环变量的值,防止for 循环失去控制。例如:
for (int i = 0; i < m; i++) { ... m++; ... }
因为你在循环体中不断地增加m的数值,使其无限增大,这样就导致了死循环的产生,或者你在循环体中修改了递进步长变量的值,如:
for (int i = 0; i < m; i++) { ... i--; ... }
同样也会导致死循环的产生,因此在书写for 循环语句时应该尽量注意这一点。另外如果在for 循环语句中步进因子(如上例中的i)的步长不是1,假设是2,此时应该特别注意for循环的书写方式,如:
for (int i = 0; i < m; i+2) { ... }
同样程序也跳不出循环体了,这是因为表达式 i+2并没有给i 赋值,因此i 的值并没有伴随循环体不断增加,正确的书写可以有下面两种方式:
for (int i = 0; i < m; i+=2) { ... } 或者, for (int i = 0; i < m;) { ... i = i + 2; }
这样就可以很好地避免上面错误的发生,导致死循环的产生。另外一个需要注意的地方是最终条件值(如上例中的m)应该是步进因子步长的整数倍。
在for 循环中我们应该尽量使用整型数作为步进因子变量的值,使用整型数比使用字节或短整型数更为高效。因为当我们采用字节或短整型数做步进因子变量的值时,系统将隐式地把它们转化为整型数,因此耗费了循环的执行时间,降低了系统性能。
while 循环
while 循环是较为常用的一种循环体格式,当我们不知道具体的循环次数时不采用for 循环而采用while 循环,while 循环每进行一次循环都要先验证一下条件是否满足,如果满足则执行循环体中的代码,否则跳出循环体。
如果你在进入while 循环之前误操作了其条件值,又可能产生死循环或根本无法进入while 循环体,如:
... ... // 误操作的赋值语句 condition = true; ... ... // 这里就会产生死循环 while (condition) { ... } ... ...
再者,
... ... // 误操作的赋值语句 condition = false; ... ... // 永远不能执行while 循环体内的代码 while (condition) { ... } ... ...
因此应该特别注意避免上面提到的误为循环条件赋值情况的发生。
do while 循环
do while 循环与while 循环类似,与之不同的是do while 循环至少可以进入循环体一次,这是由其自身的逻辑决定的,因为do while 循环是在执行一次循环后再验证条件是否成立,而while 循环则是先验证条件然后再进入循环,while 循环与do while 循环的验证与循环的逻辑分别如图3-1 和图3-2 所示。
因此,在采用do-while 循环格式时,一定要注意是否真的允许在不做任何判断的情况下进入循环体,以免产生逻辑错误。
使用循环语句的几个建议
(1)当做数组拷贝操作时,采用System.arraycopy()方法完成拷贝操作要比采用循环的办法完成数组拷贝操作效率高,下面的例子向你展示了其不同:
public class ArrayCopyTest { public static void main(String args[]) { long startIndex, endIndex; int[] a = new int[2500000]; int[] b = new int[2500000]; for (int i = 0; i < a.length; i++) { a[i] = i; } startIndex = System.currentTimeMillis(); for (int j = 0; j < a.length; j++) { b[j] = a[j]; } endIndex = System.currentTimeMillis(); System.out.println(endIndex - startIndex + " milli seconds for loop copy "); int[] c = new int[2500000]; startIndex = System.currentTimeMillis(); System.arraycopy(a, 0, c, 0, c.length); endIndex = System.currentTimeMillis(); System.out.println(endIndex - startIndex + " milli seconds for System.arraycopy() "); } }
编译并运行这个程序,输出信息如下:
105 milli seconds for loop copy
45 milli seconds for System.arraycopy()
可见其性能差别还是比较大的,采用System.arraycopy()方法完成拷贝操作要比采用循环的办法完成数组拷贝操作效率高1倍左右。
(2)尽量避免在循环体中调用方法,因为方法调用是比较昂贵的。
(3)最好避免在循环体内存取数组元素,比较好的办法是在循环体内采用临时变量,在循环体外更改数组的值。这是因为在循环体内使用变量比存取数组元素要快。
(4)当没有使用JIT或HotSpot虚拟机时,尽量使用0值作为终结条件的比较元素,以提高循环语句的性能,例如:
public class ZeroCompareTest { public static void main (String args[]) { long startIndex, endIndex; int[] a = new int[2500000]; startIndex = System.currentTimeMillis(); for (int i = 0; i < a.length; i++) { a[i] += i; } endIndex = System.currentTimeMillis(); System.out.println(endIndex - startIndex + " 毫秒(不采用零值比较)"); int[] b = new int[2500000]; startIndex = System.currentTimeMillis(); for (int i = b.length - 1; i >= 0; i--) { b[i] += i; } endIndex = System.currentTimeMillis(); System.out.println(endIndex - startIndex + " 毫秒(采用零值比较)"); } }
编译并执行这个例子,其输出信息如下:
16毫秒(不采用零值比较)
0毫秒(采用零值比较)
由此可见,使用0值作为终结条件的比较元素在很大程度上提高循环语句的性能。
(5)避免在做最终条件比较时采用方法返回值的方式进行判断,这样做将增大系统开销,降低系统性能,如:
... ... while (isTrue()) { ... } ... ...
如果按照上面的书写方式,每循环一次应该调用一次方法isTrue(),因此你最好采用下面的方式:
... ... boolean isTrue = isTrue(); while (isTrue) { ... } ... ...
(6)尽量避免在循环体中使用try-catch 块,最好在循环体外使用try-catch 块以提高系统性能,例如:
do { try { ... } catch (Exception ex) { ... } } while(isTrue);
上面的代码在你每执行一次循环的时候,都要做try-catch 检验,增大系统开销,正确的书写方式如下所示。
try { do { ... } while(isTrue); } catch (Exception ex) { ... }
这样在整个循环中只做一次try-catch 检验,避免了上面问题的出现,降低了系统开销,提高了系统性能。
(7)在多重循环中,如果有可能,尽量将最长的循环放在最内层,最短的循环放在最外层,以减少循环层间的切换次数,如:
for (int i = 0; i < 1000; i++) { for (int j = 0; j < 20; j++) { ... } }
正确的书写方式为:
for (int j = 0; j < 20; j++) { for (int i = 0; i < 1000; i++) { ... } }
(8)如果循环体内有if-else 类逻辑判断,并且循环次数很大,最好将if-else 类逻辑判断移到循环体之外,以提高应用性能。如:
for (int i = 0; i < 1000000; i++) { if (isTrue) { doThis(); } else { doThat(); } }
正确的书写方式为:
if (isTrue) { for (int i = 0; i < 1000000; i++) { doThis(); } } else { for (int i = 0; i < 1000000; i++) { doThat(); } }
这与上面的第6点建议类似,很容易理解,因为第一种书写方式比第二种书写方式多执行了999999次逻辑判断。并且由于前者总是进行逻辑判断,如果循环次数非常少,两者效率差别并不明显,采用第一种书写方式比较好,因为这种书写方式使程序更加简洁。