【JAVA优化编程】表达式、语句与保留字之——(3)判断语句与循环语句

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 所示。

while循环

 

    因此,在采用do-while 循环格式时,一定要注意是否真的允许在不做任何判断的情况下进入循环体,以免产生逻辑错误。

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次逻辑判断。并且由于前者总是进行逻辑判断,如果循环次数非常少,两者效率差别并不明显,采用第一种书写方式比较好,因为这种书写方式使程序更加简洁。

你可能感兴趣的:(java,编程,J#)