臭名昭著的goto
编程语言中一开始就有
goto关键词了。事实上,
goto起源于汇编语言的程序控制:“若条件
A成立,则跳到这里;否则跳到那里”。如果阅读由编译器最终生成的汇编代码,就会发现程序控制里包含了许多跳转。(
Java编译器生成它自己的“汇编代码”,但是这个代码是运行在
Java虚拟机上的,而不是直接运行在
CPU硬件上。)
goto语句是在源码级上的跳转,这使其招致了不好的声誉。若一个程序总是从一个地方跳到另一个地方,还有什么办法能识别程序的控制流程呢?自从Edsger Dijkstra发表了著名论文
《Goto considered harmful》(
Goto有害),众人开始痛斥
goto的不是,甚至建议将它从关键字集合中扫地出门。
对于这个问题,中庸之道是最好解决方法。真正的问题并不在于使用
goto,而在于
goto的滥用;而且少数情况下,
goto还是组织控制流程的最佳手段。
标签是什么
尽管
goto仍是
Java中的一个保留字,但在语言中并未使用它;
Java没有
goto。然而,
Java也能完成一些类似于跳转的操作,这与
break和
continue这两个关键词有关。它们其实不是一个跳转,而是中断迭代语句的一种方法。之所以把它们纳入
goto问题中一起讨论,是由于它们使用了相同的机制:标签。
标签是后面跟有冒号的标示符,就像下面这样:
label1:
在 Java中,标签起作用的唯一的地方刚好是在迭代语句之前。“刚好之前”的意思表明,在标签和迭代之间置入任何语句都不好。而在迭代之前设置标签的唯一理由是:我们希望在其中嵌套另一个迭代或者一个开关。这是由于 break和 continue关键词通常只是中断当前循环,但若随同标签一起使用,它们就会中断循环,直到标签所在的地方:
label1:
outer-iteration{
inner-iteration{
//...
break;//(1)
//...
continue;//(2)
//...
continue label1;//(3)
//...
break label1;//(4)
}
}
在(1)中,
break中断内部迭代,回到外部迭代。在(2)中,
continue使执行点移回内部迭代的起始处。在(3)中,
continue label1同时中断内部迭代及外部迭代,直接转到
label1处;随后,它实际上是继续迭代过程,但却从外部迭代开始。在(4)中,
break label1也会中断所有迭代,并回到
label1处,但并不重新进入迭代。也就是说,它实际是完全中止了两个迭代。
示例源码
下面是标签用于 for循环和 while循环的例子:package com.mufeng.thefourthchapter;
public class Labeled {
public static void main(String[] args) {
System.out.println("标签用于for循环的例子");
int i = 0;
outer: // Can't have statements here 这里不能有陈述
for (; true;) {// infinite loop 无限循环
inner: // Can't have statements here 这里不能有陈述
for (; i < 10; i++) {
System.out.println("i = " + i);
if (i == 2) {
System.out.println("continue");
continue;
}
if (i == 3) {
System.out.println("break");
i++;// Otherwise i never gets incremented 否则i不会得到增量
break;
}
if (i == 7) {
System.out.println("continue outer");
i++;// Otherwise i never gets incremented 否则i不会得到增量
continue outer;
}
if (i == 8) {
System.out.println("break outer");
break outer;
}
for (int k = 0; k < 5; k++) {
if (k == 3) {
System.out.println("continue inner");
continue inner;
}
}
}
}
System.out.println("标签用于while循环的例子");
i = 0;
outer: while (true) {
System.out.println("Outer while loop");
while (true) {
i++;
System.out.println("i = " + i);
if (i == 1) {
System.out.println("continue");
continue;
}
if (i == 3) {
System.out.println("continue outer");
continue outer;
}
if (i == 5) {
System.out.println("break");
break;
}
if (i == 7) {
System.out.println("break outer");
break outer;
}
}
}
}
}
输出结果
标签用于for循环的例子
i = 0
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer
标签用于while循环的例子
Outer while loop
i = 1
continue
i = 2
i = 3
continue outer
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer
源码解析
注意,
break会中断
for循环,而且在抵达
for循环的末尾之前,递增表达式不会执行。由于
break跳过了递增表达式,所以在
i==3的情况下直接对
i执行递增运算。在
i==7的情况下,
continue outer语句会跳到循环顶部,而且也会跳过递增,所以这里也对
i直接递增。
如果没有
break outer语句,就没办法从内部循环里跳出外部循环。这是由于
break本身只能中断最内层的循环(
continue同样也是如此)。
当然,如果想在中断循环的同时退出,简单地用一个
return即可。
同样的规则亦适用于
while:
- 一般的continue会退回最内层循环的开头(顶部),并继续执行。
- 带有标签的continue会到达标签的位置,并重新进入紧接在那个标签后面的循环。
- 一般的break会中断并跳出当前循环。
- 带标签的break会中断并跳出标签所指的循环。
要记住的重点是:在
Java里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中
break或
continue。