为什么Java允许在任意语句上标记中断?
我今天刚刚了解到以下Java代码是完全合法的:
myBlock: { /* ... code ... */ if (doneExecutingThisBlock()) break myBlock; /* ... more code ... */ }
请注意, myBlock
不是一个循环 – 它只是一个我用花括号分隔的代码块。
这似乎是一个相当奇怪的特征。 这意味着您可以使用命名break
来突破if
语句或匿名块,但通常不能在这些上下文中使用break
语句。
我的问题是: 这个设计决定有充分的理由吗? 也就是说,为什么要这样做,你只能使用带标签的break
而不是常规break
来break
某些封闭语句? 为什么要允许这种行为呢? 鉴于(相对)精心设计的Java如何作为一种语言,我认为这是有原因的,但我真的想不到一个。
这样做是为了简单起见。 如果最初标记的中断只能破坏循环语句,那么语言设计者应该立即明白限制是不必要的,语义对所有语句都是一样的。 对于语言规范的经济性,编译器的简单实现,或者只是出于普遍性的习惯,为任何语句定义标记的break,而不仅仅是循环语句。
现在我们可以回顾并判断这个选择。 通过赋予程序员额外的表达能力,它是否有益于程序员? 似乎很少,该function很少使用。 程序员是否需要学习和理解? 似乎是这样,正如这次讨论所certificate的那样。
如果你能回去改变它,不是吗? 我不能说我愿意。 我们对一般性有一种迷恋。
如果在并行Universe中它仅限于循环语句,那么有人可能会有更小的机会在stackoverflow上发布问题:为什么它不能在任意语句上工作?
可以将其视为从块而不是从整个函数返回的return
语句。 除了在函数末尾,除了在函数末尾之外的任何地方都可以应用同样的理由来应用于将对象分散到任何地方。
goto的问题在于它可以跳过代码。 带标签的rest不能那样做(它只能倒退)。 IIRC C ++必须处理goto跳过代码(自从我关心它已经超过17年了,所以我不确定我是否记得这一点)。
Java被设计为由C / C ++程序员使用,因此做了很多事情以使这些开发人员熟悉它。 可以从C / C ++到Java进行合理的翻译(虽然有些事情并不简单)。
有理由认为他们把它放到语言中给C / C ++开发人员一个安全的goto(你只能在代码中倒退),以使一些程序员转换更舒服。
我从未见过使用它,而且我在16年多的Java编程中很少看到标记符号。
你无法突破:
public class Test { public static void main(final String[] argv) { int val = 1; X: { if(argv.length == 0) { break X; } if(argv.length == 1) { break Y; <--- forward break will not compile } } val = 0; Y: { Sysytem.out.println(val); <-- if forward breaks were allowed this would print out 1 not 0. } } }
为什么要这样做,你只能使用标记的中断而不是常规中断来突破某些封闭的语句
考虑:
while (true) { if (condition) { break; } }
如果break
按照您的建议执行,则此代码将意外执行。 rest将变得更加难以使用。
为什么要允许这种行为呢?
我不使用它,但它是一个function,并允许某些独特的控制流构造。 我问你,为什么不允许它?
这个设计决定有充分的理由吗?
是。 因为它有效。
在带标签的断点情况下,您不需要在循环或开关内部这一事实可以让您表达难以用其他方式表达的内容。 (不可否认,人们很少会以这种方式使用带标签的断点……但这不是语言设计的错误。)
在未标记的中断情况下,行为是打破最内层的封闭循环或开关。 如果它是打破最内层的封闭语句,那么很多事情将难以表达,而许多事情可能需要一个标记的块。 例如:
while (...) { /* ... */ if (something) break; /* ... */ }
如果break
从最里面的封闭语句中断开,那么它就不会突破循环。
添加到Stephen C的答案, if (something)
你不能打破嵌套循环。 这些情况确实发生在数值算法中。 这里有一个简单的例子 – 你不能在没有命名的情况下突破i循环。 希望这可以帮助。
public class JBreak { private int brj; public JBreak (String arg) { brj = Integer.parseInt (arg); } public void print () { jbreak: for (int i = 1 ; i < 3 ; i++) { for (int j = 0 ; j < 5 ; j++) { if ((i*j) == brj) break jbreak; System.out.println ("i,j: " + i + "," + j); }}} public static void main (String[] args) { new JBreak(args[0]).print(); }}
它是“结构化”等同于goto,在某些情况下很有用。
我经常使用这样的标签在一个方法中创建命名子块来严格限制变量的范围,或者简单地标记一个不适合分解成单独函数的代码块。 也就是说,我用它来标记一个块,以便保留大括号周围的代码结构。 这是C中用于JNI调用的示例,我在Java中也这样做:
JNIEXPORT void JNICALL Java_xxx_SystemCall_jniChangePassword(JNIEnv *jep, jobject thsObj, jlong handle, jbyteArray rndkey, jbyteArray usrprf, jbyteArray curpwd, jbyteArray newpwd, jint pwdccs, jint tmosec) { Message rqs,rpy; thsObj=thsObj; SetupRequest: { memset(&rqs,0,sizeof(rqs)); setOpcode(&rqs,"CHGPWD"); if(!setField(mFldAndLen(rqs.rnd ),null ,jep,rndkey,"Random Key")) { return; } if(!setField(mFldAndLen(rqs.dta.chgpwd.user ),&rqs.dta.chgpwd.userLen ,jep,usrprf,"User Profile")) { return; } if(!setField(mFldAndLen(rqs.dta.chgpwd.curPass),&rqs.dta.chgpwd.curPassLen,jep,curpwd,"Cur Password")) { return; } if(!setField(mFldAndLen(rqs.dta.chgpwd.newPass),&rqs.dta.chgpwd.newPassLen,jep,newpwd,"New Password")) { return; } rqs.dta.chgpwd.ccsid=pwdccs; } ...
break语句终止带标签的语句; 它不会将控制流转移到标签上。 控制流在标记(终止)语句之后立即转移到语句。
退出嵌套循环似乎很有用。 请参阅http://download.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
它在语义上与在C#中使用Java标记的中断或者变通方法相同